From d1e7c44f164004d98926f827615330d848f73a55 Mon Sep 17 00:00:00 2001 From: Raymond Date: Mon, 29 Jul 2024 13:01:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20presigned=20URL=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/external/api/ExternalApi.java | 9 ++++++++ .../controller/ExternalController.java | 8 +++++-- .../domain/external/dto/ExternalResponse.java | 22 +++++++++++++++---- .../layer/external/ncp/dto/NcpResponse.java | 19 ++++++++++++++++ .../external/ncp/service/NcpService.java | 17 +++++++++----- 5 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 layer-external/src/main/java/org/layer/external/ncp/dto/NcpResponse.java diff --git a/layer-api/src/main/java/org/layer/domain/external/api/ExternalApi.java b/layer-api/src/main/java/org/layer/domain/external/api/ExternalApi.java index 090d3688..eef7c3c6 100644 --- a/layer-api/src/main/java/org/layer/domain/external/api/ExternalApi.java +++ b/layer-api/src/main/java/org/layer/domain/external/api/ExternalApi.java @@ -16,6 +16,15 @@ public interface ExternalApi { @Operation(summary = "Presigned URL 발급받기", method = "POST", description = """ 이미지 업로드를 위한 Presigned URL 발급합니다. + + - GetPreSignedURLResponse.presignedUrl: 이미지 저장을 위한 요청 URL
+ - GetPreSignedURLResponse.imageUrl: 저장 완료된 이미지 요청 URL + + # 참고사항 + + presigned URL 요청 시 헤더에 담긴 Content-type에 따라 파일의 확장자가 정해집니다. + 사용처에 따라 정확한 Content-type으로 요청 부탁드립니다. + """ ) @ApiResponses({ diff --git a/layer-api/src/main/java/org/layer/domain/external/controller/ExternalController.java b/layer-api/src/main/java/org/layer/domain/external/controller/ExternalController.java index 9a9892ce..5d4258aa 100644 --- a/layer-api/src/main/java/org/layer/domain/external/controller/ExternalController.java +++ b/layer-api/src/main/java/org/layer/domain/external/controller/ExternalController.java @@ -1,10 +1,12 @@ package org.layer.domain.external.controller; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.layer.common.annotation.MemberId; import org.layer.domain.external.api.ExternalApi; import org.layer.domain.external.dto.ExternalRequest; import org.layer.domain.external.dto.ExternalResponse; +import org.layer.external.ncp.dto.NcpResponse; import org.layer.external.ncp.service.NcpService; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -14,6 +16,7 @@ @RestController @RequiredArgsConstructor +@Slf4j @RequestMapping("/external") public class ExternalController implements ExternalApi { @@ -23,8 +26,9 @@ public class ExternalController implements ExternalApi { @GetMapping("/image/presigned") @PreAuthorize("isAuthenticated()") public ResponseEntity getPresignedURL(@MemberId Long memberId, ExternalRequest.GetPreSignedURLRequest getPreSignedURLRequest) { - String url = ncpService.getPreSignedUrl(memberId, getPreSignedURLRequest.domain()); + NcpResponse.PresignedResult presignedResult = ncpService.getPreSignedUrl(memberId, getPreSignedURLRequest.domain()); - return ResponseEntity.ok(ExternalResponse.GetPreSignedURLResponse.toResponse(url)); + + return ResponseEntity.ok(ExternalResponse.GetPreSignedURLResponse.toResponse(presignedResult.presignedUrl(), presignedResult.imageUrl())); } } diff --git a/layer-api/src/main/java/org/layer/domain/external/dto/ExternalResponse.java b/layer-api/src/main/java/org/layer/domain/external/dto/ExternalResponse.java index 9bd87f59..1fea8192 100644 --- a/layer-api/src/main/java/org/layer/domain/external/dto/ExternalResponse.java +++ b/layer-api/src/main/java/org/layer/domain/external/dto/ExternalResponse.java @@ -14,12 +14,26 @@ public class ExternalResponse { @Builder @Schema(description = "Presigned URL 응답") public record GetPreSignedURLResponse( - @Schema(description = "생성된 Presigned URL") + @Schema(title = "생성된 Presigned URL", description = """ + 이미지 저장을 위한 요청 URL
+ *요청시 보낸 Content-type에 따라 확장자가 결정됩니다* + """) @NotNull - String url + String presignedUrl, + + @Schema(title = "업르드된 이미지 주소", description = """ + 도메인 별로 엔티티 생성 시 해당 파라미터 객체 유무 확인 로직이 있습니다. + 업로드 성공 이후 엔티티 생성 요청 부탁드립니다 + """) + String imageUrl ) { - public static GetPreSignedURLResponse toResponse(String url) { - return Optional.ofNullable(url).map(it -> GetPreSignedURLResponse.builder().url(url).build()).orElseThrow(() -> new ExternalExeption(INTERNAL_SERVER_ERROR)); + public static GetPreSignedURLResponse toResponse(String presignedUrl, String imageUrl) { + return Optional.ofNullable(presignedUrl) + .map(it -> GetPreSignedURLResponse.builder() + .presignedUrl(presignedUrl) + .imageUrl(imageUrl) + .build() + ).orElseThrow(() -> new ExternalExeption(INTERNAL_SERVER_ERROR)); } } } diff --git a/layer-external/src/main/java/org/layer/external/ncp/dto/NcpResponse.java b/layer-external/src/main/java/org/layer/external/ncp/dto/NcpResponse.java new file mode 100644 index 00000000..a92e824d --- /dev/null +++ b/layer-external/src/main/java/org/layer/external/ncp/dto/NcpResponse.java @@ -0,0 +1,19 @@ +package org.layer.external.ncp.dto; + +import lombok.Builder; + +public class NcpResponse { + + @Builder + public record PresignedResult( + String presignedUrl, + String imageUrl + ) { + + public static PresignedResult toResponse(String presignedUrl, + String imageUrl) { + return PresignedResult.builder().presignedUrl(presignedUrl).imageUrl(imageUrl).build(); + + } + } +} diff --git a/layer-external/src/main/java/org/layer/external/ncp/service/NcpService.java b/layer-external/src/main/java/org/layer/external/ncp/service/NcpService.java index b133abbd..0ed16d3b 100644 --- a/layer-external/src/main/java/org/layer/external/ncp/service/NcpService.java +++ b/layer-external/src/main/java/org/layer/external/ncp/service/NcpService.java @@ -7,6 +7,7 @@ import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.layer.external.ncp.dto.NcpResponse; import org.layer.external.ncp.enums.ImageDomain; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -24,12 +25,16 @@ public class NcpService { private final AmazonS3Client amazonS3Client; - public String getPreSignedUrl(Long memberId, ImageDomain imageDomain) { - String fileName = imageDomain + "/" + memberId.toString() + "/" + UUID.randomUUID(); + public NcpResponse.PresignedResult getPreSignedUrl(Long memberId, ImageDomain imageDomain) { + String imagePath = imageDomain + "/" + memberId.toString() + "/" + UUID.randomUUID(); + var imageUrl = amazonS3Client.getUrl(bucket, imagePath); - GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(bucket, fileName); + GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(bucket, imagePath); - return amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest).toString(); + return NcpResponse.PresignedResult.toResponse( + amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest).toString(), + imageUrl.toString() + ); } private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket, String fileName) { @@ -46,7 +51,9 @@ private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket private Date getPreSignedUrlExpiration() { Date expiration = new Date(); long expTimeMillis = expiration.getTime(); - expTimeMillis += 1000 * 60 * 2; + + // 15 초 + expTimeMillis += 1000 * 15; expiration.setTime(expTimeMillis); return expiration; }