From 89b69801056438d03a3d94519c96b5285468dc28 Mon Sep 17 00:00:00 2001 From: Yu-Jin9 Date: Wed, 11 Jun 2025 17:42:22 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20api=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/controller/ImageController.java | 17 ++++++- .../domain/image/service/ImageService.java | 45 ++++++++++++++++--- .../backend/global/error/ErrorCode.java | 5 ++- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/day_walk/backend/domain/image/controller/ImageController.java b/src/main/java/com/day_walk/backend/domain/image/controller/ImageController.java index 47985fa..f023660 100644 --- a/src/main/java/com/day_walk/backend/domain/image/controller/ImageController.java +++ b/src/main/java/com/day_walk/backend/domain/image/controller/ImageController.java @@ -1,6 +1,7 @@ package com.day_walk.backend.domain.image.controller; import com.day_walk.backend.domain.image.service.ImageService; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,7 +21,7 @@ public class ImageController { private final ImageService imageService; - // 파일 업로드 API + @Operation(summary = "이미지 업로드", description = "S3에 이미지를 저장 합니다.") @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> uploadImage(@RequestBody MultipartFile file) { @@ -33,4 +34,18 @@ public ResponseEntity> uploadImage(@RequestBody MultipartFil return ResponseEntity.status(HttpStatus.OK).body(response); } + + @Operation(summary = "이미지 삭제", description = "S3에 저장된 이미지를 삭제합니다.") + @DeleteMapping + public ResponseEntity> deleteImage(@RequestBody String imgUrl) { + + boolean result = imageService.deleteImage(imgUrl); + + Map response = new HashMap<>(); + response.put("message", result ? "이미지 삭제 성공!" : "이미지 삭제 실패.."); + response.put("success", result); + + return ResponseEntity.status(HttpStatus.OK).body(response); + } + } diff --git a/src/main/java/com/day_walk/backend/domain/image/service/ImageService.java b/src/main/java/com/day_walk/backend/domain/image/service/ImageService.java index 2f44cc4..96c64fa 100644 --- a/src/main/java/com/day_walk/backend/domain/image/service/ImageService.java +++ b/src/main/java/com/day_walk/backend/domain/image/service/ImageService.java @@ -1,13 +1,18 @@ package com.day_walk.backend.domain.image.service; +import com.day_walk.backend.global.error.CustomException; +import com.day_walk.backend.global.error.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectResponse; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.GetUrlRequest; +import java.net.URL; import java.util.UUID; @Service @@ -18,26 +23,26 @@ public class ImageService { private final S3Client s3Client; - public String uploadImage(MultipartFile multipartFile) { + + if(multipartFile.isEmpty()) { + throw new CustomException(ErrorCode.IMAGE_NOT_FOUND); + } + try { - // S3에 저장될 고유한 파일 이름 생성 String randomName = UUID.randomUUID().toString(); String fileName = randomName + "_" + multipartFile.getOriginalFilename(); - // S3 업로드 요청 객체 생성 PutObjectRequest putObjectRequest = PutObjectRequest.builder() .bucket(bucketName) .key(fileName) .contentType(multipartFile.getContentType()) .build(); - // 파일 업로드 s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(multipartFile.getInputStream(), multipartFile.getSize()) ); - // S3에서 해당 파일의 URL 가져오기 return s3Client.utilities().getUrl(GetUrlRequest.builder() .bucket(bucketName) .key(fileName) @@ -47,4 +52,34 @@ public String uploadImage(MultipartFile multipartFile) { throw new RuntimeException("S3 업로드 실패: " + e.getMessage(), e); } } + + public boolean deleteImage(String imgUrl) { + if(imgUrl == null) { + throw new CustomException(ErrorCode.IMAGE_NOT_FOUND); + } + try { + imgUrl = imgUrl.replace("\"", ""); + URL url = new URL(imgUrl); + String key = url.getPath().substring(1); + System.out.println("Key : " + key); + + DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build(); + + DeleteObjectResponse deleteResponse = s3Client.deleteObject(deleteObjectRequest); + + if (deleteResponse.sdkHttpResponse().isSuccessful()) { + return true; + } else { + System.err.println("이미지 삭제 실패: " + key); + throw new CustomException(ErrorCode.IMAGE_DELETE_FAIL); + } + + } catch (Exception e) { + throw new RuntimeException("S3 삭제 실패: " + e.getMessage(), e); + } + } + } diff --git a/src/main/java/com/day_walk/backend/global/error/ErrorCode.java b/src/main/java/com/day_walk/backend/global/error/ErrorCode.java index 1eaed20..45db7ed 100644 --- a/src/main/java/com/day_walk/backend/global/error/ErrorCode.java +++ b/src/main/java/com/day_walk/backend/global/error/ErrorCode.java @@ -29,7 +29,10 @@ public enum ErrorCode { COURSE_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, false, "COURSE_LIKE_010", "코스 찜한 내역을 찾을 수 없습니다."), COURSE_LIKE_IS_EXIST(HttpStatus.BAD_REQUEST, false, "COURSE_LIKE_030", "이미 찜한 코스입니다."), - PLACE_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, false, "PLACE_LIKE_010", "장소 찜 내역을 찾을 수 없습니다."); + PLACE_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, false, "PLACE_LIKE_010", "장소 찜 내역을 찾을 수 없습니다."), + + IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, false, "IMAGE_010", "이미지를 불러올 수 없습니다."), + IMAGE_DELETE_FAIL(HttpStatus.BAD_REQUEST, false, "IMAGE_040", "이미지 삭제를 실패했습니다."); private final HttpStatus httpStatus; private final boolean success;