diff --git a/build.gradle b/build.gradle index d2241b3..9bd2044 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' // S3 - implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - implementation 'com.amazonaws:aws-java-sdk-s3:1.12.683' + implementation 'io.awspring.cloud:spring-cloud-starter-aws:3.3.0' + implementation 'software.amazon.awssdk:s3:2.20.14' // Resilience4j implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.1' implementation 'io.github.resilience4j:resilience4j-bulkhead:1.7.1' diff --git a/src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java b/src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java index 8b92e5a..998c7bc 100644 --- a/src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java +++ b/src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java @@ -31,6 +31,21 @@ public ResponseEntity uploadStoreImage( @RequestParam("files") List files, @RequestParam(value = "types") List types ) { + // TODO 관련 정책 확정되면 메서드로 분리 예정 + // 파일 개수 제한 검증 + if (files.isEmpty() || files.size() > 10) { + throw new IllegalArgumentException("파일은 1개 이상 10개 이하로 업로드해 주세요."); + } + // 파일 크기 검증 + for (MultipartFile file : files) { + if (file.isEmpty()) { + throw new IllegalArgumentException("빈 파일은 업로드할 수 없습니다."); + } + if (file.getSize() > 10 * 1024 * 1024) { // 10MB 제한 + throw new IllegalArgumentException("파일 크기는 10MB를 초과할 수 없습니다."); + } + } + List response = storeImageService.saveAll(storeId, files, types); return ResponseEntity .status(HttpStatus.CREATED) diff --git a/src/main/java/com/example/gtable/storeImage/service/StoreImageService.java b/src/main/java/com/example/gtable/storeImage/service/StoreImageService.java index 92ccf1d..b5efc77 100644 --- a/src/main/java/com/example/gtable/storeImage/service/StoreImageService.java +++ b/src/main/java/com/example/gtable/storeImage/service/StoreImageService.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,15 +35,26 @@ public List saveAll(Long storeId, List Store store = storeRepository.findById(storeId) .orElseThrow(() -> new EntityNotFoundException("Store not found with id: " + storeId)); - List imageUploadResponses = new ArrayList<>(); - for (int i = 0; i < files.size(); i++) { - S3Service.S3UploadResult uploadResult; - try { - uploadResult = s3Service.upload(storeId, files.get(i)).get(); - } catch (Exception e) { - throw new RuntimeException("S3 업로드 실패", e); - } + // 모든 파일을 비동기로 업로드 + List> uploadFutures = new ArrayList<>(); + for (MultipartFile file : files) { + uploadFutures.add(s3Service.upload(storeId, file)); + } + // 모든 업로드 완료 대기 + List uploadResults; + try { + uploadResults = uploadFutures.stream() + .map(CompletableFuture::join) + .toList(); + } catch (Exception e) { + throw new RuntimeException("S3 업로드 실패", e); + } + + // DB 저장은 모든 S3 업로드 성공 후 수행 + List imageUploadResponses = new ArrayList<>(); + for (int i = 0; i < uploadResults.size(); i++) { + S3Service.S3UploadResult uploadResult = uploadResults.get(i); StoreImage storeImage = StoreImage.builder() .store(store) .imageUrl(uploadResult.url())