From 0bfe0f07f7cdd017c0be7e5ada814ec53dff500d Mon Sep 17 00:00:00 2001 From: kimminu7 Date: Sat, 28 Jun 2025 14:38:44 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20S3=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/ProblemCreateRequest.java | 3 +-- .../dto/request/ProblemUpdateRequest.java | 1 + .../problem/service/ProblemService.java | 24 +++++++++++++++++-- .../domain/problem/model/entity/Problem.java | 4 ++++ .../infrastructure/s3/S3Uploader.java | 19 +++++++++++++++ .../s3/exception/code/S3ExceptionCode.java | 6 ++--- .../problem/ProblemAdminController.java | 7 +++--- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemCreateRequest.java b/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemCreateRequest.java index 2fa76941..600c5ac4 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemCreateRequest.java +++ b/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemCreateRequest.java @@ -13,12 +13,11 @@ public record ProblemCreateRequest( - + // Map으로 묶여있으니 {} 중괄호 사용 @NotNull(message = "카테고리 이름를 설정해야 합니다.") @Schema(description = "카테고리 코드 식별자(영어) / 한글 이름", example = "FOR_BEGINNER : 입문자용") Map categories, - @NotBlank(message = "문제 제목을 입력하세요.") @Schema(description = "제목", example = "A+B") String title, diff --git a/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemUpdateRequest.java index acd6cb07..9b566844 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/org/ezcode/codetest/application/problem/dto/request/ProblemUpdateRequest.java @@ -11,6 +11,7 @@ public record ProblemUpdateRequest( + // 리스트 이므로, [] 대괄호 사용 @Schema(description = "카테고리", example = "FOR_BEGINNER") List categories, diff --git a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java index eb5951dc..9402e17a 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java +++ b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java @@ -46,7 +46,7 @@ public class ProblemService { @Transactional public void createCategory(CategoryCreateRequest requestDto) { - Category category =problemDomainService.createCategory(requestDto.toCategory()); + Category category = problemDomainService.createCategory(requestDto.toCategory()); statUpdateUtil.save(new CategoryStat(category)); } @@ -102,7 +102,7 @@ public ProblemDetailResponse getProblem(Long problemId) { // 문제 수정 ( 관리자 ) @Transactional - public void modifyProblem(Long problemId, ProblemUpdateRequest request) { + public void modifyProblem(Long problemId, ProblemUpdateRequest request, MultipartFile newImage) { Problem findProblem = problemDomainService.getProblem(problemId); @@ -116,6 +116,11 @@ public void modifyProblem(Long problemId, ProblemUpdateRequest request) { request.reference() ); + // 이미지 수정 처리 + if (newImage != null && !newImage.isEmpty()) { + updateProblemImage(findProblem, newImage); + } + problemDomainService.updateCategoryAndSearchEngine(findProblem, request.categories()); } @@ -143,5 +148,20 @@ private String uploadImageAfterTransaction(MultipartFile image, Long problemId) throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED); } } + + // 이미지 수정 + private void updateProblemImage(Problem problem, MultipartFile newImage) { + + // 기존 이미지가 있다면 삭제 처리 + if (!problem.getImageUrl().isEmpty()) { + for(String imageUrl : problem.getImageUrl()) { + s3Uploader.delete(imageUrl); + } + problem.clearImages(); + } + + String newImageUrl = uploadImageAfterTransaction(newImage, problem.getId()); + problem.addImage(newImageUrl); + } } diff --git a/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java b/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java index be36aeff..b22bdb1b 100644 --- a/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java +++ b/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java @@ -149,4 +149,8 @@ public void addImage(String image) { imageUrl.add(image); } + // 이미지 초기화 + public void clearImages() { + this.imageUrl.clear(); + } } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java index fe591a4d..6057661e 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java @@ -26,6 +26,7 @@ public class S3Uploader { @Value("${cloud.aws.s3.bucket}") private String bucket; + // 이미지 업로드 public String upload(MultipartFile multipartFile, String dirName) { try { // MIME 타입 검사 (png, jpeg, jpg, webp 만 가능) @@ -58,4 +59,22 @@ public String upload(MultipartFile multipartFile, String dirName) { throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED); } } + + // 이미지 삭제 + public void delete(String imageUrl) { + try { + String fileName = extractKeyFromProblemUrl(imageUrl); + amazonS3.deleteObject(bucket, fileName); // S3 내 이미지 객체 제거. + log.info("S3에서 이미지 삭제 완료: {}", fileName); + } catch (Exception e) { + log.error("S3 이미지 삭제 실패: {}", imageUrl, e); + throw new S3Exception(S3ExceptionCode.S3_DELETE_FAILED); + } + } + + // 문제 이미지 URL 가져오기 + private String extractKeyFromProblemUrl(String fileUrl) { + // S3 주소 포맷 기준으로 잘라내기 + return fileUrl.substring(fileUrl.indexOf("problem/")); + } } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java b/src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java index 579b8c55..29a37c04 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java @@ -10,9 +10,9 @@ @RequiredArgsConstructor public enum S3ExceptionCode implements ResponseCode { - S3_UPLOAD_FAILED(false, HttpStatus.INTERNAL_SERVER_ERROR, "S3 이미지 업로드 중 오류가 발생했습니다."), - S3_INVALID_FILE_TYPE(false, HttpStatus.BAD_REQUEST, "이미지 파일만 업로드할 수 있습니다."); - + S3_UPLOAD_FAILED(false, HttpStatus.INTERNAL_SERVER_ERROR, "S3 이미지 업로드 중 오류가 발생 했습니다."), + S3_INVALID_FILE_TYPE(false, HttpStatus.BAD_REQUEST, "이미지 파일만 업로드할 수 있습니다."), + S3_DELETE_FAILED(false, HttpStatus.INTERNAL_SERVER_ERROR, "S3 이미지 삭제 중 오류가 발생 했습니다."); private final boolean success; private final HttpStatus status; diff --git a/src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java b/src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java index 06dab09c..d60510fd 100644 --- a/src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java +++ b/src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java @@ -47,14 +47,15 @@ public ResponseEntity createProblem( .build(); } - @PutMapping("/{problemId}") + @PutMapping(path = "/{problemId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) @Operation(summary = "문제 수정", description = "문제를 수정합니다.") @ApiResponse(responseCode = "200", description = "문제 수정 성공") public ResponseEntity modifyProblem( @PathVariable Long problemId, - @Valid @RequestBody ProblemUpdateRequest request + @RequestPart @Valid ProblemUpdateRequest request, + @RequestPart(value = "image", required = false) MultipartFile image ) { - problemService.modifyProblem(problemId, request); + problemService.modifyProblem(problemId, request, image); return ResponseEntity .status(HttpStatus.OK) From 84ab0f8e9b06b93f83c9ab5dd6d4837b6e0a4b8e Mon Sep 17 00:00:00 2001 From: kimminu7 Date: Sat, 28 Jun 2025 15:36:03 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat=20:=20S3=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/problem/service/ProblemService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java index 9402e17a..37b91a0f 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java +++ b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java @@ -131,6 +131,13 @@ public void removeProblem(Long problemId) { Problem findProblem = problemDomainService.getProblem(problemId); + if (!findProblem.getImageUrl().isEmpty()) { + for(String imageUrl : findProblem.getImageUrl()) { + s3Uploader.delete(imageUrl); + } + findProblem.clearImages(); + } + problemDomainService.removeProblem(findProblem); } From 6788dfd6d9a0f905929aff9ec7447374dda148db Mon Sep 17 00:00:00 2001 From: kimminu7 Date: Sat, 28 Jun 2025 16:36:51 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor=20:=20=EC=A4=91=EA=B4=84=ED=98=B8?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codetest/application/problem/service/ProblemService.java | 3 ++- .../ezcode/codetest/domain/problem/model/entity/Problem.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java index 37b91a0f..a704ed48 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java +++ b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java @@ -131,13 +131,14 @@ public void removeProblem(Long problemId) { Problem findProblem = problemDomainService.getProblem(problemId); + // 문제 이미지가 있으면 삭제 if (!findProblem.getImageUrl().isEmpty()) { for(String imageUrl : findProblem.getImageUrl()) { s3Uploader.delete(imageUrl); } - findProblem.clearImages(); } + // Soft Delete problemDomainService.removeProblem(findProblem); } diff --git a/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java b/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java index 87f20739..6642b87b 100644 --- a/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java +++ b/src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java @@ -152,6 +152,7 @@ public void addImage(String image) { // 이미지 초기화 public void clearImages() { this.imageUrl.clear(); + } public void incrementTotalSubmissions() { totalSubmissions += 1; From 72ad63f21c6fa255f1323a7ac0b49ba7838055e8 Mon Sep 17 00:00:00 2001 From: kimminu7 Date: Sat, 28 Jun 2025 16:59:39 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor=20:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/problem/service/ProblemService.java | 8 ++++---- .../org/ezcode/codetest/infrastructure/s3/S3Uploader.java | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java index a704ed48..ca7536d5 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java +++ b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java @@ -133,8 +133,8 @@ public void removeProblem(Long problemId) { // 문제 이미지가 있으면 삭제 if (!findProblem.getImageUrl().isEmpty()) { - for(String imageUrl : findProblem.getImageUrl()) { - s3Uploader.delete(imageUrl); + for(String fileUrl : findProblem.getImageUrl()) { + s3Uploader.delete(fileUrl); } } @@ -162,8 +162,8 @@ private void updateProblemImage(Problem problem, MultipartFile newImage) { // 기존 이미지가 있다면 삭제 처리 if (!problem.getImageUrl().isEmpty()) { - for(String imageUrl : problem.getImageUrl()) { - s3Uploader.delete(imageUrl); + for(String fileUrl : problem.getImageUrl()) { + s3Uploader.delete(fileUrl); } problem.clearImages(); } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java index 6057661e..362dac8b 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java @@ -61,13 +61,13 @@ public String upload(MultipartFile multipartFile, String dirName) { } // 이미지 삭제 - public void delete(String imageUrl) { + public void delete(String fileUrl) { try { - String fileName = extractKeyFromProblemUrl(imageUrl); + String fileName = extractKeyFromProblemUrl(fileUrl); amazonS3.deleteObject(bucket, fileName); // S3 내 이미지 객체 제거. log.info("S3에서 이미지 삭제 완료: {}", fileName); } catch (Exception e) { - log.error("S3 이미지 삭제 실패: {}", imageUrl, e); + log.error("S3 이미지 삭제 실패: {}", fileUrl, e); throw new S3Exception(S3ExceptionCode.S3_DELETE_FAILED); } }