diff --git a/src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java b/src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java index 36e8258..8b0932d 100644 --- a/src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java +++ b/src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java @@ -52,7 +52,8 @@ public ImageFile uploadOriginal(String dir, MultipartFile file, int index) { validateExtension(file.getOriginalFilename()); String originalFilename = file.getOriginalFilename(); - String key = buildKey(dir, originalFilename, index); +// String key = buildKey(dir, originalFilename, index); + String key = buildKey(originalFilename, index); byte[] bytes = resizeIfNeededKeepFormat(file); @@ -93,6 +94,29 @@ public ImageFile uploadAsWebpWithSize( return new ImageFile(key, url); } + + // TODO: 회원에서 사용할 단 건 이미지 저장 기능 + public ImageFile uploadAsWebpWithSize( + MultipartFile file, + Integer width, + Integer height + ) { + validateImageSize(file); + validateImageContentType(file); + validateExtension(file.getOriginalFilename()); + ImageSize size = new ImageSize(width, height); + + String baseName = buildBaseName(); + String key = baseName + "_" + size.width() + "x" + size.height() + ".webp"; + + byte[] bytes = convertToWebpWithSize(file, size); + + putToS3(key, bytes, "image/webp"); + String url = awsS3Properties.getPublicEndpoint() + "/" + key; + + return new ImageFile(key, url); + } + public List uploadAsWebpWithSizes( String dir, MultipartFile file, @@ -128,6 +152,39 @@ public List uploadAsWebpWithSizes( return result; } + public List uploadAsWebpWithSizes( + MultipartFile file, + int index, + List widths, + List heights + ) { + if (widths.size() != heights.size()) { + // 해당 예외는 이미지 예외 처리로 수행하지 않는다. + throw new IllegalArgumentException("widths와 heights의 길이가 일치해야 합니다."); + } + + validateImageSize(file); + validateImageContentType(file); + validateExtension(file.getOriginalFilename()); + + String baseName = buildBaseName(index); + List result = new ArrayList<>(); + + for (int i = 0; i < widths.size(); i++) { + ImageSize size = new ImageSize(widths.get(i), heights.get(i)); + + String key = baseName + "_" + size.width() + "x" + size.height() + ".webp"; + byte[] bytes = convertToWebpWithSize(file, size); + + putToS3(key, bytes, "image/webp"); + String url = awsS3Properties.getPublicEndpoint() + "/" + key; + + result.add(new ImageFile(key, url)); + } + + return result; + } + public void delete(String key) { s3Client.deleteObject(builder -> builder .bucket(awsS3Properties.getBucket()) @@ -197,9 +254,14 @@ private String extractExtension(String originalFilename) { } private void validateDir(String dir) { - if (dir == null || dir.isBlank()) { - throw new ImageException(ImageExceptionCode.DIR_REQUIRED); - } + // TODO: 모임 경로: FE 요청으로 루트 디렉토리로 개선했습니다. +// if (dir == null || dir.isBlank()) { +// throw new ImageException(ImageExceptionCode.DIR_REQUIRED); +// } + +// if (!dir.matches("[a-zA-Z0-9_\\-/]+")) { +// throw new ImageException(ImageExceptionCode.DIR_INVALID_PATTERN); +// } if (dir.contains("..") || dir.startsWith("/")) { throw new ImageException(ImageExceptionCode.DIR_INVALID_TRAVERSAL); @@ -208,10 +270,6 @@ private void validateDir(String dir) { if (dir.endsWith("/")) { throw new ImageException(ImageExceptionCode.DIR_TRAILING_SLASH); } - - if (!dir.matches("[a-zA-Z0-9_\\-/]+")) { - throw new ImageException(ImageExceptionCode.DIR_INVALID_PATTERN); - } } private String buildKey(String dir, String originalFilename, int index) { @@ -223,6 +281,16 @@ private String buildKey(String dir, String originalFilename, int index) { return dir + "/" + timestamp + "_" + index + "_" + uuid + extension; } + private String buildKey(String originalFilename, int index) { + String extension = extractExtension(originalFilename); + String timestamp = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String uuid = UUID.randomUUID().toString(); + + return timestamp + "_" + index + "_" + uuid + extension; + } + + private String buildBaseName(int index) { String timestamp = LocalDateTime.now() .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); @@ -230,6 +298,13 @@ private String buildBaseName(int index) { return timestamp + "_" + index + "_" + uuid; } + private String buildBaseName() { + String timestamp = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String uuid = UUID.randomUUID().toString(); + return timestamp + "_" + uuid; + } + private byte[] resizeIfNeededKeepFormat(MultipartFile file) { return resizeToBox( file, diff --git a/src/main/java/team/wego/wegobackend/image/presentation/ImageController.java b/src/main/java/team/wego/wegobackend/image/presentation/ImageController.java index 9d36396..33d4d41 100644 --- a/src/main/java/team/wego/wegobackend/image/presentation/ImageController.java +++ b/src/main/java/team/wego/wegobackend/image/presentation/ImageController.java @@ -1,6 +1,5 @@ package team.wego.wegobackend.image.presentation; -import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/test/http/group/create.http b/src/test/http/group/create.http index 2a423c7..290fa65 100644 --- a/src/test/http/group/create.http +++ b/src/test/http/group/create.http @@ -140,22 +140,22 @@ POST http://localhost:8080/api/v1/groups/images/{{groupId}}/upload ?userId=1 Content-Type: multipart/form-data; boundary=boundary ---boundary +--boundary-- Content-Disposition: form-data; name="images"; filename="test-webp1.webp" Content-Type: image/webp < ../image/resources/test-webp1.webp ---boundary +--boundary-- Content-Disposition: form-data; name="images"; filename="test-webp1.webp" Content-Type: image/webp < ../image/resources/test-webp1.webp ---boundary +--boundary-- Content-Disposition: form-data; name="images"; filename="img1.png" Content-Type: image/png < ../image/resources/img1.png ---boundary +--boundary-- Content-Disposition: form-data; name="images"; filename="img2.jpg" Content-Type: image/jpeg