diff --git a/src/main/java/space/space_spring/controller/SpaceController.java b/src/main/java/space/space_spring/controller/SpaceController.java index 057154ce..aa5fb9c8 100644 --- a/src/main/java/space/space_spring/controller/SpaceController.java +++ b/src/main/java/space/space_spring/controller/SpaceController.java @@ -5,22 +5,21 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import space.space_spring.argument_resolver.jwtLogin.JwtLoginAuth; import space.space_spring.dto.space.GetUserInfoBySpaceResponse; import space.space_spring.dto.space.PostSpaceCreateRequest; -import space.space_spring.dto.space.PostSpaceCreateResponse; -import space.space_spring.entity.UserSpace; +import space.space_spring.exception.MultipartFileException; import space.space_spring.exception.SpaceException; import space.space_spring.response.BaseResponse; import space.space_spring.service.S3Uploader; import space.space_spring.service.SpaceService; -import space.space_spring.util.userSpace.UserSpaceUtils; import java.io.IOException; -import java.util.Optional; import static space.space_spring.response.status.BaseExceptionResponseStatus.INVALID_SPACE_CREATE; +import static space.space_spring.response.status.BaseExceptionResponseStatus.IS_NOT_IMAGE_FILE; import static space.space_spring.util.bindingResult.BindingResultUtils.getErrorMessage; @RestController @@ -30,33 +29,37 @@ public class SpaceController { private final SpaceService spaceService; - private final UserSpaceUtils userSpaceUtils; private final S3Uploader s3Uploader; private final String spaceImgDirName = "spaceImg"; - @PostMapping("/create") - public BaseResponse createSpace(@JwtLoginAuth Long userId, @Validated @ModelAttribute PostSpaceCreateRequest postSpaceCreateRequest, BindingResult bindingResult) throws IOException { + @PostMapping("") + public BaseResponse createSpace(@JwtLoginAuth Long userId, @Validated @ModelAttribute PostSpaceCreateRequest postSpaceCreateRequest, BindingResult bindingResult) throws IOException { if (bindingResult.hasErrors()) { throw new SpaceException(INVALID_SPACE_CREATE, getErrorMessage(bindingResult)); } // TODO 1. 스페이스 썸네일을 s3에 upload - String spaceImgUrl = s3Uploader.upload(postSpaceCreateRequest.getSpaceProfileImg(), spaceImgDirName); + String spaceImgUrl = processSpaceImage(postSpaceCreateRequest.getSpaceProfileImg()); // TODO 2. s3에 저장하고 받은 이미지 url 정보와 spaceName 정보로 space create 작업 수행 - return new BaseResponse<>(spaceService.createSpace(userId, postSpaceCreateRequest.getSpaceName(), spaceImgUrl)); + spaceService.createSpace(userId, postSpaceCreateRequest.getSpaceName(), spaceImgUrl); + + return new BaseResponse<>("스페이스 생성 성공"); } - /** - * 테스트 용 - */ - @GetMapping("/{spaceId}") - public BaseResponse getSpaceHome(@JwtLoginAuth Long userId, @PathVariable Long spaceId) { - Optional userInSpace = userSpaceUtils.isUserInSpace(userId, spaceId); - log.info("userInSpace.get().getUserName() = {}", userInSpace.get().getUserName()); - log.info("userInspace.get().getUserSpaceAuth() = {}", userInSpace.get().getUserSpaceAuth()); + private String processSpaceImage(MultipartFile spaceProfileImg) throws IOException { + if (spaceProfileImg == null) { + return null; + } + validateImageFile(spaceProfileImg); + return s3Uploader.upload(spaceProfileImg, spaceImgDirName); - return new BaseResponse<>("스페이스에 속한 유저만 걸러내는 작업 테스트 성공"); + } + + private void validateImageFile(MultipartFile spaceProfileImg) { + if (!s3Uploader.isFileImage(spaceProfileImg)) { + throw new MultipartFileException(IS_NOT_IMAGE_FILE); + } } /** diff --git a/src/main/java/space/space_spring/controller/UserController.java b/src/main/java/space/space_spring/controller/UserController.java index fc7f3be9..09f60b42 100644 --- a/src/main/java/space/space_spring/controller/UserController.java +++ b/src/main/java/space/space_spring/controller/UserController.java @@ -31,25 +31,29 @@ public class UserController { * 회원가입 */ @PostMapping("/signup") - public BaseResponse signup(@Validated @RequestBody PostUserSignupRequest postUserSignupRequest, BindingResult bindingResult) { + public BaseResponse signup(@Validated @RequestBody PostUserSignupRequest postUserSignupRequest, BindingResult bindingResult) { if (bindingResult.hasErrors()) { throw new UserException(INVALID_USER_SIGNUP, getErrorMessage(bindingResult)); } - return new BaseResponse<>(userService.signup(postUserSignupRequest)); + + userService.signup(postUserSignupRequest); + + return new BaseResponse<>("로컬 회원가입 성공"); } /** * 로그인 */ @PostMapping("/login") - public BaseResponse login(@Validated @RequestBody PostUserLoginRequest postUserLoginRequest, BindingResult bindingResult, HttpServletResponse response) { + public BaseResponse login(@Validated @RequestBody PostUserLoginRequest postUserLoginRequest, BindingResult bindingResult, HttpServletResponse response) { if (bindingResult.hasErrors()) { throw new UserException(INVALID_USER_LOGIN, getErrorMessage(bindingResult)); } String jwtLogin = userService.login(postUserLoginRequest); response.setHeader("Authorization", "Bearer " + jwtLogin); - return new BaseResponse<>(new PostUserLoginResponse("로그인 성공")); + + return new BaseResponse<>("로컬 로그인 성공"); } /** diff --git a/src/main/java/space/space_spring/dto/space/PostSpaceCreateRequest.java b/src/main/java/space/space_spring/dto/space/PostSpaceCreateRequest.java index f92e5774..9d0ae435 100644 --- a/src/main/java/space/space_spring/dto/space/PostSpaceCreateRequest.java +++ b/src/main/java/space/space_spring/dto/space/PostSpaceCreateRequest.java @@ -1,12 +1,12 @@ package space.space_spring.dto.space; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.validator.constraints.Length; import org.springframework.web.multipart.MultipartFile; -import space.space_spring.validator.ValidFile; @Getter @Setter @@ -17,6 +17,6 @@ public class PostSpaceCreateRequest { @NotBlank(message = "스페이스 이름은 공백일 수 없습니다.") private String spaceName; - @ValidFile(message = "스페이스 프로필 이미지는 공백일 수 없습니다.") + @Nullable private MultipartFile spaceProfileImg; // 스페이스 프로필 이미지 (썸네일) } diff --git a/src/main/java/space/space_spring/dto/space/PostSpaceCreateResponse.java b/src/main/java/space/space_spring/dto/space/PostSpaceCreateResponse.java deleted file mode 100644 index bc0c2031..00000000 --- a/src/main/java/space/space_spring/dto/space/PostSpaceCreateResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package space.space_spring.dto.space; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class PostSpaceCreateResponse { - - private Long spaceId; - private String spaceImgUrl; // 사진 url이 잘 생성됐는지 확인하는 용도 -} diff --git a/src/main/java/space/space_spring/dto/user/PostUserLoginRequest.java b/src/main/java/space/space_spring/dto/user/PostUserLoginRequest.java index 69f8768f..615b2e72 100644 --- a/src/main/java/space/space_spring/dto/user/PostUserLoginRequest.java +++ b/src/main/java/space/space_spring/dto/user/PostUserLoginRequest.java @@ -11,7 +11,8 @@ @NoArgsConstructor public class PostUserLoginRequest { - @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.") // 이메일 정규표현식 확인 필요함 + // '@', '.' 이 있어야 함 + @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.") @NotBlank private String email; diff --git a/src/main/java/space/space_spring/dto/user/PostUserLoginResponse.java b/src/main/java/space/space_spring/dto/user/PostUserLoginResponse.java deleted file mode 100644 index 29b631c5..00000000 --- a/src/main/java/space/space_spring/dto/user/PostUserLoginResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package space.space_spring.dto.user; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class PostUserLoginResponse { - - private String successMsg; -} diff --git a/src/main/java/space/space_spring/dto/user/PostUserSignupRequest.java b/src/main/java/space/space_spring/dto/user/PostUserSignupRequest.java index c483849d..f9a0f208 100644 --- a/src/main/java/space/space_spring/dto/user/PostUserSignupRequest.java +++ b/src/main/java/space/space_spring/dto/user/PostUserSignupRequest.java @@ -14,7 +14,8 @@ @AllArgsConstructor public class PostUserSignupRequest { - @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.") // 이메일 정규표현식 확인 필요함 + // '@', '.' 이 있어야 함 + @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.") @NotBlank private String email; diff --git a/src/main/java/space/space_spring/dto/user/PostUserSignupResponse.java b/src/main/java/space/space_spring/dto/user/PostUserSignupResponse.java deleted file mode 100644 index 6d34b743..00000000 --- a/src/main/java/space/space_spring/dto/user/PostUserSignupResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package space.space_spring.dto.user; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class PostUserSignupResponse { - - private Long userId; -} diff --git a/src/main/java/space/space_spring/exception/MultipartFileException.java b/src/main/java/space/space_spring/exception/MultipartFileException.java new file mode 100644 index 00000000..3292f14a --- /dev/null +++ b/src/main/java/space/space_spring/exception/MultipartFileException.java @@ -0,0 +1,20 @@ +package space.space_spring.exception; + +import lombok.Getter; +import space.space_spring.response.status.ResponseStatus; + +@Getter +public class MultipartFileException extends RuntimeException { + + private final ResponseStatus exceptionStatus; + + public MultipartFileException(ResponseStatus exceptionStatus) { + super(exceptionStatus.getMessage()); + this.exceptionStatus = exceptionStatus; + } + + public MultipartFileException(ResponseStatus exceptionStatus, String message) { + super(message); + this.exceptionStatus = exceptionStatus; + } +} diff --git a/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java b/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java index 0e2e52d3..3999db2c 100644 --- a/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java +++ b/src/main/java/space/space_spring/response/status/BaseExceptionResponseStatus.java @@ -76,8 +76,12 @@ public enum BaseExceptionResponseStatus implements ResponseStatus { /** * 8000: Chat 오류 */ - INVALID_CHATROOM_CREATE(8000, HttpStatus.BAD_REQUEST.value(), "채팅방 생성 요청에서 잘못된 값이 존재합니다."); + INVALID_CHATROOM_CREATE(8000, HttpStatus.BAD_REQUEST.value(), "채팅방 생성 요청에서 잘못된 값이 존재합니다."), + /** + * 9000 : MultipartFile 오류 + */ + IS_NOT_IMAGE_FILE(9000, HttpStatus.BAD_REQUEST.value(), "지원되는 이미지 파일의 형식이 아닙니다."); private final int code; private final int status; diff --git a/src/main/java/space/space_spring/service/S3Uploader.java b/src/main/java/space/space_spring/service/S3Uploader.java index 3fa8e348..1eb4eb17 100644 --- a/src/main/java/space/space_spring/service/S3Uploader.java +++ b/src/main/java/space/space_spring/service/S3Uploader.java @@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import space.space_spring.validator.AllowedImageFileExtensions; import java.io.File; import java.io.FileOutputStream; @@ -26,7 +27,7 @@ public class S3Uploader { private String bucket; //MultipartFile을 전달 받아 File로 전환 후 S3 업로드 - public String upload(MultipartFile multipartFile, String dirName) throws IOException{// dirName의 디렉토리가 S3 Bucket 내부에 생성됨 + public String upload(MultipartFile multipartFile, String dirName) throws IOException{// dirName의 디렉토리가 S3 Bucket 내부에 생성됨 File uploadFile = convert(multipartFile).orElseThrow(()-> new IllegalArgumentException("MultipartFile -> File 전환 실패")); //System.out.p @@ -64,4 +65,14 @@ private Optional convert(MultipartFile file) throws IOException { } return Optional.empty(); } + + // MultipartFile이 지원하는 이미지 파일 형식인지 검증 + public boolean isFileImage(MultipartFile file) { + String fileName = file.getOriginalFilename(); + String extension = fileName.substring(fileName.lastIndexOf(".") + 1); + + log.info("extension : {}", extension); + + return AllowedImageFileExtensions.contains(extension); + } } diff --git a/src/main/java/space/space_spring/service/SpaceService.java b/src/main/java/space/space_spring/service/SpaceService.java index f17c12f8..6f709d9b 100644 --- a/src/main/java/space/space_spring/service/SpaceService.java +++ b/src/main/java/space/space_spring/service/SpaceService.java @@ -7,14 +7,11 @@ import space.space_spring.dao.UserDao; import space.space_spring.dao.UserSpaceDao; import space.space_spring.dto.space.GetUserInfoBySpaceResponse; -import space.space_spring.dto.space.PostSpaceCreateResponse; import space.space_spring.entity.Space; import space.space_spring.entity.User; -import space.space_spring.dto.space.PostSpaceCreateRequest; import space.space_spring.entity.UserSpace; import space.space_spring.util.space.SpaceUtils; -import java.util.List; @Service @RequiredArgsConstructor @@ -26,7 +23,7 @@ public class SpaceService { private final SpaceUtils spaceUtils; @Transactional - public PostSpaceCreateResponse createSpace(Long userId, String spaceName, String spaceImgUrl) { + public Long createSpace(Long userId, String spaceName, String spaceImgUrl) { // TODO 1. 스페이스 생성 정보 db insert Space saveSpace = spaceDao.saveSpace(spaceName, spaceImgUrl); @@ -35,7 +32,7 @@ public PostSpaceCreateResponse createSpace(Long userId, String spaceName, String User manager = userDao.findUserByUserId(userId); UserSpace userSpace = userSpaceDao.createUserSpace(manager, saveSpace); - return new PostSpaceCreateResponse(saveSpace.getSpaceId(), spaceImgUrl); + return saveSpace.getSpaceId(); } @Transactional diff --git a/src/main/java/space/space_spring/service/UserService.java b/src/main/java/space/space_spring/service/UserService.java index 551e5720..f03d3523 100644 --- a/src/main/java/space/space_spring/service/UserService.java +++ b/src/main/java/space/space_spring/service/UserService.java @@ -27,7 +27,7 @@ public class UserService { private final UserUtils userUtils; @Transactional - public PostUserSignupResponse signup(PostUserSignupRequest postUserSignupRequest) { + public Long signup(PostUserSignupRequest postUserSignupRequest) { // TODO 1. 이메일 중복 검사(아이디 중복 검사) validateEmailForLocalSignup(postUserSignupRequest.getEmail()); @@ -40,7 +40,7 @@ public PostUserSignupResponse signup(PostUserSignupRequest postUserSignupRequest User saveUser = userDao.saveUser(email, password, userName, LOCAL); - return new PostUserSignupResponse(saveUser.getUserId()); + return saveUser.getUserId(); } private void validateEmailForLocalSignup(String email) { diff --git a/src/main/java/space/space_spring/validator/AllowedImageFileExtensions.java b/src/main/java/space/space_spring/validator/AllowedImageFileExtensions.java new file mode 100644 index 00000000..b863798d --- /dev/null +++ b/src/main/java/space/space_spring/validator/AllowedImageFileExtensions.java @@ -0,0 +1,35 @@ +package space.space_spring.validator; + +import lombok.Getter; + +@Getter +public enum AllowedImageFileExtensions { + /** + * 허용가능한 이미지 파일 확장자 list + */ + JPEG("jpeg"), + JPG("jpg"), + PNG("png"), + GIF("gif"), + WebP("webp"), + SVG("svg"), + BMP("bmp"), + TIF("tif"), + TIFF("tiff"), + HEIC("heic"); + + private String extension; + + AllowedImageFileExtensions(String extension) { + this.extension = extension; + } + + public static boolean contains(String extension) { + for (AllowedImageFileExtensions allowedImageFileExtensions : AllowedImageFileExtensions.values()) { + if (allowedImageFileExtensions.getExtension().equals(extension)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/space/space_spring/validator/ValidFile.java b/src/main/java/space/space_spring/validator/ValidFile.java deleted file mode 100644 index 6981573c..00000000 --- a/src/main/java/space/space_spring/validator/ValidFile.java +++ /dev/null @@ -1,18 +0,0 @@ -package space.space_spring.validator; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = ValidFileValidator.class) -public @interface ValidFile { - String message() default "유효하지 않은 파일입니다."; - Class[] groups() default {}; - Class[] payload() default {}; -} diff --git a/src/main/java/space/space_spring/validator/ValidFileValidator.java b/src/main/java/space/space_spring/validator/ValidFileValidator.java deleted file mode 100644 index 73f814b9..00000000 --- a/src/main/java/space/space_spring/validator/ValidFileValidator.java +++ /dev/null @@ -1,14 +0,0 @@ -package space.space_spring.validator; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import org.springframework.web.multipart.MultipartFile; - -public class ValidFileValidator implements ConstraintValidator { - - // multipartFile이 null 이 아니거나 비어있지 않으면 검증로직 통과 - @Override - public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext constraintValidatorContext) { - return multipartFile != null && !multipartFile.isEmpty(); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index fbe4dd4f..fb5e0036 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -107,4 +107,10 @@ spring: messages: basename: errors +--- +spring: + servlet: + multipart: + max-file-size: 5MB + max-request-size: 5MB \ No newline at end of file