From 7e3cbbf87bc54526672558c197682192b6f84120 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:11:20 +0900 Subject: [PATCH 01/10] =?UTF-8?q?:sparkles:=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20response=20DTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit part별 이미지 업로드시 반환될 dto를 구현 - 관련 : #404 --- .../api/image/dto/MultipartStartResponse.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java new file mode 100644 index 00000000..8fe17757 --- /dev/null +++ b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java @@ -0,0 +1,17 @@ +package com.namo.spring.application.external.api.image.dto; + +import java.net.URL; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class MultipartStartResponse { + private String uploadId; + private List presignedUrls; + private String fileName; +} From 70703b670e65799f8080b121bab4cdb0f235be51 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:19:09 +0900 Subject: [PATCH 02/10] =?UTF-8?q?:recycle:=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20response=20DTO=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core-infra 모듈로 이동 - 관련 : #404 --- .../core/infra/common/aws}/dto/MultipartStartResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image => core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws}/dto/MultipartStartResponse.java (83%) diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartStartResponse.java similarity index 83% rename from application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java rename to core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartStartResponse.java index 8fe17757..ba64be8f 100644 --- a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/dto/MultipartStartResponse.java +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartStartResponse.java @@ -1,4 +1,4 @@ -package com.namo.spring.application.external.api.image.dto; +package com.namo.spring.core.infra.common.aws.dto; import java.net.URL; import java.util.List; From eb39b7b68acfa9b633e09e6de842e3620a38d00f Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:19:59 +0900 Subject: [PATCH 03/10] =?UTF-8?q?:sparkles:=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20URL=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getPreSignedUrls() 를 통해 분할 이미지 업로드 URL 발급 - 관련 : #404 --- .../core/infra/common/aws/s3/S3Uploader.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java index 3469ddd0..7f1e9b27 100644 --- a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java @@ -2,7 +2,9 @@ import java.io.InputStream; import java.net.URL; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.UUID; import org.springframework.stereotype.Component; @@ -13,8 +15,11 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; +import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; +import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; +import com.namo.spring.core.infra.common.aws.dto.MultipartStartResponse; import com.namo.spring.core.infra.common.constant.FilePath; import com.namo.spring.core.infra.config.AwsS3Config; @@ -46,6 +51,40 @@ public void delete(String key) { amazonS3Client.deleteObject(deleteObjectRequest); } + public MultipartStartResponse getPreSignedUrls(String prefix, String fileName, int partCount) { + String uniqueFileName = createPath(prefix, fileName); + + // 1. Multipart 업로드 초기화 + InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest( + awsS3Config.getBucketName(), + uniqueFileName + ) + .withCannedACL(CannedAccessControlList.PublicRead); + + InitiateMultipartUploadResult initResult = amazonS3Client.initiateMultipartUpload(initRequest); + String uploadId = initResult.getUploadId(); + + // 2. 각 파트별 presigned URL 생성 + List presignedUrls = new ArrayList<>(); + for (int i = 1; i <= partCount; i++) { + GeneratePresignedUrlRequest presignedUrlRequest = new GeneratePresignedUrlRequest( + awsS3Config.getBucketName(), + uniqueFileName + ) + .withMethod(HttpMethod.PUT) + .withExpiration(getPreSignedUrlExpiration()); + + // Request Parameters에 partNumber와 uploadId 추가 + presignedUrlRequest.addRequestParameter("partNumber", String.valueOf(i)); + presignedUrlRequest.addRequestParameter("uploadId", uploadId); + + URL url = amazonS3Client.generatePresignedUrl(presignedUrlRequest); + presignedUrls.add(url); + } + + return new MultipartStartResponse(uploadId, presignedUrls, uniqueFileName); + } + /** * presigned url 발급 * From 92fe142d84605147dd72409f1a2919479f1ad374 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:32:23 +0900 Subject: [PATCH 04/10] =?UTF-8?q?:sparkles:=20Refactor:=20=EB=8B=A4?= =?UTF-8?q?=EC=A4=91=20=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능별 분리 - 관련 : #404 --- .../core/infra/common/aws/s3/S3Uploader.java | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java index 7f1e9b27..df1d75a6 100644 --- a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java @@ -6,6 +6,8 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.springframework.stereotype.Component; @@ -33,6 +35,7 @@ public class S3Uploader { private final AmazonS3Client amazonS3Client; private final AwsS3Config awsS3Config; + // v1 public void uploadFile(InputStream inputStream, ObjectMetadata objectMeTadata, String fileName) { amazonS3Client.putObject( new PutObjectRequest(awsS3Config.getBucketName(), fileName, inputStream, objectMeTadata) @@ -51,38 +54,53 @@ public void delete(String key) { amazonS3Client.deleteObject(deleteObjectRequest); } + /** + * Part 업로드를 위한 PresignedURLs 발급 + * @param prefix + * @param fileName + * @param partCount + * @return + */ public MultipartStartResponse getPreSignedUrls(String prefix, String fileName, int partCount) { String uniqueFileName = createPath(prefix, fileName); + String uploadId = initiateMultipartUpload(uniqueFileName); + List presignedUrls = generatePartPresignedUrls(uniqueFileName, uploadId, partCount); + return new MultipartStartResponse(uploadId, presignedUrls, uniqueFileName); + } - // 1. Multipart 업로드 초기화 - InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest( - awsS3Config.getBucketName(), - uniqueFileName - ) + /** + * S3에 Multipart Upload를 초기화하고 uploadId를 반환 + */ + private String initiateMultipartUpload(String fileName) { + InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(awsS3Config.getBucketName(), fileName) .withCannedACL(CannedAccessControlList.PublicRead); - InitiateMultipartUploadResult initResult = amazonS3Client.initiateMultipartUpload(initRequest); - String uploadId = initResult.getUploadId(); - - // 2. 각 파트별 presigned URL 생성 - List presignedUrls = new ArrayList<>(); - for (int i = 1; i <= partCount; i++) { - GeneratePresignedUrlRequest presignedUrlRequest = new GeneratePresignedUrlRequest( - awsS3Config.getBucketName(), - uniqueFileName - ) - .withMethod(HttpMethod.PUT) - .withExpiration(getPreSignedUrlExpiration()); - - // Request Parameters에 partNumber와 uploadId 추가 - presignedUrlRequest.addRequestParameter("partNumber", String.valueOf(i)); - presignedUrlRequest.addRequestParameter("uploadId", uploadId); - - URL url = amazonS3Client.generatePresignedUrl(presignedUrlRequest); - presignedUrls.add(url); - } - return new MultipartStartResponse(uploadId, presignedUrls, uniqueFileName); + return initResult.getUploadId(); + } + + /** + * 각 파트별 업로드를 위한 presigned URL 목록 생성 + */ + private List generatePartPresignedUrls(String fileName, String uploadId, int partCount) { + return IntStream.rangeClosed(1, partCount) + .mapToObj(partNumber -> generatePresignedUrl(fileName, uploadId, partNumber)) + .collect(Collectors.toList()); + } + + /** + * 단일 파트에 대한 presigned URL 생성 + */ + private URL generatePresignedUrl(String fileName, String uploadId, int partNumber) { + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest( + awsS3Config.getBucketName(), fileName) + .withMethod(HttpMethod.PUT) + .withExpiration(getPreSignedUrlExpiration()); + + request.addRequestParameter("partNumber", String.valueOf(partNumber)); + request.addRequestParameter("uploadId", uploadId); + + return amazonS3Client.generatePresignedUrl(request); } /** From ae253bec0e690a4ea8f46e7773e46f9aaaf34c3b Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:33:00 +0900 Subject: [PATCH 05/10] =?UTF-8?q?:sparkles:=20Feat:=20=EB=8B=A4=EC=A4=91?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API 구현 완료 - 관련 : #404 --- .../api/image/controller/S3Controller.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java index 49e80ab1..36e4e895 100644 --- a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java +++ b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java @@ -2,13 +2,18 @@ import static com.namo.spring.core.common.code.status.ErrorStatus.*; +import java.net.URL; +import java.util.List; + import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.namo.spring.application.external.global.annotation.swagger.ApiErrorCodes; import com.namo.spring.core.common.response.ResponseDto; +import com.namo.spring.core.infra.common.aws.dto.MultipartStartResponse; import com.namo.spring.core.infra.common.aws.s3.S3Uploader; import io.swagger.v3.oas.annotations.Operation; @@ -39,4 +44,15 @@ public ResponseDto generatePresignedUrl( String preSignedUrl = s3Service.getPreSignedUrl(prefix, fileName); return ResponseDto.onSuccess(preSignedUrl); } + + @PostMapping("/pre-signed/start") + public ResponseDto start( + @RequestParam String fileName, + @Parameter(description = "이미지 종류입니다 {activity: 활동 이미지, diary: 일기 이미지, cover: 커버 이미지, profile: 프로필 이미지} 입력 가능합니다.", example = "activity") + @RequestParam String prefix, + @RequestParam int partCount + ) { + return ResponseDto.onSuccess(s3Service.getPreSignedUrls(prefix, fileName, partCount)); + } + } From 5f1f5725daa59a86912fab73f41a30faf5f977f2 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:43:30 +0900 Subject: [PATCH 06/10] =?UTF-8?q?:sparkles:=20Feat:=20=EB=8B=A4=EC=A4=91?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20Request=20DTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Etag를 포함하여 요청하는 dto - 관련 : #404 --- .../common/aws/dto/MultipartCompleteRequest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartCompleteRequest.java diff --git a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartCompleteRequest.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartCompleteRequest.java new file mode 100644 index 00000000..eca1daee --- /dev/null +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/dto/MultipartCompleteRequest.java @@ -0,0 +1,16 @@ +package com.namo.spring.core.infra.common.aws.dto; + +import java.util.List; + +import com.amazonaws.services.s3.model.PartETag; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MultipartCompleteRequest { + private String fileName; + private String uploadId; + private List partETags; +} From 36f8fcb9454404c93034417f944354370a796e6c Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:44:13 +0900 Subject: [PATCH 07/10] =?UTF-8?q?:sparkles:=20Feat:=20=EB=8B=A4=EC=A4=91?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=9A=94=EC=B2=AD=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S3에 part 업로드 완료 처리 - 관련 : #404 --- .../core/infra/common/aws/s3/S3Uploader.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java index df1d75a6..69765bb8 100644 --- a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java @@ -2,7 +2,6 @@ import java.io.InputStream; import java.net.URL; -import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @@ -15,11 +14,13 @@ import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.Headers; import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest; import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PartETag; import com.amazonaws.services.s3.model.PutObjectRequest; import com.namo.spring.core.infra.common.aws.dto.MultipartStartResponse; import com.namo.spring.core.infra.common.constant.FilePath; @@ -172,6 +173,28 @@ private String createPath(String prefix, String fileName) { return String.format("%s/%s", filepath, fileId + fileName); } + + /** + * part 업로드 완료 처리 (합침, 태깅으로 삭제 방지) + * @param fileName + * @param uploadId + * @param partETags + * @return + */ + public String completeMultipartUpload( + String fileName, + String uploadId, + List partETags + ) { + CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest( + awsS3Config.getBucketName(), + fileName, + uploadId, + partETags + ); + + return amazonS3Client.completeMultipartUpload(completeRequest).getLocation(); + } } From 5d6d67b5ec1569f1bb30034d2d26e6b1cbabc2b8 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:44:54 +0900 Subject: [PATCH 08/10] =?UTF-8?q?:sparkles:=20Feat:=20S3=20=EB=A9=80?= =?UTF-8?q?=ED=8B=B0=ED=8C=8C=ED=8A=B8=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EC=9A=94=EC=B2=AD=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S3에 멀티파트 업로드를 완료하는 요청을 보내는 API 구현 완료 - 관련 : #404 --- .../api/image/controller/S3Controller.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java index 36e4e895..7ecb2a35 100644 --- a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java +++ b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java @@ -7,12 +7,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.namo.spring.application.external.global.annotation.swagger.ApiErrorCodes; import com.namo.spring.core.common.response.ResponseDto; +import com.namo.spring.core.infra.common.aws.dto.MultipartCompleteRequest; import com.namo.spring.core.infra.common.aws.dto.MultipartStartResponse; import com.namo.spring.core.infra.common.aws.s3.S3Uploader; @@ -38,21 +40,48 @@ public class S3Controller { INTERNET_SERVER_ERROR}) @GetMapping("/generate-presigned-url") public ResponseDto generatePresignedUrl( - @Parameter(description = "이미지 종류입니다 {activity: 활동 이미지, diary: 일기 이미지, cover: 커버 이미지, profile: 프로필 이미지} 입력 가능합니다.", example = "activity") + @Parameter(description = "업로드할 이미지의 종류를 선택해 주세요. 예: 'activity' (활동 이미지), 'diary' (일기 이미지), 'cover' (커버 이미지), 'profile' (프로필 이미지)", + example = "activity") @RequestParam String prefix, + @Parameter(description = "업로드할 파일의 이름을 입력해 주세요.", example = "example.jpg") @RequestParam String fileName) { String preSignedUrl = s3Service.getPreSignedUrl(prefix, fileName); return ResponseDto.onSuccess(preSignedUrl); } @PostMapping("/pre-signed/start") + @Operation( + summary = "S3 Presigned URLs 생성 요청 (Part 업로드용)", + description = "이 API는 클라이언트가 큰 파일을 여러 파트로 나누어 S3에 업로드할 수 있도록 각 파트에 대한 Presigned URL을 생성합니다. \n" + + "파일을 원하는 만큼의 파트로 나누기 위해 partCount에 파트 수를 입력해 주세요. 반환된 각 URL을 사용해 각 파트를 업로드한 후, " + + "업로드가 완료되면 반드시 '/pre-signed/complete' API를 호출하여 최종 업로드를 완료해야 합니다. 그렇지 않으면 S3에 임시로 저장된 파일이 일정 기간(7일) 후 삭제될 수 있습니다." + ) public ResponseDto start( + @Parameter(description = "업로드할 파일의 이름을 입력해 주세요.", example = "example.jpg") @RequestParam String fileName, - @Parameter(description = "이미지 종류입니다 {activity: 활동 이미지, diary: 일기 이미지, cover: 커버 이미지, profile: 프로필 이미지} 입력 가능합니다.", example = "activity") + @Parameter(description = "업로드할 이미지의 종류를 선택해 주세요. 예: 'activity' (활동 이미지), 'diary' (일기 이미지), 'cover' (커버 이미지), 'profile' (프로필 이미지)", + example = "activity") @RequestParam String prefix, + @Parameter(description = "파일을 몇 개의 파트로 나누어 업로드할지 입력해 주세요.", example = "3") @RequestParam int partCount ) { return ResponseDto.onSuccess(s3Service.getPreSignedUrls(prefix, fileName, partCount)); } + @PostMapping("/pre-signed/complete") + @Operation( + summary = "S3 멀티파트 업로드 완료 요청", + description = "이 API는 S3에 멀티파트 업로드를 완료하는 요청을 보냅니다. 클라이언트는 모든 파트를 Presigned URLs을 통해 업로드한 후 이 API를 호출하여 업로드를 최종 완료해야 합니다. \n" + + "업로드가 완료되지 않으면 임시 저장된 파일이 일정 기간(7일) 후 삭제될 수 있습니다." + ) + public ResponseDto complete( + @Parameter(description = "업로드 완료를 위한 요청 정보를 JSON 형식으로 전달합니다. 이 정보에는 파일 이름, 업로드 ID, 각 파트의 ETag 정보가 포함되어야 합니다.") + @RequestBody MultipartCompleteRequest request + ) { + return ResponseDto.onSuccess(s3Service.completeMultipartUpload( + request.getFileName(), + request.getUploadId(), + request.getPartETags() + )); + } } From 11bd5eac57451e558e3c0fcf198df3458c9ac491 Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:53:13 +0900 Subject: [PATCH 09/10] =?UTF-8?q?:recycle:=20Refactor:=20API=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=EC=97=90=EC=84=9C=20s3=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PartETag .Get 메서드 사용시 의존성 때문에 파람 수정 - 관련 : #404 --- .../api/image/controller/S3Controller.java | 6 +----- .../core/infra/common/aws/s3/S3Uploader.java | 16 +++++----------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java index 7ecb2a35..72eef545 100644 --- a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java +++ b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java @@ -78,10 +78,6 @@ public ResponseDto complete( @Parameter(description = "업로드 완료를 위한 요청 정보를 JSON 형식으로 전달합니다. 이 정보에는 파일 이름, 업로드 ID, 각 파트의 ETag 정보가 포함되어야 합니다.") @RequestBody MultipartCompleteRequest request ) { - return ResponseDto.onSuccess(s3Service.completeMultipartUpload( - request.getFileName(), - request.getUploadId(), - request.getPartETags() - )); + return ResponseDto.onSuccess(s3Service.completeMultipartUpload(request)); } } diff --git a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java index 69765bb8..bc647ded 100644 --- a/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java +++ b/core/core-infra/src/main/java/com/namo/spring/core/infra/common/aws/s3/S3Uploader.java @@ -22,6 +22,7 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PartETag; import com.amazonaws.services.s3.model.PutObjectRequest; +import com.namo.spring.core.infra.common.aws.dto.MultipartCompleteRequest; import com.namo.spring.core.infra.common.aws.dto.MultipartStartResponse; import com.namo.spring.core.infra.common.constant.FilePath; import com.namo.spring.core.infra.config.AwsS3Config; @@ -176,21 +177,14 @@ private String createPath(String prefix, String fileName) { /** * part 업로드 완료 처리 (합침, 태깅으로 삭제 방지) - * @param fileName - * @param uploadId - * @param partETags * @return */ - public String completeMultipartUpload( - String fileName, - String uploadId, - List partETags - ) { + public String completeMultipartUpload(MultipartCompleteRequest request) { CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest( awsS3Config.getBucketName(), - fileName, - uploadId, - partETags + request.getFileName(), + request.getUploadId(), + request.getPartETags() ); return amazonS3Client.completeMultipartUpload(completeRequest).getLocation(); From fc7bd19959d21570fc9299d021cba155041d142f Mon Sep 17 00:00:00 2001 From: hosung-222 Date: Fri, 1 Nov 2024 20:55:57 +0900 Subject: [PATCH 10/10] =?UTF-8?q?:memo:=20Docs:=20Etag=20=EC=98=88?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 바디 etag 값 스웨거 문서화 - 관련 : #404 --- .../external/api/image/controller/S3Controller.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java index 72eef545..a4cd8b4a 100644 --- a/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java +++ b/application/external-api-v2/src/main/java/com/namo/spring/application/external/api/image/controller/S3Controller.java @@ -72,7 +72,8 @@ public ResponseDto start( @Operation( summary = "S3 멀티파트 업로드 완료 요청", description = "이 API는 S3에 멀티파트 업로드를 완료하는 요청을 보냅니다. 클라이언트는 모든 파트를 Presigned URLs을 통해 업로드한 후 이 API를 호출하여 업로드를 최종 완료해야 합니다. \n" - + "업로드가 완료되지 않으면 임시 저장된 파일이 일정 기간(7일) 후 삭제될 수 있습니다." + + "업로드가 완료되지 않으면 임시 저장된 파일이 일정 기간(7일) 후 삭제될 수 있습니다. " + + "partETags example 입니다 : \"[{\\\"partNumber\\\": 1, \\\"eTag\\\": \\\"etag_value_1\\\"}, {\\\"partNumber\\\": 2, \\\"eTag\\\": \\\"etag_value_2\\\"}]\")" ) public ResponseDto complete( @Parameter(description = "업로드 완료를 위한 요청 정보를 JSON 형식으로 전달합니다. 이 정보에는 파일 이름, 업로드 ID, 각 파트의 ETag 정보가 포함되어야 합니다.")