From 8bc53a7c8b9c6cca00999d6bf15cc4980b4db782 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 04:19:15 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20s3=20sdk=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C=20-=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +- .../s3/config/AmazonS3Config.java | 20 ++--- .../domain/{ImgType.java => UploadType.java} | 6 +- .../s3/service/FileUploadService.java | 82 +++++++++---------- .../solidconnection/s3/service/S3Service.java | 54 ++++++------ 5 files changed, 82 insertions(+), 83 deletions(-) rename src/main/java/com/example/solidconnection/s3/domain/{ImgType.java => UploadType.java} (79%) diff --git a/build.gradle b/build.gradle index 91cc2e77d..014a60951 100644 --- a/build.gradle +++ b/build.gradle @@ -65,8 +65,9 @@ dependencies { testImplementation 'org.awaitility:awaitility:4.2.0' // Etc + implementation platform('software.amazon.awssdk:bom:2.41.4') + implementation 'software.amazon.awssdk:s3' implementation 'org.hibernate.validator:hibernate-validator' - implementation 'com.amazonaws:aws-java-sdk-s3:1.12.782' implementation 'org.springframework.boot:spring-boot-starter-websocket' } diff --git a/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java b/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java index 3b19cecfa..69d3426a2 100644 --- a/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java +++ b/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java @@ -1,12 +1,12 @@ package com.example.solidconnection.s3.config; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; @Configuration public class AmazonS3Config { @@ -21,12 +21,12 @@ public class AmazonS3Config { private String region; @Bean - public AmazonS3Client amazonS3Client() { - BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); - return (AmazonS3Client) AmazonS3ClientBuilder - .standard() - .withRegion(region) - .withCredentials(new AWSStaticCredentialsProvider(credentials)) + public S3Client s3Client() { + AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey); + + return S3Client.builder() + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) .build(); } } diff --git a/src/main/java/com/example/solidconnection/s3/domain/ImgType.java b/src/main/java/com/example/solidconnection/s3/domain/UploadType.java similarity index 79% rename from src/main/java/com/example/solidconnection/s3/domain/ImgType.java rename to src/main/java/com/example/solidconnection/s3/domain/UploadType.java index b26d5fc10..89c563d67 100644 --- a/src/main/java/com/example/solidconnection/s3/domain/ImgType.java +++ b/src/main/java/com/example/solidconnection/s3/domain/UploadType.java @@ -3,19 +3,19 @@ import lombok.Getter; @Getter -public enum ImgType { +public enum UploadType { PROFILE("profile"), GPA("gpa"), LANGUAGE_TEST("language"), COMMUNITY("community"), NEWS("news"), - CHAT("chat"), + CHAT("chat/files"), MENTOR_PROOF("mentor-proof"), ; private final String type; - ImgType(String type) { + UploadType(String type) { this.type = type; } } diff --git a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java index 51ef4caa9..a93d05975 100644 --- a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java +++ b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java @@ -1,51 +1,49 @@ -package com.example.solidconnection.s3.service; + package com.example.solidconnection.s3.service; -import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; -import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; + import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; + import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.SdkClientException; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.CannedAccessControlList; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.example.solidconnection.common.exception.CustomException; -import java.io.IOException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; + import com.example.solidconnection.common.exception.CustomException; + import java.io.IOException; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + import org.springframework.scheduling.annotation.Async; + import org.springframework.stereotype.Component; + import org.springframework.web.multipart.MultipartFile; + import software.amazon.awssdk.core.exception.SdkException; + import software.amazon.awssdk.core.sync.RequestBody; + import software.amazon.awssdk.services.s3.S3Client; + import software.amazon.awssdk.services.s3.model.ObjectCannedACL; + import software.amazon.awssdk.services.s3.model.PutObjectRequest; + import software.amazon.awssdk.services.s3.model.S3Exception; -@Component -@EnableAsync -@Slf4j -public class FileUploadService { + @Component + @Slf4j + @RequiredArgsConstructor + public class FileUploadService { - private final AmazonS3Client amazonS3; + private final S3Client s3Client; - public FileUploadService(AmazonS3Client amazonS3) { - this.amazonS3 = amazonS3; - } + @Async + public void uploadFile(String bucket, String fileName, MultipartFile multipartFile) { + try { + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucket) + .key(fileName) + .contentType(multipartFile.getContentType()) + .contentLength(multipartFile.getSize()) + .build(); - @Async - public void uploadFile(String bucket, String fileName, MultipartFile multipartFile) { - // 메타데이터 생성 - String contentType = multipartFile.getContentType(); - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentType(contentType); - metadata.setContentLength(multipartFile.getSize()); + s3Client.putObject(putObjectRequest, + RequestBody.fromInputStream(multipartFile.getInputStream(), multipartFile.getSize())); - try { - amazonS3.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), metadata) - .withCannedAcl(CannedAccessControlList.PublicRead)); - log.info("이미지 업로드 정상적 완료 thread: {}", Thread.currentThread().getName()); - } catch (AmazonServiceException e) { - log.error("이미지 업로드 중 s3 서비스 예외 발생 : {}", e.getMessage()); - throw new CustomException(S3_SERVICE_EXCEPTION); - } catch (SdkClientException | IOException e) { - log.error("이미지 업로드 중 s3 클라이언트 예외 발생 : {}", e.getMessage()); - throw new CustomException(S3_CLIENT_EXCEPTION); + log.info("파일 업로드 정상 완료 thread: {}", Thread.currentThread().getName()); + } catch (S3Exception e) { + log.error("S3 서비스 예외 발생 : {}", e.awsErrorDetails().errorMessage()); + throw new CustomException(S3_SERVICE_EXCEPTION); + } catch (SdkException | IOException e) { + log.error("S3 클라이언트 또는 IO 예외 발생 : {}", e.getMessage()); + throw new CustomException(S3_CLIENT_EXCEPTION); + } } } -} diff --git a/src/main/java/com/example/solidconnection/s3/service/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java index 4c4110693..c8c0a90bc 100644 --- a/src/main/java/com/example/solidconnection/s3/service/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -7,10 +7,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.SdkClientException; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -23,21 +19,22 @@ import java.util.Objects; import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; @Service @RequiredArgsConstructor public class S3Service { - private static final Logger log = LoggerFactory.getLogger(S3Service.class); private static final long MAX_FILE_SIZE_MB = 1024 * 1024 * 5; - private final AmazonS3Client amazonS3; + private final S3Client s3Client; private final SiteUserRepository siteUserRepository; private final FileUploadService fileUploadService; private final ThreadPoolTaskExecutor asyncExecutor; @@ -56,22 +53,23 @@ public class S3Service { * - 5mb 미만의 파일은 바로 업로드한다. * */ public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, ImgType imageFile) { - // 파일 검증 validateImgFile(multipartFile); - // 파일 이름 생성 UUID randomUUID = UUID.randomUUID(); - String fileName = imageFile.getType() + "/" + randomUUID; - // 파일업로드 비동기로 진행 - if (multipartFile.getSize() >= MAX_FILE_SIZE_MB) { - asyncExecutor.submit(() -> { - fileUploadService.uploadFile(bucket, "origin/" + fileName, multipartFile); - }); - } else { - asyncExecutor.submit(() -> { - fileUploadService.uploadFile(bucket, fileName, multipartFile); - }); - } - return new UploadedFileUrlResponse(fileName); + String extension = getFileExtension(Objects.requireNonNull(multipartFile.getOriginalFilename())); + String baseFileName = randomUUID + "." + extension; + String fileName = imageFile.getType() + "/" + baseFileName; + final boolean isLargeFile = multipartFile.getSize() >= MAX_FILE_SIZE_MB && imageFile != ImgType.CHAT; + + final String uploadPath = isLargeFile ? "original/" + fileName : fileName; + final String returnPath = isLargeFile + ? "resize/" + fileName.substring(0, fileName.lastIndexOf('.')) + ".webp" + : fileName; + + asyncExecutor.submit(() -> { + fileUploadService.uploadFile(bucket, uploadPath, multipartFile); + }); + + return new UploadedFileUrlResponse(returnPath); } public List uploadFiles(List multipartFile, ImgType imageFile) { @@ -125,12 +123,14 @@ public void deletePostImage(String url) { private void deleteFile(String fileName) { try { - amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName)); - } catch (AmazonServiceException e) { - log.error("파일 삭제 중 s3 서비스 예외 발생 : {}", e.getMessage()); + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(bucket) + .key(fileName) + .build(); + s3Client.deleteObject(deleteObjectRequest); + } catch (S3Exception e) { throw new CustomException(S3_SERVICE_EXCEPTION); - } catch (SdkClientException e) { - log.error("파일 삭제 중 s3 클라이언트 예외 발생 : {}", e.getMessage()); + } catch (SdkException e) { throw new CustomException(S3_CLIENT_EXCEPTION); } } From 6210cac2aa935fdfbbfc6ba91b8db883aac831ac Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 04:24:45 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EC=99=B8=EC=9D=98=20=ED=8C=8C=EC=9D=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=B4=20ImgType=20=EC=9D=98?= =?UTF-8?q?=EB=AF=B8=20=EB=AA=85=ED=99=95=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20ImgType=EC=97=90=EC=84=9C=20UploadType?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20-=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=EB=90=98=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/service/ChatService.java | 23 +++++++++++-------- .../post/service/PostCommandService.java | 4 ++-- .../service/MentorApplicationService.java | 4 ++-- .../news/service/NewsCommandService.java | 6 ++--- .../s3/controller/S3Controller.java | 16 ++++++------- .../solidconnection/s3/service/S3Service.java | 12 +++++----- .../score/service/ScoreService.java | 6 ++--- .../siteuser/service/MyPageService.java | 5 ++-- .../post/service/PostCommandServiceTest.java | 6 ++--- .../service/MentorApplicationServiceTest.java | 12 +++++----- .../news/service/NewsCommandServiceTest.java | 14 +++++------ .../score/service/ScoreServiceTest.java | 6 ++--- .../siteuser/service/MyPageServiceTest.java | 10 ++++---- 13 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index 57f8cad65..bdb2d21b7 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -27,6 +27,7 @@ import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -39,6 +40,7 @@ import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; @Service public class ChatService { @@ -240,16 +242,19 @@ public void sendChatImage(ChatImageSendRequest chatImageSendRequest, long siteUs ChatRoom chatRoom = chatRoomRepository.findById(roomId) .orElseThrow(() -> new CustomException(INVALID_CHAT_ROOM_STATE)); - ChatMessage chatMessage = new ChatMessage( - "", - senderId, - chatRoom - ); + ChatMessage chatMessage = new ChatMessage("", senderId, chatRoom); + + // 이미지 판별을 위한 확장자 리스트 + List imageExtensions = Arrays.asList("jpg", "jpeg", "png", "webp"); for (String imageUrl : chatImageSendRequest.imageUrls()) { - String thumbnailUrl = generateThumbnailUrl(imageUrl); + String extension = StringUtils.getFilenameExtension(imageUrl); - ChatAttachment attachment = new ChatAttachment(true, imageUrl, thumbnailUrl, null); + boolean isImage = extension != null && imageExtensions.contains(extension.toLowerCase()); + + String thumbnailUrl = isImage ? generateThumbnailUrl(imageUrl) : null; + + ChatAttachment attachment = new ChatAttachment(isImage, imageUrl, thumbnailUrl, null); chatMessage.addAttachment(attachment); } @@ -268,11 +273,9 @@ private String generateThumbnailUrl(String originalUrl) { String thumbnailFileName = nameWithoutExt + "_thumb" + extension; - String thumbnailUrl = originalUrl.replace("chat/images/", "chat/thumbnails/") + return originalUrl.replace("chat/files/", "chat/thumbnails/") .replace(fileName, thumbnailFileName); - return thumbnailUrl; - } catch (Exception e) { return originalUrl; } diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java index 4b3b8d15a..8b85aa651 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java @@ -18,7 +18,7 @@ import com.example.solidconnection.community.post.dto.PostUpdateRequest; import com.example.solidconnection.community.post.dto.PostUpdateResponse; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -88,7 +88,7 @@ private void savePostImages(List imageFile, Post post) { if (imageFile.isEmpty()) { return; } - List uploadedFileUrlResponseList = s3Service.uploadFiles(imageFile, ImgType.COMMUNITY); + List uploadedFileUrlResponseList = s3Service.uploadFiles(imageFile, UploadType.COMMUNITY); for (UploadedFileUrlResponse uploadedFileUrlResponse : uploadedFileUrlResponseList) { PostImage postImage = new PostImage(uploadedFileUrlResponse.fileUrl()); postImage.setPost(post); diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java index e4e187808..6bd8e564f 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.mentor.domain.MentorApplicationStatus; import com.example.solidconnection.mentor.dto.MentorApplicationRequest; import com.example.solidconnection.mentor.repository.MentorApplicationRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -45,7 +45,7 @@ public void submitMentorApplication( .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Term term = termRepository.findByName(mentorApplicationRequest.term()) .orElseThrow(() -> new CustomException(TERM_NOT_FOUND)); - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.MENTOR_PROOF); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, UploadType.MENTOR_PROOF); MentorApplication mentorApplication = new MentorApplication( siteUser.getId(), mentorApplicationRequest.country(), diff --git a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java index ca1b262fe..8fe390aaf 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java @@ -11,7 +11,7 @@ import com.example.solidconnection.news.dto.NewsCreateRequest; import com.example.solidconnection.news.dto.NewsUpdateRequest; import com.example.solidconnection.news.repository.NewsRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.Role; @@ -41,7 +41,7 @@ public NewsCommandResponse createNews(long siteUserId, NewsCreateRequest newsCre private String getImageUrl(MultipartFile imageFile) { if (imageFile != null && !imageFile.isEmpty()) { - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, UploadType.NEWS); return uploadedFile.fileUrl(); } return newsProperties.defaultThumbnailUrl(); @@ -73,7 +73,7 @@ private void updateThumbnail(News news, MultipartFile imageFile, Boolean resetTo deleteCustomImage(news.getThumbnailUrl()); news.updateThumbnailUrl(newsProperties.defaultThumbnailUrl()); } else if (imageFile != null && !imageFile.isEmpty()) { - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, UploadType.NEWS); deleteCustomImage(news.getThumbnailUrl()); news.updateThumbnailUrl(uploadedFile.fileUrl()); } diff --git a/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java index 98b0574f9..e66d36676 100644 --- a/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java +++ b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java @@ -1,7 +1,7 @@ package com.example.solidconnection.s3.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.dto.urlPrefixResponse; import com.example.solidconnection.s3.service.S3Service; @@ -39,7 +39,7 @@ public class S3Controller { public ResponseEntity uploadPreProfileImage( @RequestParam("file") MultipartFile imageFile ) { - UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, ImgType.PROFILE); + UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, UploadType.PROFILE); return ResponseEntity.ok(profileImageUrl); } @@ -48,7 +48,7 @@ public ResponseEntity uploadPostProfileImage( @AuthorizedUser long siteUserId, @RequestParam("file") MultipartFile imageFile ) { - UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, ImgType.PROFILE); + UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, UploadType.PROFILE); s3Service.deleteExProfile(siteUserId); return ResponseEntity.ok(profileImageUrl); } @@ -57,7 +57,7 @@ public ResponseEntity uploadPostProfileImage( public ResponseEntity uploadGpaImage( @RequestParam("file") MultipartFile imageFile ) { - UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, ImgType.GPA); + UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, UploadType.GPA); return ResponseEntity.ok(profileImageUrl); } @@ -65,15 +65,15 @@ public ResponseEntity uploadGpaImage( public ResponseEntity uploadLanguageImage( @RequestParam("file") MultipartFile imageFile ) { - UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, ImgType.LANGUAGE_TEST); + UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, UploadType.LANGUAGE_TEST); return ResponseEntity.ok(profileImageUrl); } @PostMapping("/chat") - public ResponseEntity> uploadChatImage( - @RequestParam("files") List imageFiles + public ResponseEntity> uploadChatFile( + @RequestParam("files") List files ) { - List chatImageUrls = s3Service.uploadFiles(imageFiles, ImgType.CHAT); + List chatImageUrls = s3Service.uploadFiles(files, UploadType.CHAT); return ResponseEntity.ok(chatImageUrls); } diff --git a/src/main/java/com/example/solidconnection/s3/service/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java index c8c0a90bc..03f064d75 100644 --- a/src/main/java/com/example/solidconnection/s3/service/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -8,7 +8,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -52,13 +52,13 @@ public class S3Service { * - 5mb 이상의 파일은 /origin/ 경로로 업로드하여 lambda 함수로 리사이징 진행한다. * - 5mb 미만의 파일은 바로 업로드한다. * */ - public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, ImgType imageFile) { + public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, UploadType uploadType) { validateImgFile(multipartFile); UUID randomUUID = UUID.randomUUID(); String extension = getFileExtension(Objects.requireNonNull(multipartFile.getOriginalFilename())); String baseFileName = randomUUID + "." + extension; - String fileName = imageFile.getType() + "/" + baseFileName; - final boolean isLargeFile = multipartFile.getSize() >= MAX_FILE_SIZE_MB && imageFile != ImgType.CHAT; + String fileName = uploadType.getType() + "/" + baseFileName; + final boolean isLargeFile = multipartFile.getSize() >= MAX_FILE_SIZE_MB && uploadType != UploadType.CHAT; final String uploadPath = isLargeFile ? "original/" + fileName : fileName; final String returnPath = isLargeFile @@ -72,11 +72,11 @@ public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, ImgType i return new UploadedFileUrlResponse(returnPath); } - public List uploadFiles(List multipartFile, ImgType imageFile) { + public List uploadFiles(List multipartFile, UploadType uploadType) { List uploadedFileUrlResponseList = new ArrayList<>(); for (MultipartFile file : multipartFile) { - UploadedFileUrlResponse uploadedFileUrlResponse = uploadFile(file, imageFile); + UploadedFileUrlResponse uploadedFileUrlResponse = uploadFile(file, uploadType); uploadedFileUrlResponseList.add(uploadedFileUrlResponse); } return uploadedFileUrlResponseList; diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index f16951d49..09823bbfb 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.score.domain.GpaScore; @@ -40,7 +40,7 @@ public class ScoreService { public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, MultipartFile file) { SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.GPA); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, UploadType.GPA); Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl()); GpaScore newGpaScore = new GpaScore(gpa, siteUser); GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); @@ -51,7 +51,7 @@ public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, Mul public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest languageTestScoreRequest, MultipartFile file) { SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.LANGUAGE_TEST); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, UploadType.LANGUAGE_TEST); LanguageTest languageTest = new LanguageTest(languageTestScoreRequest.languageTestType(), languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser); diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 6e8b88b66..94b524287 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -14,7 +14,7 @@ import com.example.solidconnection.location.country.repository.CountryRepository; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.repository.MentorRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.AuthType; @@ -90,9 +90,8 @@ public void updateMyPageInfo(long siteUserId, MultipartFile imageFile, String ni user.setNickname(nickname); user.setNicknameModifiedAt(LocalDateTime.now()); } - if (imageFile != null && !imageFile.isEmpty()) { - UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.PROFILE); + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, UploadType.PROFILE); if (!isDefaultProfileImage(user.getProfileImageUrl())) { s3Service.deleteExProfile(user.getId()); } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 36211c341..bd2b7bfd0 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -25,7 +25,7 @@ import com.example.solidconnection.community.post.fixture.PostFixture; import com.example.solidconnection.community.post.fixture.PostImageFixture; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -109,7 +109,7 @@ class 게시글_생성_테스트 { PostCreateRequest request = createPostCreateRequest(PostCategory.자유.name()); List imageFiles = List.of(createImageFile()); String expectedImageUrl = "test-image-url"; - given(s3Service.uploadFiles(any(), eq(ImgType.COMMUNITY))) + given(s3Service.uploadFiles(any(), eq(UploadType.COMMUNITY))) .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); // when @@ -179,7 +179,7 @@ class 게시글_수정_테스트 { PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(createImageFile()); - given(s3Service.uploadFiles(any(), eq(ImgType.COMMUNITY))) + given(s3Service.uploadFiles(any(), eq(UploadType.COMMUNITY))) .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); // when diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorApplicationServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorApplicationServiceTest.java index daa429fc3..aa22ca191 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorApplicationServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorApplicationServiceTest.java @@ -13,7 +13,7 @@ import com.example.solidconnection.mentor.dto.MentorApplicationRequest; import com.example.solidconnection.mentor.fixture.MentorApplicationFixture; import com.example.solidconnection.mentor.repository.MentorApplicationRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.ExchangeStatus; @@ -69,7 +69,7 @@ void setUp() { MentorApplicationRequest request = createMentorApplicationRequest(universitySelectType, universityId); MockMultipartFile file = createMentorProofFile(); String fileUrl = "/mentor-proof.pdf"; - given(s3Service.uploadFile(file, ImgType.MENTOR_PROOF)) + given(s3Service.uploadFile(file, UploadType.MENTOR_PROOF)) .willReturn(new UploadedFileUrlResponse(fileUrl)); // when @@ -87,7 +87,7 @@ void setUp() { MentorApplicationRequest request = createMentorApplicationRequest(universitySelectType, universityId); MockMultipartFile file = createMentorProofFile(); String fileUrl = "/mentor-proof.pdf"; - given(s3Service.uploadFile(file, ImgType.MENTOR_PROOF)) + given(s3Service.uploadFile(file, UploadType.MENTOR_PROOF)) .willReturn(new UploadedFileUrlResponse(fileUrl)); // when @@ -105,7 +105,7 @@ void setUp() { MentorApplicationRequest request = createMentorApplicationRequest(universitySelectType, universityId); MockMultipartFile file = createMentorProofFile(); String fileUrl = "/mentor-proof.pdf"; - given(s3Service.uploadFile(file, ImgType.MENTOR_PROOF)) + given(s3Service.uploadFile(file, UploadType.MENTOR_PROOF)) .willReturn(new UploadedFileUrlResponse(fileUrl)); // when & then @@ -122,7 +122,7 @@ void setUp() { MentorApplicationRequest request = createMentorApplicationRequest(universitySelectType, universityId); MockMultipartFile file = createMentorProofFile(); String fileUrl = "/mentor-proof.pdf"; - given(s3Service.uploadFile(file, ImgType.MENTOR_PROOF)) + given(s3Service.uploadFile(file, UploadType.MENTOR_PROOF)) .willReturn(new UploadedFileUrlResponse(fileUrl)); // when & then @@ -173,7 +173,7 @@ void setUp() { MentorApplicationRequest request = createMentorApplicationRequest(universitySelectType, universityId); MockMultipartFile file = createMentorProofFile(); String fileUrl = "/mentor-proof.pdf"; - given(s3Service.uploadFile(file, ImgType.MENTOR_PROOF)) + given(s3Service.uploadFile(file, UploadType.MENTOR_PROOF)) .willReturn(new UploadedFileUrlResponse(fileUrl)); // when diff --git a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java index f82a3bd84..91658334f 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java @@ -18,7 +18,7 @@ import com.example.solidconnection.news.dto.NewsUpdateRequest; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.news.repository.NewsRepository; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -71,7 +71,7 @@ class 소식지_생성_테스트 { NewsCreateRequest request = createNewsCreateRequest(); MultipartFile imageFile = createImageFile(); String expectedImageUrl = "news/5a02ba2f-38f5-4ae9-9a24-53d624a18233"; - given(s3Service.uploadFile(any(), eq(ImgType.NEWS))) + given(s3Service.uploadFile(any(), eq(UploadType.NEWS))) .willReturn(new UploadedFileUrlResponse(expectedImageUrl)); // when @@ -110,7 +110,7 @@ void setUp() { String expectedUrl = "https://youtu.be/test-edit"; MultipartFile expectedFile = createImageFile(); String expectedNewImageUrl = "news/5a02ba2f-38f5-4ae9-9a24-53d624a18233-edit"; - given(s3Service.uploadFile(any(), eq(ImgType.NEWS))) + given(s3Service.uploadFile(any(), eq(UploadType.NEWS))) .willReturn(new UploadedFileUrlResponse(expectedNewImageUrl)); NewsUpdateRequest request = createNewsUpdateRequest( expectedTitle, @@ -185,7 +185,7 @@ void setUp() { assertAll( () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newsProperties.defaultThumbnailUrl()), () -> then(s3Service).should().deletePostImage(CUSTOM_IMAGE_URL), - () -> then(s3Service).should(never()).uploadFile(null, ImgType.NEWS) + () -> then(s3Service).should(never()).uploadFile(null, UploadType.NEWS) ); } @@ -194,7 +194,7 @@ void setUp() { // given MultipartFile newImageFile = createImageFile(); String newImageUrl = "news/new-image-url"; - given(s3Service.uploadFile(newImageFile, ImgType.NEWS)) + given(s3Service.uploadFile(newImageFile, UploadType.NEWS)) .willReturn(new UploadedFileUrlResponse(newImageUrl)); NewsUpdateRequest request = createNewsUpdateRequest( null, @@ -248,7 +248,7 @@ void setUp() { assertAll( () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newsProperties.defaultThumbnailUrl()), () -> then(s3Service).should(never()).deletePostImage(newsProperties.defaultThumbnailUrl()), - () -> then(s3Service).should(never()).uploadFile(null, ImgType.NEWS) + () -> then(s3Service).should(never()).uploadFile(null, UploadType.NEWS) ); } @@ -257,7 +257,7 @@ void setUp() { // given MultipartFile newImageFile = createImageFile(); String newImageUrl = "news/new-image-url"; - given(s3Service.uploadFile(newImageFile, ImgType.NEWS)) + given(s3Service.uploadFile(newImageFile, UploadType.NEWS)) .willReturn(new UploadedFileUrlResponse(newImageUrl)); NewsUpdateRequest request = createNewsUpdateRequest(null, null, null, null); diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index 8760a645b..a6acc6617 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -4,7 +4,7 @@ import static org.mockito.BDDMockito.given; import com.example.solidconnection.common.VerifyStatus; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.score.domain.GpaScore; @@ -115,7 +115,7 @@ void setUp() { GpaScoreRequest request = createGpaScoreRequest(); MockMultipartFile file = createFile(); String fileUrl = "/gpa-report.pdf"; - given(s3Service.uploadFile(file, ImgType.GPA)).willReturn(new UploadedFileUrlResponse(fileUrl)); + given(s3Service.uploadFile(file, UploadType.GPA)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when long scoreId = scoreService.submitGpaScore(user.getId(), request, file); @@ -131,7 +131,7 @@ void setUp() { LanguageTestScoreRequest request = createLanguageTestScoreRequest(); MockMultipartFile file = createFile(); String fileUrl = "/gpa-report.pdf"; - given(s3Service.uploadFile(file, ImgType.LANGUAGE_TEST)).willReturn(new UploadedFileUrlResponse(fileUrl)); + given(s3Service.uploadFile(file, UploadType.LANGUAGE_TEST)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when long scoreId = scoreService.submitLanguageTestScore(user.getId(), request, file); diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 3a82681f3..52a530d44 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -25,7 +25,7 @@ import com.example.solidconnection.location.region.fixture.RegionFixture; import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.mentor.fixture.MentorFixture; -import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.AuthType; @@ -209,7 +209,7 @@ class 프로필_이미지_수정_테스트 { // given String expectedUrl = "newProfileImageUrl"; MockMultipartFile imageFile = createValidImageFile(); - given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) + given(s3Service.uploadFile(any(), eq(UploadType.PROFILE))) .willReturn(new UploadedFileUrlResponse(expectedUrl)); // when @@ -224,7 +224,7 @@ class 프로필_이미지_수정_테스트 { void 프로필을_처음_수정하는_것이면_이전_이미지를_삭제하지_않는다() { // given MockMultipartFile imageFile = createValidImageFile(); - given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) + given(s3Service.uploadFile(any(), eq(UploadType.PROFILE))) .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when @@ -239,7 +239,7 @@ class 프로필_이미지_수정_테스트 { // given SiteUser 커스텀_프로필_사용자 = createSiteUserWithCustomProfile(); MockMultipartFile imageFile = createValidImageFile(); - given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) + given(s3Service.uploadFile(any(), eq(UploadType.PROFILE))) .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when @@ -255,7 +255,7 @@ class 닉네임_수정_테스트 { @BeforeEach void setUp() { - given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) + given(s3Service.uploadFile(any(), eq(UploadType.PROFILE))) .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); } From 4a020d6fc5af1d1e9cedf7770c24fc9ceb913f40 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 04:25:01 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20s3=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../s3/service/S3ServiceTest.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java diff --git a/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java b/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java new file mode 100644 index 000000000..1fae5d152 --- /dev/null +++ b/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java @@ -0,0 +1,123 @@ +package com.example.solidconnection.s3.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.s3.domain.UploadType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import software.amazon.awssdk.services.s3.S3Client; + +@DisplayName("S3 서비스 테스트") +@TestContainerSpringBootTest +public class S3ServiceTest { + + @InjectMocks + private S3Service s3Service; + + @Mock + private S3Client s3Client; + + @Mock + private FileUploadService fileUploadService; + + @Mock + private ThreadPoolTaskExecutor asyncExecutor; + + private static final long MAX_FILE_SIZE_MB = 1024 * 1024 * 5; + + private MockMultipartFile createMockFile(String originalName, long size) { + return new MockMultipartFile("file", originalName, "image/jpeg", new byte[(int) size]); + } + + @Nested + class 파일_업로드_경로_및_리사이징_로직 { + + @Test + void O5MB_미만의_이미지는_원본_확장자를_유지하며_업로드된다() { + // given + MockMultipartFile file = createMockFile("test.png", MAX_FILE_SIZE_MB - 100); + + // when + UploadedFileUrlResponse response = s3Service.uploadFile(file, UploadType.PROFILE); + + // then + assertAll( + () -> assertThat(response.fileUrl()).startsWith("profile/"), + () -> assertThat(response.fileUrl()).endsWith(".png"), + () -> assertThat(response.fileUrl()).doesNotContain("original/", "resize/") + ); + } + + @Test + void O5MB_이상의_이미지는_original_경로로_업로드되고_resize_webp_경로를_반환한다() { + // given + MockMultipartFile file = createMockFile("test.jpg", MAX_FILE_SIZE_MB + 100); + + // when + UploadedFileUrlResponse response = s3Service.uploadFile(file, UploadType.PROFILE); + + // then + assertAll( + () -> assertThat(response.fileUrl()).startsWith("resize/profile/"), + () -> assertThat(response.fileUrl()).endsWith(".webp") + ); + } + + @Test + void 채팅_파일은_5MB가_넘어도_리사이징_경로를_적용하지_않고_원본_경로를_반환한다() { + // given + MockMultipartFile file = createMockFile("chat.jpg", MAX_FILE_SIZE_MB + 100); + + // when + UploadedFileUrlResponse response = s3Service.uploadFile(file, UploadType.CHAT); + + // then + assertAll( + () -> assertThat(response.fileUrl()).startsWith("chat/files/"), + () -> assertThat(response.fileUrl()).endsWith(".jpg"), + () -> assertThat(response.fileUrl()).doesNotContain("resize/") + ); + } + } + + @Nested + class 파일_검증 { + + @Test + void 허용되지_않은_확장자의_파일은_예외를_던진다() { + // given + MockMultipartFile invalidFile = createMockFile("virus.exe", 100); + + // when & then + assertThatThrownBy(() -> s3Service.uploadFile(invalidFile, UploadType.PROFILE)) + .isInstanceOf(CustomException.class) + .hasMessageContaining("허용된 형식"); + } + + @Test + void 채팅_업로드시_이미지_외의_허용된_문서_확장자들도_성공적으로_검증을_통과한다() { + // given + MockMultipartFile pdfFile = createMockFile("test.pdf", 100); + MockMultipartFile wordFile = createMockFile("test.docx", 100); + + // when & then + assertAll( + () -> assertThatCode(() -> s3Service.uploadFile(pdfFile, UploadType.CHAT)) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> s3Service.uploadFile(wordFile, UploadType.CHAT)) + .doesNotThrowAnyException() + ); + } + } +} From e799840fd15fba6af2ed1d8c7efe8f384d698a22 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 04:35:01 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20s3=20access-key,=20secret-key=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94,=20=EB=B2=84=ED=82=B7=20=EB=AA=85?= =?UTF-8?q?=EC=B9=AD=20=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/secret b/src/main/resources/secret index 29524e2d6..1f93968a8 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 29524e2d6dad2042400de0370a11893029aacff2 +Subproject commit 1f93968a8475d4545d90e8f681b96382d25586af From 18b90399668e01fc3c88265053ddfc5579107985 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 05:08:17 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20ChatService=20Test=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=A0=90=20=EB=B0=98=EC=98=81,=20S3ServiceTest=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20images->files=EB=A1=9C=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/chat/service/ChatServiceTest.java | 4 ++-- .../example/solidconnection/s3/service/S3ServiceTest.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java index f5ec202bb..4f53fcd37 100644 --- a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java +++ b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java @@ -454,8 +454,8 @@ class 채팅_이미지를_전송한다 { private SiteUser sender; private ChatParticipant senderParticipant; private ChatRoom chatRoom; - private static final String TEST_IMAGE_URL = "https://bucket.s3.ap-northeast-2.amazonaws.com/chat/images/example.jpg"; - private static final String TEST_IMAGE_URL2 = "https://bucket.s3.ap-northeast-2.amazonaws.com/chat/images/example2.jpg"; + private static final String TEST_IMAGE_URL = "https://bucket.s3.ap-northeast-2.amazonaws.com/chat/files/example.jpg"; + private static final String TEST_IMAGE_URL2 = "https://bucket.s3.ap-northeast-2.amazonaws.com/chat/files/example2.jpg"; private static final String EXPECTED_THUMBNAIL_URL = "https://bucket.s3.ap-northeast-2.amazonaws.com/chat/thumbnails/example_thumb.jpg"; @BeforeEach diff --git a/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java b/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java index 1fae5d152..59c024952 100644 --- a/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java +++ b/src/test/java/com/example/solidconnection/s3/service/S3ServiceTest.java @@ -8,18 +8,19 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.UploadType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; -import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockMultipartFile; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import software.amazon.awssdk.services.s3.S3Client; @DisplayName("S3 서비스 테스트") -@TestContainerSpringBootTest +@ExtendWith(MockitoExtension.class) public class S3ServiceTest { @InjectMocks From a709bdfa090987e606617eea88a9667826ff1c88 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 05:13:25 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A4=91=20=EB=B9=84?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=20=EC=8B=A4=ED=96=89=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20-=20@Async=EC=97=90=20=EC=A0=84=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/s3/service/S3Service.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/example/solidconnection/s3/service/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java index 03f064d75..637821203 100644 --- a/src/main/java/com/example/solidconnection/s3/service/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -65,9 +65,7 @@ public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, UploadTyp ? "resize/" + fileName.substring(0, fileName.lastIndexOf('.')) + ".webp" : fileName; - asyncExecutor.submit(() -> { - fileUploadService.uploadFile(bucket, uploadPath, multipartFile); - }); + fileUploadService.uploadFile(bucket, uploadPath, multipartFile); return new UploadedFileUrlResponse(returnPath); } From 1bd38eeda0bffe070b81fcd983d5146c7dceef29 Mon Sep 17 00:00:00 2001 From: Yeonri Date: Thu, 15 Jan 2026 05:16:07 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20S3Service=20error=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20NPE=20=EA=B0=80=EB=8A=A5=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/s3/service/FileUploadService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java index a93d05975..568ef9a73 100644 --- a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java +++ b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java @@ -39,7 +39,10 @@ public void uploadFile(String bucket, String fileName, MultipartFile multipartFi log.info("파일 업로드 정상 완료 thread: {}", Thread.currentThread().getName()); } catch (S3Exception e) { - log.error("S3 서비스 예외 발생 : {}", e.awsErrorDetails().errorMessage()); + String errorMessage = (e.awsErrorDetails() != null) + ? e.awsErrorDetails().errorMessage() + : e.getMessage(); + log.error("S3 서비스 예외 발생 : {}", errorMessage); throw new CustomException(S3_SERVICE_EXCEPTION); } catch (SdkException | IOException e) { log.error("S3 클라이언트 또는 IO 예외 발생 : {}", e.getMessage());