diff --git a/server/src/main/java/server/haengdong/application/ImageService.java b/server/src/main/java/server/haengdong/application/ImageService.java index 6e0ffdb76..b6d64b033 100644 --- a/server/src/main/java/server/haengdong/application/ImageService.java +++ b/server/src/main/java/server/haengdong/application/ImageService.java @@ -6,12 +6,14 @@ import java.io.InputStream; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import server.haengdong.application.response.ImageNameAppResponse; import server.haengdong.exception.HaengdongErrorCode; import server.haengdong.exception.HaengdongException; import software.amazon.awssdk.services.s3.S3Client; @@ -30,18 +32,26 @@ public class ImageService { private String directoryPath; private final S3Client s3Client; + private final ExecutorService executorService; public List uploadImages(List images) { - return images.stream() - .map(this::uploadImage) + List> futures = images.stream() + .map(image -> CompletableFuture.supplyAsync(() -> uploadImage(image), executorService)) .toList(); + + CompletableFuture> result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream() + .map(this::getFuture) + .toList()); + + return result.join(); } private String uploadImage(MultipartFile image) { try (InputStream inputStream = image.getInputStream()) { return uploadImageToStorage(inputStream, image); } catch (IOException e) { - throw new HaengdongException(HaengdongErrorCode.IMAGE_UPLOAD_FAIL); + throw new HaengdongException(HaengdongErrorCode.IMAGE_UPLOAD_FAIL, e); } } @@ -61,6 +71,14 @@ private String uploadImageToStorage(InputStream inputStream, MultipartFile image return imageName; } + private String getFuture(CompletableFuture future) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new HaengdongException(HaengdongErrorCode.IMAGE_UPLOAD_FAIL, e); + } + } + public void deleteImage(String imageName) { DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() .bucket(bucketName) diff --git a/server/src/main/java/server/haengdong/config/S3Config.java b/server/src/main/java/server/haengdong/config/S3Config.java index 13aa51546..cdac7c1dc 100644 --- a/server/src/main/java/server/haengdong/config/S3Config.java +++ b/server/src/main/java/server/haengdong/config/S3Config.java @@ -1,5 +1,7 @@ package server.haengdong.config; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import software.amazon.awssdk.regions.Region; @@ -8,10 +10,17 @@ @Configuration public class S3Config { + private static final int THREAD_POOL_SIZE = 10; + @Bean public S3Client s3Client() { return S3Client.builder() .region(Region.AP_NORTHEAST_2) .build(); } + + @Bean + public ExecutorService executorService() { + return Executors.newFixedThreadPool(THREAD_POOL_SIZE); + } } diff --git a/server/src/main/java/server/haengdong/exception/HaengdongException.java b/server/src/main/java/server/haengdong/exception/HaengdongException.java index 4454fe7bf..212b5bed5 100644 --- a/server/src/main/java/server/haengdong/exception/HaengdongException.java +++ b/server/src/main/java/server/haengdong/exception/HaengdongException.java @@ -16,4 +16,9 @@ public HaengdongException(HaengdongErrorCode errorCode, Object... args) { super(String.format(errorCode.getMessage(), args)); this.errorCode = errorCode; } + + public HaengdongException(HaengdongErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.errorCode = errorCode; + } }