-
Notifications
You must be signed in to change notification settings - Fork 3
feat : 문제 이미지 기능 ( S3, 업로드 만 가능 ) #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
""" Walkthrough이 변경사항은 AWS S3를 이용한 이미지 업로드 기능을 문제 등록 과정에 통합하는 데 초점을 맞추고 있습니다. S3 연동을 위한 의존성, 설정, 예외 처리, 업로더 클래스 및 관련 엔티티/컨트롤러/서비스의 수정이 포함되어 있습니다. 기존 문제 이미지 엔티티 및 저장소는 삭제되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Admin as Admin
participant Controller as ProblemAdminController
participant Service as ProblemService
participant S3 as S3Uploader
participant DB as Database
Admin->>Controller: POST /problems (multipart/form-data: request, image)
Controller->>Service: createProblem(request, image, user)
Service->>DB: 문제 엔티티 저장
alt 이미지 파일 존재
Service->>S3: upload(image, "problem")
S3-->>Service: 이미지 URL 반환
Service->>DB: 문제 엔티티에 이미지 URL 추가
end
Service-->>Controller: ProblemDetailResponse 반환
Controller-->>Admin: 응답 반환
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (8)
src/main/resources/application.properties (1)
115-124: 하드코드된 S3 설정을 환경 변수로 변경 권장보안상 AWS 크리덴셜을 환경 변수로 관리하는 것은 좋은 접근입니다. 하지만 버킷 이름과 리전이 하드코드되어 있어 다른 환경(개발/스테이징/프로덕션)에서의 유연성이 떨어집니다.
다음과 같이 환경 변수로 변경하는 것을 고려해보세요:
-cloud.aws.s3.bucket=ezcode-s3 -cloud.aws.region.static=ap-northeast-2 +cloud.aws.s3.bucket=${AWS_S3_BUCKET:ezcode-s3} +cloud.aws.region.static=${AWS_REGION:ap-northeast-2}src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java (1)
81-82: ElementCollection 사용 시 성능 고려사항이미지 URL 저장을 위해
@ElementCollection을 사용하는 것은 적절하지만, 대량의 이미지가 있을 경우 성능 문제가 발생할 수 있습니다.FetchType.LAZY사용은 좋은 선택입니다.다음 개선사항을 고려해보세요:
- 이미지 개수 제한 추가
- 별도의 테이블로 분리하여 더 나은 인덱싱과 쿼리 최적화 가능
@Size(max = 10, message = "이미지는 최대 10개까지 등록 가능합니다") @ElementCollection(fetch = FetchType.LAZY) private List<String> imageUrl = new ArrayList<>();src/main/java/org/ezcode/codetest/infrastructure/s3/config/S3Config.java (3)
12-12: 불필요한 어노테이션 제거
@RequiredArgsConstructor어노테이션이 있지만 생성자 주입이 필요한 final 필드가 없습니다.-@RequiredArgsConstructorAlso applies to: 18-18
22-29: AWS 자격증명 보안 강화 권장하드코딩된 자격증명 방식 대신 더 안전한 방법을 고려해보세요.
AWS SDK는 다음과 같은 자격증명 순서로 작동합니다:
- IAM Role (EC2/ECS에서 권장)
- AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 환경변수
- ~/.aws/credentials 파일
- 하드코딩된 자격증명 (현재 방식, 권장하지 않음)
프로덕션 환경에서는 IAM Role 사용을 권장합니다.
18-18: 불필요한 @requiredargsconstructor 어노테이션 제거 필요클래스에 생성자 주입이 필요한 final 필드가 없으므로 @requiredargsconstructor 어노테이션이 불필요합니다.
-@RequiredArgsConstructorsrc/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java (3)
40-41: 파일명 생성 로직 개선 권장현재 파일명 생성 방식에 잠재적 문제가 있습니다.
개선 방안:
private String generateFileName(String dirName, String originalFilename) { if (originalFilename == null || originalFilename.trim().isEmpty()) { originalFilename = "unknown"; } // 파일명에서 확장자 추출 String extension = ""; int dotIndex = originalFilename.lastIndexOf("."); if (dotIndex > 0) { extension = originalFilename.substring(dotIndex); } // 안전한 파일명 생성 (특수문자 제거) String safeName = originalFilename.replaceAll("[^a-zA-Z0-9.-]", "_"); return String.format("%s/%s-%s", dirName, UUID.randomUUID(), safeName); }이유:
- 원본 파일명이 null일 경우 처리
- 특수문자로 인한 S3 키 오류 방지
- 파일명 길이 제한 고려
30-38: MIME 타입 검증 로직 개선 권장현재
contentType.startsWith("image/")로만 검증하고 있지만, 주석에서 언급한 특정 이미지 형식(png, jpeg, jpg, webp)에 대한 더 엄격한 검증을 고려해보세요.-if (contentType == null || !contentType.startsWith("image/")) { +private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of( + "image/jpeg", "image/jpg", "image/png", "image/webp" +); + +if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
47-48: 파일 크기 제한 검증 추가 고려현재 구현에는 업로드 파일 크기 제한이 없습니다. 대용량 파일 업로드를 방지하기 위해 파일 크기 검증을 추가하는 것을 고려해보세요.
+private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB + +if (multipartFile.getSize() > MAX_FILE_SIZE) { + throw new S3Exception( + S3ExceptionCode.S3_INVALID_FILE_TYPE, + S3ExceptionCode.S3_INVALID_FILE_TYPE.getStatus(), + "파일 크기가 너무 큽니다. 최대 10MB까지 업로드 가능합니다." + ); +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
build.gradle(1 hunks)src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java(2 hunks)src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java(4 hunks)src/main/java/org/ezcode/codetest/domain/problem/model/entity/ProblemImage.java(0 hunks)src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemImageRepository.java(0 hunks)src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemImageJpaRepository.java(0 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/S3Directory.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/config/S3Config.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/exception/S3Exception.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java(1 hunks)src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java(3 hunks)src/main/resources/application.properties(1 hunks)
💤 Files with no reviewable changes (3)
- src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemImageRepository.java
- src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemImageJpaRepository.java
- src/main/java/org/ezcode/codetest/domain/problem/model/entity/ProblemImage.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (16)
build.gradle (1)
113-114: Spring Cloud AWS 버전 호환성 검증 필요Spring Boot 3.5.0과 Spring Cloud AWS 2.2.6.RELEASE 간의 호환성 문제가 있을 수 있습니다. 이 버전은 Spring Boot 2.x용으로 설계되었으며 javax 네임스페이스를 사용하지만, Spring Boot 3.x는 Jakarta EE를 사용합니다.
최신 호환 버전 확인을 위해 다음 웹 검색을 실행해주세요:
Spring Boot 3.x compatible Spring Cloud AWS version latestsrc/main/java/org/ezcode/codetest/infrastructure/s3/S3Directory.java (1)
1-21: 깔끔한 enum 구현입니다S3 디렉터리 관리를 위한 enum이 잘 구현되었습니다. 불변성과 명확한 네이밍 컨벤션을 따르고 있으며, 코드가 간결하고 이해하기 쉽습니다.
src/main/java/org/ezcode/codetest/infrastructure/s3/exception/S3Exception.java (1)
18-22: 아래 스크립트를 실행하여BaseException.java클래스의 위치와 생성자 선언부를 확인해주세요.#!/bin/bash # BaseException.java 파일 위치 검색 및 내용 출력 file=$(fd BaseException.java) echo "Found BaseException.java at: $file" sed -n '1,200p' "$file"src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java (1)
72-76: 제출 통계 필드 추가 승인문제별 제출 횟수와 정답 횟수를 추적하는 필드가 적절히 추가되었습니다. null 제약조건과 기본값 초기화가 올바르게 구현되어 있습니다.
src/main/java/org/ezcode/codetest/infrastructure/s3/config/S3Config.java (2)
31-38: Bean 설정 구현 승인AmazonS3 클라이언트 설정이 올바르게 구현되었습니다.
22-29: 전체 설정 파일(.properties, .yml, .yaml)에서 AWS 자격증명 키 사용 여부를 확인하겠습니다:#!/bin/bash # 전체 설정 파일에서 AWS 자격증명 확인 fd -e properties -e yml -e yaml --exec grep -H "cloud.aws.credentials" {}src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java (1)
14-14: 파일 타입 검증 에러 코드 승인잘못된 파일 타입에 대한 BAD_REQUEST 상태 코드 사용이 적절합니다.
src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java (3)
13-14: S3 관련 의존성 추가 승인S3 업로드 기능을 위한 import와 의존성 주입이 적절합니다.
Also applies to: 29-29
33-33: 메서드 시그니처 변경 승인이미지 업로드를 위한 MultipartFile 파라미터 추가가 적절합니다.
33-47: 이미지 업로드 로직 구현이 적절합니다문제 생성 후 이미지 업로드를 처리하는 로직이 올바르게 구현되었습니다. null/empty 체크와 트랜잭션 경계 설정이 적절하며, S3Uploader를 통한 업로드 후 엔티티에 URL을 추가하는 흐름이 정확합니다.
src/main/java/org/ezcode/codetest/presentation/problemmanagement/problem/ProblemAdminController.java (3)
9-9: 멀티파트 지원을 위한 import 추가 승인멀티파트 폼 데이터 처리를 위한 필요한 import들이 적절히 추가되었습니다.
Also applies to: 18-18, 20-20
36-47: 멀티파트 폼 데이터 처리 구현 승인@RequestPart를 사용한 멀티파트 데이터 처리와 선택적 이미지 파라미터 구현이 적절합니다.
구현이 올바른 이유:
consumes = MediaType.MULTIPART_FORM_DATA_VALUE로 명시적 컨텐츠 타입 지정@RequestPart로 각 파트를 별도 처리required = false로 이미지를 선택사항으로 설정- 기존 유효성 검증(
@Valid) 유지
36-48: 멀티파트 폼 데이터 처리 구현이 정확합니다컨트롤러에서 @RequestPart를 사용한 멀티파트 폼 데이터 처리가 올바르게 구현되었습니다. 이미지 파일을 선택적 매개변수로 처리하고, 검증 어노테이션도 적절히 유지되었습니다.
src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java (3)
18-26: 컴포넌트 설정 승인S3Uploader 컴포넌트의 기본 설정과 의존성 주입이 적절합니다.
43-48: S3 업로드 구현 승인ObjectMetadata 설정과 S3 업로드 로직이 적절하게 구현되었습니다.
구현이 우수한 점:
- ContentLength와 ContentType 메타데이터 설정
- 업로드 후 URL 반환
- 적절한 로깅
28-61: S3 업로드 구현이 전반적으로 우수합니다파일 업로드 로직이 잘 구현되었습니다. UUID를 사용한 파일명 생성, 적절한 메타데이터 설정, 예외 처리, 그리고 로깅이 모두 적절합니다.
src/main/java/org/ezcode/codetest/domain/problem/model/entity/Problem.java
Show resolved
Hide resolved
src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java
Outdated
Show resolved
Hide resolved
| Problem problem = ProblemCreateRequest.toEntity(requestDto, user); | ||
| Problem savedProblem = problemDomainService.createProblem(problem); | ||
|
|
||
| // 문제 이미지 있다면? | ||
| if (image != null && !image.isEmpty()) { | ||
| String imageUrl = s3Uploader.upload(image, S3Directory.PROBLEM.getDir()); | ||
| savedProblem.addImage(imageUrl); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
트랜잭션 범위 내 외부 서비스 호출 검토 필요
S3 업로드가 트랜잭션 내에서 실행되고 있습니다. 이는 다음과 같은 문제를 야기할 수 있습니다.
문제점:
- S3 업로드 실패 시 DB 트랜잭션도 롤백됨
- S3 업로드 성공 후 DB 저장 실패 시 S3에 불필요한 파일 남음
- 트랜잭션이 오래 지속됨
개선 방안:
@Transactional
public ProblemDetailResponse createProblem(ProblemCreateRequest requestDto, MultipartFile image, AuthUser authUser) {
User user = userDomainService.getUserById(authUser.getId());
Problem problem = ProblemCreateRequest.toEntity(requestDto, user);
Problem savedProblem = problemDomainService.createProblem(problem);
// 트랜잭션 완료 후 S3 업로드 수행
if (image != null && !image.isEmpty()) {
String imageUrl = uploadImageAfterTransaction(image, savedProblem.getId());
updateProblemWithImage(savedProblem.getId(), imageUrl);
}
return ProblemDetailResponse.from(savedProblem);
}
@Transactional
public void updateProblemWithImage(Long problemId, String imageUrl) {
Problem problem = problemDomainService.getProblem(problemId);
problem.addImage(imageUrl);
}
private String uploadImageAfterTransaction(MultipartFile image, Long problemId) {
try {
return s3Uploader.upload(image, S3Directory.PROBLEM.getDir());
} catch (Exception e) {
// 업로드 실패 시 로깅하고 기본값 반환 또는 재시도 로직
log.error("Problem {} 이미지 업로드 실패", problemId, e);
throw e;
}
}🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java
around lines 37 to 44, the S3 upload is performed inside the transaction, which
can cause rollback issues and leave orphan files on S3. To fix this, refactor
the code to perform the S3 upload after the transaction commits by first saving
the Problem entity within the transaction, then uploading the image outside the
transaction, and finally updating the Problem entity with the image URL in a
separate transactional method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED); 이렇게 커스텀 예외처리 해보는게 좋을거같아 적용해봐
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kimminu7 좋은 제안이네요! 커스텀 예외를 사용하면 일관성 있는 예외 처리가 가능하겠어요. 제안하신 방식으로 개선해보겠습니다:
@Transactional
public ProblemDetailResponse createProblem(ProblemCreateRequest requestDto, MultipartFile image, AuthUser authUser) {
User user = userDomainService.getUserById(authUser.getId());
Problem problem = ProblemCreateRequest.toEntity(requestDto, user);
Problem savedProblem = problemDomainService.createProblem(problem);
// 트랜잭션 완료 후 S3 업로드 수행
if (image != null && !image.isEmpty()) {
String imageUrl = uploadImageAfterTransaction(image, savedProblem.getId());
updateProblemWithImage(savedProblem.getId(), imageUrl);
}
return ProblemDetailResponse.from(savedProblem);
}
@Transactional
public void updateProblemWithImage(Long problemId, String imageUrl) {
Problem problem = problemDomainService.getProblem(problemId);
problem.addImage(imageUrl);
}
private String uploadImageAfterTransaction(MultipartFile image, Long problemId) {
try {
return s3Uploader.upload(image, S3Directory.PROBLEM.getDir());
} catch (Exception e) {
// 커스텀 예외로 일관성 있는 예외 처리
log.error("Problem {} 이미지 업로드 실패", problemId, e);
throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED);
}
}이렇게 하면 S3Uploader 내부의 예외 처리와 동일한 패턴을 유지하면서 일관성 있는 예외 처리가 가능하겠네요!
(\ /)
( ._. )
o_(")(")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안되는데? S3ExceptionCode.S3_UPLOAD_FAILED 생성자 생성하라고하네
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@Kimminu7 어떤 부분이 안 되는지 구체적으로 알려주실 수 있을까요?
컴파일 에러인지, 실행 시 에러인지, 아니면 다른 문제인지 확인해보고 싶어요. 현재 코드베이스 구조를 더 자세히 살펴보고 정확한 해결책을 제공해드리겠습니다.
구체적인 에러 메시지나 문제 상황을 알려주시면 더 정확한 도움을 드릴 수 있어요!
(>.<)
o_(")(")
🏁 Script executed:
#!/bin/bash
# S3Exception과 관련 import들이 올바른지 확인
echo "=== S3Exception 클래스 확인 ==="
cat "src/main/java/org/ezcode/codetest/infrastructure/s3/exception/S3Exception.java"
echo -e "\n=== S3ExceptionCode 확인 ==="
cat "src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java"
echo -e "\n=== ProblemService 현재 import 확인 ==="
head -20 "src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java"
echo -e "\n=== 로깅 관련 import나 annotation 확인 ==="
rg "import.*log|@Slf4j" "src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java"
Length of output: 3056
@Kimminu7 아, 문제를 찾았어요! 몇 가지 이슈가 있었네요:
- S3Exception 생성자 문제: S3Exception은 3개의 파라미터가 필요한데 1개만 전달했어요
- 로깅 import 누락:
log객체가 없는데 사용하려고 했어요 - 필요한 import 누락
다음과 같이 수정하면 됩니다:
// 필요한 import 추가
import org.ezcode.codetest.infrastructure.s3.exception.S3Exception;
import org.ezcode.codetest.infrastructure.s3.exception.code.S3ExceptionCode;
import lombok.extern.slf4j.Slf4j;
@Slf4j // 로깅을 위한 어노테이션 추가
@Service
@RequiredArgsConstructor
public class ProblemService {
// ... 기존 코드 ...
private String uploadImageAfterTransaction(MultipartFile image, Long problemId) {
try {
return s3Uploader.upload(image, S3Directory.PROBLEM.getDir());
} catch (Exception e) {
log.error("Problem {} 이미지 업로드 실패", problemId, e);
// S3Exception 생성자에 필요한 모든 파라미터 전달
throw new S3Exception(
S3ExceptionCode.S3_UPLOAD_FAILED,
S3ExceptionCode.S3_UPLOAD_FAILED.getStatus(),
S3ExceptionCode.S3_UPLOAD_FAILED.getMessage()
);
}
}
}또는 더 간단하게 S3ExceptionCode의 정보를 직접 사용할 수도 있어요:
throw new S3Exception(
S3ExceptionCode.S3_UPLOAD_FAILED,
S3ExceptionCode.S3_UPLOAD_FAILED.getStatus(),
S3ExceptionCode.S3_UPLOAD_FAILED.getMessage()
);이제 제대로 작동할 거예요!
(\ /)
( ^.^ )
o_(")(")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/main/java/org/ezcode/codetest/infrastructure/s3/exception/S3Exception.java (1)
13-17: 필드 중복성 검토
responseCode에서 이미httpStatus와message정보를 추출할 수 있는데 별도 필드로 저장하고 있습니다. 메모리 효율성을 고려하면 getter 메서드에서 동적으로 반환하는 방식도 검토해볼 수 있습니다.현재 구조가 성능상 이점이 있다면 유지하되, 필드 중복성에 대한 검토를 권장합니다:
@Getter public class S3Exception extends BaseException { private final S3ExceptionCode responseCode; - private final ResponseCode responseCode; - private final HttpStatus httpStatus; - private final String message; public S3Exception(S3ExceptionCode responseCode) { + Objects.requireNonNull(responseCode, "S3ExceptionCode cannot be null"); this.responseCode = responseCode; - this.httpStatus = responseCode.getStatus(); - this.message = responseCode.getMessage(); } + + public HttpStatus getHttpStatus() { + return responseCode.getStatus(); + } + + public String getMessage() { + return responseCode.getMessage(); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/exception/S3Exception.java(1 hunks)src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java
- src/main/java/org/ezcode/codetest/infrastructure/s3/exception/code/S3ExceptionCode.java
작업 내용
변경 사항
트러블 슈팅
해결해야 할 문제
참고 사항
⭐ postman에서만 사용 가능

Swagger에서는 request를 json, image를 form data로 따로따로 지원해주지 않음.
그래서, 한다고해도 record 클래스 방식을 다 뜯어 고쳐나가야 하기 해서, postman으로 해야함.
코드 리뷰 전 확인 체크리스트
type :)Summary by CodeRabbit
Summary by CodeRabbit
신규 기능
버그 수정
환경설정