diff --git a/build.gradle b/build.gradle index a764012f..4f6877fc 100644 --- a/build.gradle +++ b/build.gradle @@ -108,6 +108,10 @@ dependencies { //elastic search implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' + + //webp + implementation 'com.sksamuel.scrimage:scrimage-core:4.2.0' + implementation 'com.sksamuel.scrimage:scrimage-webp:4.2.0' } test { diff --git a/src/main/java/com/konggogi/veganlife/global/AwsS3Uploader.java b/src/main/java/com/konggogi/veganlife/global/AwsS3Uploader.java index 6f7c6f9e..364b56d3 100644 --- a/src/main/java/com/konggogi/veganlife/global/AwsS3Uploader.java +++ b/src/main/java/com/konggogi/veganlife/global/AwsS3Uploader.java @@ -7,12 +7,14 @@ import com.konggogi.veganlife.global.domain.AwsS3Folders; import com.konggogi.veganlife.global.exception.ErrorCode; import com.konggogi.veganlife.global.exception.FileUploadException; +import com.sksamuel.scrimage.ImmutableImage; +import com.sksamuel.scrimage.webp.WebpWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.nio.file.Files; +import java.util.*; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -47,15 +49,19 @@ public String uploadFile(AwsS3Folders uploadFolder, MultipartFile multipartFile) return null; } String newFileName = uploadFolder.getName() + generateRandomFilename(multipartFile); + File file = convertMultipartFileToFile(multipartFile); + File webpFile = convertFileToWebp(file); ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(multipartFile.getSize()); - metadata.setContentType(multipartFile.getContentType()); + metadata.setContentLength(webpFile.length()); + metadata.setContentType("image/webp"); - try { - amazonS3.putObject(bucket, newFileName, multipartFile.getInputStream(), metadata); + try (FileInputStream fileInputStream = new FileInputStream(webpFile)) { + amazonS3.putObject(bucket, newFileName, fileInputStream, metadata); } catch (SdkClientException | IOException e) { throw new FileUploadException(ErrorCode.FILE_UPLOAD_ERROR); + } finally { + deleteTempFiles(file, webpFile); } return cloudfrontDomain + newFileName; } @@ -65,18 +71,46 @@ private String generateRandomFilename(MultipartFile multipartFile) { if (originalFileName == null) { throw new FileUploadException(ErrorCode.NULL_FILE_NAME); } - String extension = validateFileExtension(originalFileName); - return UUID.randomUUID() + "." + extension; + validateFileExtension(originalFileName); + return UUID.randomUUID() + ".webp"; } - private String validateFileExtension(String originalFilename) { + private void validateFileExtension(String originalFilename) { String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase(); - List allowedExtensions = Arrays.asList("jpg", "png", "gif", "jpeg", "webp"); + List allowedExtensions = Arrays.asList("jpg", "png", "jpeg", "webp"); if (!allowedExtensions.contains(fileExtension)) { throw new FileUploadException(ErrorCode.INVALID_EXTENSION); } - return fileExtension; + } + + private File convertMultipartFileToFile(MultipartFile multipartFile) { + File file = new File(multipartFile.getOriginalFilename()); + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(multipartFile.getBytes()); + return file; + } catch (IOException e) { + throw new FileUploadException(ErrorCode.FILE_CONVERT_ERROR); + } + } + + private File convertFileToWebp(File file) { + File outputFile = new File("resize_" + System.currentTimeMillis() + ".webp"); + try { + return ImmutableImage.loader().fromFile(file).output(WebpWriter.DEFAULT, outputFile); + } catch (IOException e) { + throw new FileUploadException(ErrorCode.FILE_CONVERT_ERROR); + } + } + + private void deleteTempFiles(File... files) { + for (File file : files) { + try { + Files.deleteIfExists(file.toPath()); + } catch (IOException e) { + throw new FileUploadException(ErrorCode.FILE_DELETE_ERROR); + } + } } } diff --git a/src/main/java/com/konggogi/veganlife/global/exception/ErrorCode.java b/src/main/java/com/konggogi/veganlife/global/exception/ErrorCode.java index 2940344f..4e69e6df 100644 --- a/src/main/java/com/konggogi/veganlife/global/exception/ErrorCode.java +++ b/src/main/java/com/konggogi/veganlife/global/exception/ErrorCode.java @@ -64,8 +64,10 @@ public enum ErrorCode { // file-upload MAX_UPLOAD_SIZE_EXCEEDED("FILE_UPLOAD_001", "파일 크기 제한(10MB)을 초과한 파일입니다."), NULL_FILE_NAME("FILE_UPLOAD_002", "파일 이름은 null일 수 없습니다."), - INVALID_EXTENSION("FILE_UPLOAD_003", "유효한 이미지 파일 확장자(jpg, png, gif, jpeg)가 아닙니다."), - FILE_UPLOAD_ERROR("FILE_UPLOAD_004", "파일을 업로드 하는 중 에러가 발생했습니다."), + INVALID_EXTENSION("FILE_UPLOAD_003", "유효한 이미지 파일 확장자(jpg, png, jpeg, webp)가 아닙니다."), + FILE_CONVERT_ERROR("FILE_UPLOAD_004", "파일을 변환 하는 중 에러가 발생했습니다."), + FILE_UPLOAD_ERROR("FILE_UPLOAD_005", "파일을 업로드 하는 중 에러가 발생했습니다."), + FILE_DELETE_ERROR("FILE_UPLOAD_006", "임시 파일을 삭제 하는 중 에러가 발생했습니다."), // elastic search ES_OPERATION_FAILED("ELASTIC_SEARCH_001", "검색 작업 도중 오류가 발생했습니다.");