-
Notifications
You must be signed in to change notification settings - Fork 0
[CMAT-48] feat: 커리어 작성 API에 이미지 업로드 기능 추가 #31
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이 PR은 AWS S3 서비스 사용을 위한 SDK 종속성 추가와 함께, Answer 도메인의 엔티티, 컨트롤러, DTO, Converter, 서비스 등 전반에 걸쳐 필드 재정렬, 파라미터 단순화, 네이밍 수정 및 파일 업로드 기능 추가 등의 변경을 포함합니다. 또한, API 문서화를 위한 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AnswerController
participant AnswerCommandService
participant S3Uploader
participant "AWS S3"
Client->>AnswerController: 이미지 포함 응답 전송 요청
AnswerController->>AnswerCommandService: 멀티파트 파라미터 전달 (데이터 + 이미지 파일 리스트)
AnswerCommandService->>S3Uploader: uploadImage(file) 호출
S3Uploader->>AWS S3: 파일 업로드 (putObject)
AWS S3-->>S3Uploader: 업로드 URL 응답
S3Uploader-->>AnswerCommandService: URL 반환
AnswerCommandService-->>AnswerController: 응답 데이터에 URL 포함 처리
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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: 5
🔭 Outside diff range comments (1)
src/main/java/UMC/career_mate/domain/answer/Answer.java (1)
50-52: 내용 업데이트 메소드에 유효성 검증이 필요합니다.updateContent 메소드에서 content의 null 체크나 빈 문자열 검증이 없습니다. 다음과 같은 검증을 추가하는 것이 좋습니다:
public void updateContent(String content) { + if (content == null || content.trim().isEmpty()) { + throw new IllegalArgumentException("Content cannot be null or empty"); + } this.content = content; }
🧹 Nitpick comments (8)
src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java (1)
70-72: @deprecated 어노테이션에 대한 상세한 설명 필요@deprecated 어노테이션이 사용된 메서드에 대해 더 자세한 설명이 필요합니다. 대체 방법이나 제거 예정 시기를 명시하면 좋을 것 같습니다.
@Deprecated -@Operation(summary = "채용 공고 db 저장 api", description = "테스트 개발용입니다") +@Operation( + summary = "채용 공고 db 저장 api", + description = """ + 테스트 개발용입니다. + 이 API는 향후 제거될 예정이며, 프로덕션 환경에서 사용하지 마세요. + 대체 방법: [대체 방법 설명] + 제거 예정 시기: [제거 예정 시기] + """ +)src/main/java/UMC/career_mate/domain/member/controller/MemberController.java (1)
23-45: API 문서의 가독성 개선 필요현재 Operation 설명이 텍스트 블록으로 되어 있어 가독성이 떨어집니다. Swagger UI에서 더 보기 좋게 표현하기 위해 마크다운 형식으로 변경하면 좋을 것 같습니다.
@Operation(summary = "프로필 설정 API", description = """ - 아래 두가지 요소에는 반드시 목록 중 하나를 입력해주세요 - educationLevel(학력) 에 들어가야 할 목록입니다. - 1. MIDDLE (중학교 이하) \s - 2. HIGH (고등학교) \s - 3. JUNIOR_COLLEGE (전문대학) \s - 4. UNIVERSITY (대학교) \s - 5. MASTER (석사) \s - 6. DOCTOR (박사) \s + ## 필수 입력 사항 + 아래 두 가지 요소는 반드시 목록 중 하나를 선택해야 합니다. - educationStatus(수료 상태)에 들어가야 할 목록입니다. - 1. ENROLLED (재학) \s - 2. ON_LEAVE (휴학) \s - 3. GRADUATED (졸업) \s - 4. COMPLETED (수료) \s + ### educationLevel (학력) + - MIDDLE: 중학교 이하 + - HIGH: 고등학교 + - JUNIOR_COLLEGE: 전문대학 + - UNIVERSITY: 대학교 + - MASTER: 석사 + - DOCTOR: 박사 - 직무는 직무 id를 넣어주세요.\s - ex) "job" : 1 + ### educationStatus (수료 상태) + - ENROLLED: 재학 + - ON_LEAVE: 휴학 + - GRADUATED: 졸업 + - COMPLETED: 수료 + + ### job (직무) + 직무 ID를 입력해주세요. 예: `"job": 1` """src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java (3)
30-31: 멤버 리포지토리 의존성 주의
memberRepository 필드가 주입되어 있지만 현재 메서드 로직에서 직접 사용되지 않는 것으로 보입니다. 이후 기능 확장 대비라면 문제 없으나, 불필요하다면 제거하거나 활용 방안을 재검토하길 권장합니다.
33-63: 이미지 리스트와 AnswerGroup의 매핑 로직 확인 권장
answerGroupDTOList와 imageUrlList 크기가 일치하지 않을 경우, 특정 시퀀스에 이미지가 null로 매핑될 수 있습니다. 예상된 동작이라면 괜찮으나, 사용자 혼란을 줄이기 위해 입력 데이터 개수를 검증하는 로직을 추가하는 것을 고려해보세요.
65-102: 중복 제거 및 재사용성 향상을 위한 메서드 분리 제안
updateAnswerList 메서드가 saveAnswerList와 유사 로직을 갖고 있으므로, 공통 부문을 별도의 메서드나 유틸 클래스로 추출하여 코드 중복을 줄이는 방안을 고려해볼 수 있습니다.src/main/java/UMC/career_mate/global/s3/service/S3Uploader.java (1)
1-34: S3 업로더 구현이 단순 명확하며 확장 가능성도 충분합니다.
UUID로 파일 충돌을 방지하고, ObjectMetadata를 설정하여 메타정보를 기록하는 방식이 적절합니다. 향후 접근 제어 정책(ACL)이나 버킷 폴더 구조가 필요하다면 putObject 과정에서 추가 설정을 고려해보시길 바랍니다.src/main/java/UMC/career_mate/global/s3/config/S3Config.java (1)
23-33: S3 클라이언트 구성이 적절합니다.AmazonS3Client 빈 설정이 올바르게 구현되어 있습니다. 다만, 다음 사항을 고려해보세요:
- 타임아웃 설정 추가
- 재시도 정책 구성
- 에러 핸들링 추가
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
build.gradle(1 hunks)src/main/java/UMC/career_mate/domain/answer/Answer.java(2 hunks)src/main/java/UMC/career_mate/domain/answer/controller/AnswerController.java(6 hunks)src/main/java/UMC/career_mate/domain/answer/converter/AnswerConverter.java(3 hunks)src/main/java/UMC/career_mate/domain/answer/dto/request/AnswerCreateOrUpdateDTO.java(1 hunks)src/main/java/UMC/career_mate/domain/answer/dto/response/AnswerInfoListDTO.java(1 hunks)src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java(2 hunks)src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java(2 hunks)src/main/java/UMC/career_mate/domain/content/controller/ContentController.java(2 hunks)src/main/java/UMC/career_mate/domain/job/controller/JobController.java(2 hunks)src/main/java/UMC/career_mate/domain/member/controller/MemberController.java(1 hunks)src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java(1 hunks)src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java(2 hunks)src/main/java/UMC/career_mate/domain/recruitScrap/controller/RecruitScrapController.java(2 hunks)src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java(2 hunks)src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java(0 hunks)src/main/java/UMC/career_mate/global/s3/config/S3Config.java(1 hunks)src/main/java/UMC/career_mate/global/s3/service/S3Uploader.java(1 hunks)src/main/java/UMC/career_mate/global/security/SecurityController.java(2 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java
✅ Files skipped from review due to trivial changes (5)
- src/main/java/UMC/career_mate/domain/job/controller/JobController.java
- src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java
- src/main/java/UMC/career_mate/domain/answer/dto/response/AnswerInfoListDTO.java
- src/main/java/UMC/career_mate/global/security/SecurityController.java
- src/main/java/UMC/career_mate/domain/recruitScrap/controller/RecruitScrapController.java
🔇 Additional comments (9)
src/main/java/UMC/career_mate/domain/template/controller/TemplateController.java (1)
10-10: Swagger 문서화가 적절히 추가되었습니다.API 문서화를 위한 @tag 어노테이션이 일관성 있게 적용되었습니다.
Also applies to: 21-21
src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java (1)
15-16: Swagger 문서화가 적절히 추가되었습니다.API 문서화를 위한 @tag 어노테이션이 일관성 있게 적용되었습니다.
Also applies to: 28-28
src/main/java/UMC/career_mate/domain/member/controller/MemberController.java (1)
11-11: Swagger 문서화가 적절히 추가되었습니다.API 문서화를 위한 @tag 어노테이션이 일관성 있게 적용되었습니다.
Also applies to: 17-17
src/main/java/UMC/career_mate/domain/content/controller/ContentController.java (2)
13-13: Swagger 문서화가 적절히 추가되었습니다.API 문서화를 위한 @tag 어노테이션이 일관성 있게 적용되었습니다.
Also applies to: 21-21
28-46: API 문서의 요청 예시가 잘 작성되었습니다.JSON 예시를 포함한 API 문서화가 매우 명확하게 작성되었습니다. 다른 API들도 이와 같은 형식으로 예시를 추가하면 좋을 것 같습니다.
src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java (1)
10-10: 사용되지 않는 import 확인 필요현재 MemberRepository import가 추가되었지만 서비스 내에서 실제로 사용되지 않고 있습니다. 향후 구현을 위해 추가된 것인지 확인이 필요합니다.
다음 스크립트를 실행하여 MemberRepository의 사용 여부를 확인할 수 있습니다:
✅ Verification successful
사용되지 않는
MemberRepositoryimport 관련 확인 결과
AnswerQueryService.java파일 내에서 import된UMC.career_mate.domain.member.repository.MemberRepository는 사용되지 않는 것으로 확인되었습니다.- 저장소 내 다른 파일(
MemberService.java)에서는 해당 리포지토리가 사용되고 있으나, 리뷰 대상 파일에서는 별도로 참조되고 있지 않습니다.- 추후 사용 계획이 없다면 해당 import를 제거하거나, 현재 진행 중인 구현 사항과 관련된 주석 등으로 향후 작성될 기능임을 명시하는 것을 권장합니다.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # MemberRepository 사용 현황 확인 rg -l "memberRepository\." src/Length of output: 104
src/main/java/UMC/career_mate/domain/answer/service/AnswerCommandService.java (1)
6-7: 새로 추가된 import 문들이 정상적으로 반영되었습니다.
새로운 DTO와 S3Uploader, MultipartFile, IOException 등이 적절히 import되어 있어 보입니다. 현재 코드 상에서 의도에 맞는 의존성만 추가된 것으로 판단됩니다.Also applies to: 10-10, 15-15, 19-19, 21-23
src/main/java/UMC/career_mate/domain/answer/dto/request/AnswerCreateOrUpdateDTO.java (1)
5-5: DTO 구조 개편으로 가독성과 명확성이 증가했습니다.
AnswerList → AnswerGroupDTO, AnswerInfo → AnswerInfoDTO 등 이름이 더 직관적으로 개선되었으며, record를 통한 간결성도 유지되어 있습니다. 이 변경 사항은 데이터를 다뤄야 하는 서비스 로직에서도 확실한 의도를 전달할 수 있을 것으로 보입니다.Also applies to: 7-7, 10-10
build.gradle (1)
70-74: 🛠️ Refactor suggestion중복 및 버전 불일치 가능성 검토 필요
새롭게 추가된 AWS S3 관련 의존성 부분에서 동일한 라이브러리인
software.amazon.awssdk:s3가 두 가지 다른 버전(2.20.79와 2.30.2)으로 포함되어 있습니다. 이로 인해 의도하지 않은 버전 충돌이나 예기치 않은 동작이 발생할 수 있으므로, 실제로 두 버전을 모두 사용해야 하는지 검토하시기 바랍니다. 또한,com.amazonaws:aws-java-sdk-s3와 함께 사용할 경우, AWS SDK의 버전 간 충돌 가능성도 있으니 의존성 관리를 재점검하는 것이 좋겠습니다.
src/main/java/UMC/career_mate/domain/answer/service/AnswerQueryService.java
Outdated
Show resolved
Hide resolved
| public static Answer toAnswer(String content, Member member, Question question, long sequence) { | ||
| return Answer.builder() | ||
| .content(answerInfo.content()) | ||
| .content(content) | ||
| .member(member) | ||
| .question(question) | ||
| .sequence(sequence) | ||
| .build(); | ||
| } |
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
toAnswer 메소드의 매개변수 유효성 검증이 필요합니다.
매개변수에 대한 null 체크가 없습니다. 다음과 같은 방어적 프로그래밍을 추가하는 것이 좋습니다:
public static Answer toAnswer(String content, Member member, Question question, long sequence) {
+ Objects.requireNonNull(member, "Member cannot be null");
+ Objects.requireNonNull(question, "Question cannot be null");
+ if (content == null) {
+ content = "";
+ }
return Answer.builder()
.content(content)
.member(member)
.question(question)
.sequence(sequence)
.build();
}📝 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.
| public static Answer toAnswer(String content, Member member, Question question, long sequence) { | |
| return Answer.builder() | |
| .content(answerInfo.content()) | |
| .content(content) | |
| .member(member) | |
| .question(question) | |
| .sequence(sequence) | |
| .build(); | |
| } | |
| public static Answer toAnswer(String content, Member member, Question question, long sequence) { | |
| Objects.requireNonNull(member, "Member cannot be null"); | |
| Objects.requireNonNull(question, "Question cannot be null"); | |
| if (content == null) { | |
| content = ""; | |
| } | |
| return Answer.builder() | |
| .content(content) | |
| .member(member) | |
| .question(question) | |
| .sequence(sequence) | |
| .build(); | |
| } |
| @RequestPart("data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | ||
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | ||
| answerCommandService.updateAnswerList(member, answerCreateOrUpdateDTO, imageFileList); | ||
| return ApiResponse.onSuccess(UPDATE_ANSWER_LIST); |
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.
updateAnswerList 메소드도 동일한 파일 검증이 필요합니다.
saveAnswerList와 동일한 파일 업로드 보안 및 유효성 검증을 적용해야 합니다.
| @RequestPart(value = "data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | ||
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | ||
| answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO, imageFileList); |
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.
파일 업로드 처리에 대한 보안 및 유효성 검증이 필요합니다.
이미지 파일 업로드 처리 시 다음 사항들을 고려해야 합니다:
- 파일 크기 제한
- 허용된 파일 형식 검증
- 악성 파일 검사
- IOException에 대한 구체적인 예외 처리
다음과 같은 검증 로직을 추가하는 것을 권장합니다:
+ @PostMapping
+ public ApiResponse<CommonResultCode> saveAnswerList(
+ @LoginMember Member member,
+ @RequestPart(value = "data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO,
+ @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException {
+
+ if (imageFileList != null) {
+ for (MultipartFile file : imageFileList) {
+ validateImageFile(file);
+ }
+ }
+
+ answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO, imageFileList);
+ return ApiResponse.onSuccess(CREATE_ANSWER_LIST);
+ }
+
+ private void validateImageFile(MultipartFile file) {
+ if (file.getSize() > 5_000_000) { // 5MB
+ throw new IllegalArgumentException("File size exceeds maximum limit");
+ }
+ String contentType = file.getContentType();
+ if (contentType == null || !contentType.startsWith("image/")) {
+ throw new IllegalArgumentException("Only image files are allowed");
+ }
+ }📝 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.
| @RequestPart(value = "data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | |
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | |
| answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO, imageFileList); | |
| @PostMapping | |
| public ApiResponse<CommonResultCode> saveAnswerList( | |
| @LoginMember Member member, | |
| @RequestPart(value = "data") @Valid AnswerCreateOrUpdateDTO answerCreateOrUpdateDTO, | |
| @RequestPart(value = "image", required = false) List<MultipartFile> imageFileList) throws IOException { | |
| if (imageFileList != null) { | |
| for (MultipartFile file : imageFileList) { | |
| validateImageFile(file); | |
| } | |
| } | |
| answerCommandService.saveAnswerList(member, answerCreateOrUpdateDTO, imageFileList); | |
| return ApiResponse.onSuccess(CREATE_ANSWER_LIST); | |
| } | |
| private void validateImageFile(MultipartFile file) { | |
| if (file.getSize() > 5_000_000) { // 5MB | |
| throw new IllegalArgumentException("File size exceeds maximum limit"); | |
| } | |
| String contentType = file.getContentType(); | |
| if (contentType == null || !contentType.startsWith("image/")) { | |
| throw new IllegalArgumentException("Only image files are allowed"); | |
| } | |
| } |
jpark0506
left a comment
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.
LGTM~ 고생 많으셨습니다
| List<String> imageUrlList = new ArrayList<>(); | ||
| if (imageFileList != null && !imageFileList.isEmpty()) { | ||
| for (MultipartFile imageFile : imageFileList) { | ||
| if (!imageFile.isEmpty()) { | ||
| imageUrlList.add(s3Uploader.uploadImage(imageFile)); // S3 업로드 후 URL 리스트에 저장 | ||
| } | ||
| } | ||
| } |
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.
S3 업로드를 구현하시느라 수고 많으셨습니다!
개인마다 스타일이 다르긴 하지만, 저 같은 경우에는 여러 파일을 업로드할 때 uploadImageList 메서드를 따로 만들어 Uploader 클래스에 위임하는 방식을 선호하는데요
이렇게 하면 단일 업로드와 다중 업로드의 역할이 명확하게 분리되고, SRP와 코드의 가독성과 유지보수성이 향상되는 장점이 있습니다.
현재 방식도 충분히 깔끔하지만, 만약 Uploader 클래스에서 단일 파일 업로드(uploadImage)와 리스트 업로드(uploadImageList)를 분리하면 재사용성이 높아질 것 같습니다!
현재 코드도 이미 잘 작성되어 있지만, 유지보수를 고려한다면 이런 방식도 한 번 고려해 보시면 좋을 것 같습니다.
고생 많으셨습니다~!
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.
넵! 좋은 생각인 거 같습니다! 추후에 코드 리팩토링할 때, 반영하겠습니다!😀
momuzzi
left a comment
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.
LGTM~~ 전체 도메인 스웨거 tag 설정 고생하셨습니다!
#️⃣ 요약 설명
커리어 작성 API에 이미지 업로드 기능 추가
📝 작업 내용
커리어 작성 API에 이미지 업로드 기능 추가했습니다.
동작 확인
💬 리뷰 요구사항(선택)
Summary by CodeRabbit
새로운 기능
API 개선
문서화 개선
기타