20260125 #76 메뉴 이미지 파일 업로드 기능 추가#82
Conversation
Walkthrough메뉴 CRUD에 멀티파트 이미지 업로드가 추가되고 S3 업로드/삭제 서비스와 유틸이 도입되었으며, 관련 의존성이 Gradle에 추가되었습니다. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Controller as MenuController
participant Service as MenuService
participant S3Svc as S3Service
participant S3 as AWS_S3
Client->>Controller: POST /admin/menus/{menu_id}/image (menuId, MultipartFile)
Controller->>Service: uploadMenuImage(menuId, image)
Service->>Service: 메뉴 조회 (getMenuById)
alt 기존 이미지 존재
Service->>S3Svc: deleteFile(oldImageUrl)
S3Svc->>S3: DeleteObject(bucket, key)
S3-->>S3Svc: delete 응답
end
Service->>S3Svc: uploadFile(image, dirName)
S3Svc->>S3: PutObject(bucket, key, stream)
S3-->>S3Svc: 업로드된 URL
S3Svc-->>Service: imageUrl
Service->>Service: menu.setMenuUrl(imageUrl) 및 저장
Service-->>Controller: MenuResponse
Controller-->>Client: 200 OK (MenuResponse)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 52-53: The build.gradle uses the S3 dependency coordinate
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.1.0' which is
incompatible with Spring Boot 3.5.x; update that dependency to the Spring Boot
3.5-compatible release (use 3.4.x, e.g., 3.4.2) by replacing the old version
string in the implementation declaration so the project uses
io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2.
In `@src/main/java/com/campustable/be/domain/menu/controller/MenuController.java`:
- Around line 57-64: The path variable in
MenuController.getAllMenusByCafeteriaId uses "{cafeteria-id}" which is
nonstandard and inconsistent; change the `@GetMapping` path to use "{cafeteriaId}"
and update the `@PathVariable` annotation to match (remove the explicit name or
use "cafeteriaId") so the method signature and mapping use camelCase
consistently with other endpoints and RFC 6570-friendly variable names.
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 63-85: The current uploadMenuImage method deletes the existing S3
file before uploading the new one, risking data loss if uploadFile fails; modify
uploadMenuImage to first validate input, fetch Menu, store the existing URL
(e.g., oldUrl = menu.getMenuUrl()), perform s3Service.uploadFile(image, dirName)
to obtain newMenuUrl, set menu.setMenuUrl(newMenuUrl) and save via
menuRepository.save(menu), and only after successful save call
s3Service.deleteFile(oldUrl) if oldUrl is non-null/non-blank; ensure you still
throw Invalid file errors early and keep exception handling so failed uploads
never trigger deletion.
🧹 Nitpick comments (2)
src/main/java/com/campustable/be/global/common/CommonUtil.java (1)
3-14: 유틸 클래스 인스턴스화 방지 추가 권장.
정적 메서드만 제공하므로SecurityUtil패턴처럼 private 생성자(또는@UtilityClass)로 인스턴스화를 막는 게 안전합니다.♻️ 제안 변경안
public class CommonUtil { + private CommonUtil() {} public static String nvl(String str1,String str2){src/main/java/com/campustable/be/domain/s3/service/S3Service.java (1)
54-77: 업로드 파일명 정규화로 경로 구분자 제거 권장.
클라이언트가 경로가 포함된 파일명을 보낼 수 있어 S3 키가 의도치 않게 생성될 수 있습니다. 저장 전에 파일명만 추출해 사용하는 편이 안전합니다.♻️ 제안 변경안
- String originalFilename = validateAndExtractFilename(file); - - String storedPath = dirName + "/" + UUID.randomUUID() + "_" + originalFilename; + String originalFilename = validateAndExtractFilename(file); + String sanitizedFilename = originalFilename.replace("\\", "/"); + sanitizedFilename = sanitizedFilename.substring(sanitizedFilename.lastIndexOf('/') + 1); + + String storedPath = dirName + "/" + UUID.randomUUID() + "_" + sanitizedFilename;
| @Override | ||
| @LogMonitoringInvocation | ||
| @GetMapping("/cafeteria/{cafeteria-id}") | ||
| @GetMapping("/menus/cafeteria/{cafeteria-id}") | ||
| public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId( | ||
| @PathVariable(name = "cafeteria-id") Long cafeteriaId | ||
| ) { | ||
| return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId)); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Spring MVC path variable hyphen naming rules allowed characters
💡 Result:
In Spring MVC path patterns, there are two different “naming rules” to keep straight:
1) The path variable name (inside {...}) — hyphen not allowed
Spring’s {var} syntax is based on URI Template variable naming, where a variable name is:
ALPHA / DIGIT / "_" / pct-encoded, with optional dot-separated parts (.) — no-(hyphen). [2]
So this is invalid as a variable name:
/orders/{order-id}←order-idcontains-(not allowed by the varname grammar) [2]
Use something like:
/orders/{orderId}/orders/{order_id}
2) The path variable value (what gets matched from the URL) — hyphen allowed
A captured path segment can contain any URI “unreserved” characters, which explicitly includes the hyphen (-). [1]
So this is fine:
/orders/abc-123matched by/orders/{id}
Notes (Spring-specific syntax)
{var}captures exactly one path segment. [3]{var:regex}lets you constrain the value with a regex. [3]{*var}captures the rest of the path across/separators (multiple segments). [3]
Sources: RFC 3986 (hyphen is unreserved) [1], RFC 6570 (variable-name grammar) [2], Spring PathPattern syntax [3].
🌐 Web query:
Spring @PathVariable hyphen example {variable-name}
💡 Result:
In Spring MVC/Spring Boot you can use a hyphenated URI template variable name like {variable-name}, but you must bind it explicitly because variable-name is not a valid Java parameter name.
`@RestController`
class DemoController {
`@GetMapping`("/items/{variable-name}")
public String get(`@PathVariable`("variable-name") String variableName) {
return variableName;
}
}Spring matches the URI template variable by the name you provide in @PathVariable("..."), which can differ from the Java method parameter name. [1][2]
🏁 Script executed:
# Search for any existing code patterns in the repository
rg "@GetMapping.*{.*-.*}" -A 3 -B 1
rg "@PathVariable.*-" -A 1 -B 1Repository: CampusTable/campus-table-be
Length of output: 572
🌐 Web query:
Spring @GetMapping @PathVariable hyphen work runtime error
💡 Result:
A hyphen in the path variable value (e.g., /users/u-fred) is valid and Spring MVC should bind it normally—no special config needed. If you’re getting null / “Missing URI template variable …”, it’s almost always a name mismatch between the {…} in @GetMapping and what Spring thinks your method parameter is called. Spring binds by the URI-template variable name (or by the Java parameter name if you don’t specify one). [1][2][3]
Use one of these fixes:
// Prefer: use a normal Java identifier in the template
`@GetMapping`("/users/{userId}")
public User get(`@PathVariable` String userId) { ... }// If your template variable name itself contains a hyphen, you MUST specify it explicitly
`@GetMapping`("/users/{user-id}")
public User get(`@PathVariable`("user-id") String userId) { ... }If you were relying on implicit parameter-name binding, ensure your code is compiled with Java’s -parameters flag, otherwise Spring may not “see” the real parameter name and binding can fail unless you specify @PathVariable("..."). [2]
Sources: Spring @PathVariable docs (name/value attributes) [1]; common cause is URI-template-variable name mismatch [2]; dashes in the value are supported [3].
[1] https://docs.spring.io/spring-framework/docs/6.0.17/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html
[2] https://stackoverflow.com/questions/20713522/missing-uri-template-variable-studentid-for-method-parameter-type-java-lang-i
[3] https://stackoverflow.com/questions/4833438/how-do-you-configure-spring-mvc-pathvariables-to-accept-strings-containing-dash
🌐 Web query:
Spring PathPattern variable naming RFC 6570 hyphen support
💡 Result:
In RFC 6570, a variable name (varname) is defined as varchar *( ["."] varchar ) where varchar = ALPHA / DIGIT / "_" / pct-encoded — i.e., a hyphen (-) is not allowed in the variable name grammar. [1]
Spring’s PathPattern / URI template syntax for path variables is {varName} (and {varName:regex}, {*varName}), and it follows the same “variable name” concept, so a template like /{user-id} will not be treated as a valid variable name (use something like /{userId} or /{user_id} instead). [2][3]
Sources:
[1] RFC 6570 (URI Template) variable name ABNF. (datatracker.ietf.org)
[2] Spring Framework reference docs on URI template variables ({varName}, {varName:regex}, {*varName}). (docs.spring.io)
[3] Spring PathPattern Javadoc examples of variable capture syntax. (docs.enterprise.spring.io)
Citations:
- 1: https://datatracker.ietf.org/doc/draft-gregorio-uritemplate?utm_source=openai
- 2: https://docs.spring.io/spring-framework/reference/web/webflux/controller/ann-requestmapping.html?utm_source=openai
- 3: https://docs.enterprise.spring.io/spring-framework/docs/6.1.22/javadoc-api/org/springframework/web/util/pattern/PathPattern.html?utm_source=openai
🏁 Script executed:
# Search for other path variable examples in the codebase to see the pattern
rg "@PathVariable" -B 2 -A 0 | head -30Repository: CampusTable/campus-table-be
Length of output: 3070
🏁 Script executed:
# Check if there are any other examples with hyphenated paths
rg "@GetMapping.*{.*-.*}" -A 2 src/Repository: CampusTable/campus-table-be
Length of output: 187
🏁 Script executed:
# Search for GetMapping patterns with hyphens (fixing regex)
rg '@GetMapping.*\{' -A 1 | grep -E '{.*-.*}'Repository: CampusTable/campus-table-be
Length of output: 197
🏁 Script executed:
# Check all GetMapping annotations to see naming patterns
rg '@GetMapping' -A 0 | head -20Repository: CampusTable/campus-table-be
Length of output: 1787
경로 변수명 형식을 코드베이스 규칙과 통일해주세요.
{cafeteria-id}는 RFC 6570 URI 템플릿 표준에서 변수명에 하이픈을 허용하지 않습니다. 현재 코드는 명시적 @PathVariable(name = "cafeteria-id")로 인해 런타임에 작동하지만, 코드베이스의 다른 모든 엔드포인트({userId}, {menuId}, {cafeteriaId} 등)가 camelCase를 사용하고 있으므로 일관성을 위해 cafeteriaId로 변경해주세요.
수정 제안
- `@GetMapping`("/menus/cafeteria/{cafeteria-id}")
+ `@GetMapping`("/menus/cafeteria/{cafeteriaId}")
public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
- `@PathVariable`(name = "cafeteria-id") Long cafeteriaId
+ `@PathVariable` Long cafeteriaId
) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| @LogMonitoringInvocation | |
| @GetMapping("/cafeteria/{cafeteria-id}") | |
| @GetMapping("/menus/cafeteria/{cafeteria-id}") | |
| public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId( | |
| @PathVariable(name = "cafeteria-id") Long cafeteriaId | |
| ) { | |
| return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId)); | |
| } | |
| `@Override` | |
| `@LogMonitoringInvocation` | |
| `@GetMapping`("/menus/cafeteria/{cafeteriaId}") | |
| public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId( | |
| `@PathVariable` Long cafeteriaId | |
| ) { | |
| return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId)); | |
| } |
🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/menu/controller/MenuController.java`
around lines 57 - 64, The path variable in
MenuController.getAllMenusByCafeteriaId uses "{cafeteria-id}" which is
nonstandard and inconsistent; change the `@GetMapping` path to use "{cafeteriaId}"
and update the `@PathVariable` annotation to match (remove the explicit name or
use "cafeteriaId") so the method signature and mapping use camelCase
consistently with other endpoints and RFC 6570-friendly variable names.
… 3.4.2, 이미지 삭제 후 업로드 -> 업로드 후 삭제 } #76
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 51-53: The S3 starter implementation line (implementation
'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2') pulls a transitive
amazon-s3-encryption-client version vulnerable to CVE-2025-14763; if your
project uses S3 encryption, explicitly override the transitive dependency to a
patched release (amazon-s3-encryption-client version >= 4.0.0) in your
dependency management (or add it as a direct
implementation/dependencyConstraint), or wait for the upstream patch—ensure the
override references the amazon-s3-encryption-client-java artifact and keeps
compatibility with the Spring Cloud AWS 3.4.2 starter.
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 55-59: When an image is uploaded the current code returns
MenuResponse.from(savedMenu) before uploadMenuImage persists the menuUrl,
causing a missing URL in the response; modify the create flow so that after
calling uploadMenuImage(savedMenu.getId(), image) you load the updated Menu
(e.g., via menuRepository.findById(savedMenu.getId()) or have uploadMenuImage
return the updated entity) and then return MenuResponse.from(updatedMenu)
instead of the original savedMenu; keep returning MenuResponse.from(savedMenu)
only when no image was uploaded.
- Around line 76-82: The current flow calls s3Service.deleteFile after
menuRepository.save, but if deleteFile throws an exception it will propagate and
roll back the transaction, undoing the saved newUrl while the S3 upload remains
— instead surround the old-file deletion with a try/catch inside the method
(where menuRepository.save and s3Service.uploadFile are used) so any exception
from s3Service.deleteFile is caught and logged (use the class logger) without
rethrowing; keep uploadFile and menuRepository.save unchanged so the new URL
persists even if deletion fails (refer to s3Service.deleteFile,
s3Service.uploadFile, menuRepository.save, and the method in MenuService that
performs these operations).
|
|
||
| // S3 | ||
| implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2' |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Spring Cloud AWS 3.4.2 Spring Boot 3.5.7 compatibility
💡 Result:
Yes—Spring Cloud AWS 3.4.2 is compatible with Spring Boot 3.5.7, because Spring Cloud AWS 3.4.x targets Spring Boot 3.5.x (and Spring Cloud 2025.0.x). [1][2]
If you’re on Boot 3.5.7, you should align your BOMs accordingly (i.e., Spring Cloud 2025.0.x + Spring Cloud AWS 3.4.2) to avoid dependency mismatches. [1][2]
Sources:
[1] Spring Cloud AWS GitHub README – compatibility table (3.4.x → Boot 3.5.x)
[2] Spring Cloud project page – release train mapping (2025.0.x → Boot 3.5.x)
🌐 Web query:
io.awspring.cloud spring-cloud-aws-starter-s3 latest version 2024 2025
💡 Result:
For io.awspring.cloud:spring-cloud-aws-starter-s3:
- Latest released in 2025: 3.4.2 (published Nov 30, 2025) [1]
- Latest released in 2024: 3.2.1 (published Oct 28, 2024) [1]
As of Feb 1, 2026, Maven Central also shows a newer pre-release: 4.0.0-RC1 (published Jan 14, 2026). The latest stable remains 3.4.2. [1]
Maven coords
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-s3</artifactId>
<version>3.4.2</version>
</dependency>🌐 Web query:
spring-cloud-aws-starter-s3 3.4.2 security vulnerabilities release notes
💡 Result:
spring-cloud-aws-starter-s3 3.4.2 release notes (security-related)
Official 3.4.2 release notes (awspring)
Spring Cloud AWS 3.4.2 (released Dec 1, 2025) lists only one change, and it is in Core (STS autoconfiguration):
StsWebIdentityTokenFileCredentialsProvideris not configured unless the required properties are provided. [1]
No other fixes (including S3-specific or security fixes) are mentioned in the 3.4.2 release notes. [1]
Known vulnerability reported for 3.4.2 (transitive dependency)
MvnRepository flags CVE-2025-14763 as a vulnerability coming from dependencies for Spring Cloud AWS 3.4.2 / its BOM. [2][3]
Per NVD, CVE-2025-14763 affects the Amazon S3 Encryption Client for Java (missing cryptographic key commitment) and AWS recommends upgrading the S3 Encryption Client to 4.0.0+. [4]
Implication: if your dependency tree (directly or transitively) pulls in a vulnerable amazon-s3-encryption-client-java version, you may need to override that dependency (or upgrade to a Spring Cloud AWS release line that pulls a fixed version). [2][4]
Sources: [1] GitHub Releases (awspring/spring-cloud-aws) • [2] MvnRepository spring-cloud-aws 3.4.2 • [3] MvnRepository spring-cloud-aws-dependencies 3.4.2 • [4] NVD CVE-2025-14763
Spring Cloud AWS S3 스타터 3.4.2 버전 확인
Spring Boot 3.5.7과의 호환성 및 최신 안정 버전 여부가 확인되었습니다.
- ✓ 호환성: Spring Cloud AWS 3.4.x는 Spring Boot 3.5.x와 호환됨 (Spring Cloud 2025.0.x BOM과 함께 사용)
- ✓ 최신 안정 버전: 3.4.2는 2026년 2월 현재 최신 정식 릴리스 (4.0.0-RC1은 프리릴리스 상태)
다만 CVE-2025-14763 (Amazon S3 Encryption Client의 암호화 키 커밋 누락)이 전이 의존성으로 플래그되었습니다. S3 암호화 기능을 사용하는 경우, amazon-s3-encryption-client-java 의존성을 4.0.0 이상으로 오버라이드하거나 업스트림 패치를 기다려야 합니다.
🤖 Prompt for AI Agents
In `@build.gradle` around lines 51 - 53, The S3 starter implementation line
(implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2') pulls a
transitive amazon-s3-encryption-client version vulnerable to CVE-2025-14763; if
your project uses S3 encryption, explicitly override the transitive dependency
to a patched release (amazon-s3-encryption-client version >= 4.0.0) in your
dependency management (or add it as a direct
implementation/dependencyConstraint), or wait for the upstream patch—ensure the
override references the amazon-s3-encryption-client-java artifact and keeps
compatibility with the Spring Cloud AWS 3.4.2 starter.
| if (image != null && !image.isEmpty()) { | ||
| uploadMenuImage(savedMenu.getId(), image); | ||
| } | ||
|
|
||
| Menu menu = request.toEntity(category); | ||
| return MenuResponse.from(menuRepository.save(menu)); | ||
| return MenuResponse.from(savedMenu); |
There was a problem hiding this comment.
이미지 업로드 시 createMenu 응답에 menuUrl 누락 가능
uploadMenuImage가 URL을 저장해도 createMenu는 저장 직후의 엔티티로 응답해 menuUrl이 비어 보일 수 있습니다. 업로드가 발생하면 해당 응답을 그대로 반환하는 편이 안전합니다.
✅ 제안 수정
- if (image != null && !image.isEmpty()) {
- uploadMenuImage(savedMenu.getId(), image);
- }
-
- return MenuResponse.from(savedMenu);
+ if (image != null && !image.isEmpty()) {
+ return uploadMenuImage(savedMenu.getId(), image);
+ }
+ return MenuResponse.from(savedMenu);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (image != null && !image.isEmpty()) { | |
| uploadMenuImage(savedMenu.getId(), image); | |
| } | |
| Menu menu = request.toEntity(category); | |
| return MenuResponse.from(menuRepository.save(menu)); | |
| return MenuResponse.from(savedMenu); | |
| if (image != null && !image.isEmpty()) { | |
| return uploadMenuImage(savedMenu.getId(), image); | |
| } | |
| return MenuResponse.from(savedMenu); |
🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java` around
lines 55 - 59, When an image is uploaded the current code returns
MenuResponse.from(savedMenu) before uploadMenuImage persists the menuUrl,
causing a missing URL in the response; modify the create flow so that after
calling uploadMenuImage(savedMenu.getId(), image) you load the updated Menu
(e.g., via menuRepository.findById(savedMenu.getId()) or have uploadMenuImage
return the updated entity) and then return MenuResponse.from(updatedMenu)
instead of the original savedMenu; keep returning MenuResponse.from(savedMenu)
only when no image was uploaded.
… 삭제 실패시 로깅만 하고 업로드 결과 유지} #76
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 174-180: Wrap the S3 deletion call in a try/catch so that
exceptions from s3Service.deleteFile(...) do not abort the database delete; in
the method in MenuService where you check menu.get().getMenuUrl() and call
s3Service.deleteFile(menu.get().getMenuUrl()), catch Throwable/Exception around
that call, log the failure with the logger (including the menuId and exception)
and then proceed to call menuRepository.deleteById(menuId) so DB deletion is not
rolled back by S3 errors.
- Around line 63-89: In uploadMenuImage, ensure that if
menuRepository.save(menu) throws, the newly uploaded S3 file (returned by
s3Service.uploadFile) is deleted to avoid orphaned files: call
s3Service.deleteFile(newUrl) in a catch block around save, log any deletion
failure (include newUrl) but rethrow the original exception so callers still see
the DB error; keep existing cleanup of the oldUrl after successful save
unchanged and reference s3Service.uploadFile, menuRepository.save, and
s3Service.deleteFile when implementing the compensation logic.
| @Transactional | ||
| public MenuResponse uploadMenuImage(Long menuId, MultipartFile image) { | ||
| Menu menu = menuRepository.findById(menuId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.MENU_NOT_FOUND)); | ||
|
|
||
| if (image == null || image.isEmpty()) { | ||
| throw new CustomException(ErrorCode.INVALID_FILE_REQUEST); | ||
| } | ||
|
|
||
| String oldUrl = menu.getMenuUrl(); | ||
| String cafeteriaName = menu.getCategory().getCafeteria().getName(); | ||
| String dirName = "menu/" + cafeteriaName; | ||
|
|
||
| String newUrl = s3Service.uploadFile(image, dirName); | ||
| menu.setMenuUrl(newUrl); | ||
| Menu savedMenu = menuRepository.save(menu); | ||
|
|
||
| if(oldUrl != null && !oldUrl.isBlank()){ | ||
| try { | ||
| s3Service.deleteFile(oldUrl); | ||
| }catch (Exception e){ | ||
| log.warn("uploadMenuImage: 기존 이미지 삭제 실패. oldUrl={}", oldUrl, e); | ||
| } | ||
| } | ||
|
|
||
| return MenuResponse.from(savedMenu); | ||
| } |
There was a problem hiding this comment.
DB 저장 실패 시 신규 S3 파일이 고아로 남을 수 있습니다.
업로드 성공 후 menuRepository.save가 실패하면 S3 파일만 남습니다. 저장 실패 시 신규 파일 정리(보상) 로직을 추가해 주세요.
🧹 제안 수정
String newUrl = s3Service.uploadFile(image, dirName);
menu.setMenuUrl(newUrl);
- Menu savedMenu = menuRepository.save(menu);
+ Menu savedMenu;
+ try {
+ savedMenu = menuRepository.save(menu);
+ } catch (Exception e) {
+ try {
+ s3Service.deleteFile(newUrl);
+ } catch (Exception cleanupEx) {
+ log.warn("uploadMenuImage: 신규 이미지 정리 실패. newUrl={}", newUrl, cleanupEx);
+ }
+ throw e;
+ }🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java` around
lines 63 - 89, In uploadMenuImage, ensure that if menuRepository.save(menu)
throws, the newly uploaded S3 file (returned by s3Service.uploadFile) is deleted
to avoid orphaned files: call s3Service.deleteFile(newUrl) in a catch block
around save, log any deletion failure (include newUrl) but rethrow the original
exception so callers still see the DB error; keep existing cleanup of the oldUrl
after successful save unchanged and reference s3Service.uploadFile,
menuRepository.save, and s3Service.deleteFile when implementing the compensation
logic.
src/main/java/com/campustable/be/domain/menu/service/MenuService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 79-88: In MenuService where menuRepository.save(menu) is called,
fix the catch block so DB save failures aren't masked as S3 errors: attempt S3
cleanup (s3Service.deleteFile(newUrl)) but catch and log any cleanup failure,
then rethrow the original DB exception or throw a CustomException with an
appropriate DB-related ErrorCode (preserving the original exception as the
cause). Ensure references to menuRepository.save(menu),
s3Service.deleteFile(newUrl), CustomException and ErrorCode are updated so the
thrown error reflects the DB failure and includes the original exception for
debugging.
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
New Features
Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.