-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Swagger 설정 및 모집글 API 문서화 완료 #48
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이 변경은 recruitment-service 모듈의 전체 소스 코드, Gradle 빌드 및 설정 파일, 그리고 여러 서비스별 CI/CD GitHub Actions 워크플로우를 추가합니다. 주요 도메인 엔티티, 컨트롤러, 서비스, 예외 처리, API 응답 구조, 테스트, 그리고 배포 자동화 구성이 포함되어 있습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant RecruitmentController
participant RecruitmentService
participant UserRepository
participant StoreRepository
participant RecruitmentRepository
participant OrderClient
participant OrderServer
Client->>RecruitmentController: POST /api/v1/recruitments
RecruitmentController->>RecruitmentService: createRecruitment(dto)
RecruitmentService->>UserRepository: findById(userId)
RecruitmentService->>StoreRepository: findById(storeId)
RecruitmentService->>RecruitmentRepository: save(new Recruitment)
RecruitmentService->>OrderClient: createOrder(orderRequestDto)
OrderClient->>OrderServer: POST /api/v1/orders
OrderServer-->>OrderClient: 응답 (orderId)
OrderClient-->>RecruitmentService: orderId
RecruitmentService->>RecruitmentRepository: save(Recruitment with orderId)
RecruitmentService-->>RecruitmentController: 완료
RecruitmentController-->>Client: 응답
Possibly related PRs
Suggested labels
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: 30
🔭 Outside diff range comments (1)
recruitment-service/.github/workflows/ci-cd-payment-service.yml (1)
1-72: 🛠️ Refactor suggestion워크플로우 템플릿화를 고려하여 중복을 줄이세요.
이 워크플로우는 앞의 order-service, user-service 워크플로우와 거의 동일한 구조와 문제점을 가지고 있습니다. 중복을 줄이기 위해 재사용 가능한 워크플로우 템플릿 사용을 고려해보세요.
GitHub Actions의 재사용 가능한 워크플로우를 사용하여 중복을 줄일 수 있습니다:
.github/workflows/reusable-service-ci-cd.yml생성- 각 서비스별 워크플로우에서 해당 템플릿 호출
- 서비스명을 파라미터로 전달
이렇게 하면 유지보수성이 크게 향상됩니다.
♻️ Duplicate comments (3)
recruitment-service/.github/workflows/ci-cd-user-service.yml (2)
1-1: 워크플로우 이름을 더 구체적으로 수정하세요.order-service 워크플로우와 동일한 문제입니다. user-service를 위한 워크플로우임을 명확히 해야 합니다.
-name: CI/CD for Spring Boot + MySQL with Docker Compose +name: CI/CD for User Service - Spring Boot + MySQL with Docker Compose
62-71: 🛠️ Refactor suggestionSSH Action 버전 업데이트 및 배포 스크립트 개선이 필요합니다.
order-service 워크플로우와 동일한 문제들이 있습니다.
recruitment-service/.github/workflows/ci-cd-payment-service.yml (1)
10-10: 주석 일관성 문제를 수정하세요.다른 워크플로우들과 동일한 주석 문제가 있습니다.
- SERVICE_NAME: payment-service # 예: order-service + SERVICE_NAME: payment-service # 현재 서비스명Also applies to: 18-18
🧹 Nitpick comments (32)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (2)
10-13: 에러 코드 번호 체계 검토 권장
NOT_FOUND(2007),FORBIDDEN(2006)순서가 숫자 오름차순과 어긋납니다. 팀 내 에러 코드 가이드에 맞춰 연속성 있게 재정렬하거나 의미별 그룹으로 관리하면 유지보수에 도움이 됩니다.
15-17: 필드 설명용 Javadoc 추가 제안
code,status,message에 대한 간단한 Javadoc 주석을 넣으면 IDE에서 문서를 확인하기 더 편리해집니다.recruitment-service/settings.gradle (1)
1-1: rootProject.name 명칭 일관성 제안
현재'recruitment'로 설정되어 있는데, 서비스 디렉토리명(recruitment-service) 및 CI/CD Workflow의SERVICE_NAME(recruitment-service)과 맞추는 것을 권장합니다.-rootProject.name = 'recruitment' +rootProject.name = 'recruitment-service'recruitment-service/.gitattributes (1)
1-3: 추가 파일 유형 EOL 관리 추천
.sh스크립트와 YAML 파일에도 EOL 규칙을 추가하면 더 일관성 있는 크로스 플랫폼 지원이 가능합니다.*.sh text eol=lf *.yml text eol=lfrecruitment-service/gradle/wrapper/gradle-wrapper.properties (1)
4-4: 네트워크 타임아웃 값 재검토 제안
networkTimeout=10000은 네트워크 상황에 따라 짧을 수 있습니다. 다운로드 실패 가능성을 줄이기 위해 기본값(60000) 또는 그 이상으로 조정하는 것을 고려해 보세요.recruitment-service/src/test/java/com/example/recruitment/RecruitmentApplicationTests.java (1)
1-13: 스모크 테스트: 애플리케이션 컨텍스트 로딩 확인
@SpringBootTest와contextLoads()메서드를 통해 애플리케이션 컨텍스트가 정상적으로 로드되는지 검증합니다. 기본적인 통합 테스트로 적절하지만, 향후 MockMVC나 단위 테스트를 추가하여 컨트롤러·서비스 레이어 기능을 검증하면 더욱 좋습니다.recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1)
7-14: 테스트 컨트롤러 구현이 적절함Swagger 테스트용 컨트롤러가 올바르게 구현되었습니다. 기본적인 Spring MVC 패턴을 잘 따르고 있습니다.
선택적 개선사항으로 OpenAPI 문서화 어노테이션을 추가하는 것을 고려해보세요:
+import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; @RestController @RequestMapping("/swagger-test") +@Tag(name = "Swagger Test", description = "Swagger 연동 테스트 API") public class SwaggerTestController { + @Operation(summary = "Swagger 연동 테스트", description = "Swagger가 정상적으로 작동하는지 확인하는 테스트 엔드포인트") @GetMapping public String test() { return "Swagger works!"; } }recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java (1)
6-11: 데이터 클래스 구현이 적절함Lombok을 활용한 깔끔한 데이터 클래스 구현입니다. 에러 처리를 위한 구조가 잘 설계되었습니다.
더 안전한 불변 객체로 만드는 것을 고려해보세요:
@Getter @AllArgsConstructor public class FieldErrorDetail { - private String field; - private String reason; + private final String field; + private final String reason; }또는 record 사용을 고려해보세요 (Java 14+):
public record FieldErrorDetail(String field, String reason) { }recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1)
12-13: 금액 필드의 타입 개선 권장
price필드가int타입으로 선언되어 있는데, 금액을 다루는 필드의 경우 정밀도와 소수점 처리를 위해BigDecimal타입 사용을 권장합니다. 또한 유효성 검증을 위한 어노테이션 추가도 고려해보세요.+ import jakarta.validation.constraints.NotBlank; + import jakarta.validation.constraints.PositiveOrZero; + import java.math.BigDecimal; - private String name; - private int price; + @NotBlank(message = "메뉴 이름은 필수입니다") + private String name; + + @PositiveOrZero(message = "가격은 0 이상이어야 합니다") + private BigDecimal price;recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)
6-18: JPA 엔티티 개선 제안Store 엔티티에 다음 개선사항들을 고려해보세요:
- equals/hashCode 구현: JPA 엔티티에서는 연관관계 처리를 위해 필요합니다
- 유효성 검증: 필드들에 대한 검증 로직 추가
+ import jakarta.validation.constraints.NotBlank; + import java.util.Objects; @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Store { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NotBlank(message = "매장 이름은 필수입니다") private String name; + + @NotBlank(message = "카테고리는 필수입니다") private String category; + + @NotBlank(message = "위치는 필수입니다") private String location; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Store store = (Store) o; + return Objects.equals(id, store.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } }recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java (1)
13-14: 불필요한 빈 줄 제거파일 끝 부분에 불필요한 빈 줄이 있습니다. 코드 품질을 위해 제거해주세요.
List<Recruitment> findByUserId(Long userId); - -} +}recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (1)
20-22: 엔티티 직접 노출 대신 DTO 사용을 고려하세요.컨트롤러에서 JPA 엔티티를 직접 반환하는 것은 보안 및 유지보수 측면에서 권장되지 않습니다. DTO(Data Transfer Object) 패턴 사용을 고려해보세요.
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
8-29: 엔티티 구조는 양호하지만 검증 및 감사 기능 추가를 권장합니다.기본적인 JPA 엔티티 구조는 잘 설계되었습니다. 하지만 다음 개선사항들을 고려해보세요:
+import jakarta.validation.constraints.NotNull; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +@Table(indexes = { + @Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id", unique = true), + @Index(name = "idx_recruitment_joined_at", columnList = "recruitment_id, joined_at") +}) public class RecruitmentParticipant { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NotNull @ManyToOne @JoinColumn(name = "user_id", nullable = false) private User user; + @NotNull @ManyToOne @JoinColumn(name = "recruitment_id", nullable = false) private Recruitment recruitment; + @CreatedDate @Column(name = "joined_at", nullable = false) private LocalDateTime joinedAt; }recruitment-service/build.gradle (1)
26-38: 의존성 그룹화 및 순서 정리를 권장합니다.가독성 향상을 위해 의존성을 기능별로 그룹화하고 정렬하는 것을 권장합니다:
dependencies { + // Spring Boot Core implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' + + // API Documentation implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + + // Database + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'com.h2database:h2' + + // Development Tools compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' + + // Testing testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - testImplementation 'com.h2database:h2' }recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (2)
19-21: 불필요한 빈 줄을 제거해주세요.코드 가독성을 위해 불필요한 빈 줄들을 정리해주세요.
List<RecruitmentParticipant> findByRecruitmentId(Long recruitmentId); List<RecruitmentParticipant> findByUserId(Long userId); - - - }
9-18: 유용한 추가 쿼리 메서드를 고려해보세요.현재 쿼리 메서드들은 기본 요구사항을 잘 충족하지만, 다음과 같은 편의 메서드들을 추가하면 더 효율적인 API를 제공할 수 있습니다:
public interface RecruitmentParticipantRepository extends JpaRepository<RecruitmentParticipant, Long> { // 특정 유저가 특정 모집글에 이미 참여했는지 확인 Optional<RecruitmentParticipant> findByUserIdAndRecruitmentId(Long userId, Long recruitmentId); + + // 참여 여부만 확인하는 경우 더 효율적 + boolean existsByUserIdAndRecruitmentId(Long userId, Long recruitmentId); // 특정 모집글에 참여한 인원 수 카운트 long countByRecruitmentId(Long recruitmentId); List<RecruitmentParticipant> findByRecruitmentId(Long recruitmentId); List<RecruitmentParticipant> findByUserId(Long userId); + + // 최근 참여자 순으로 정렬 + List<RecruitmentParticipant> findByRecruitmentIdOrderByJoinedAtDesc(Long recruitmentId); }recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java (1)
28-30: 유효성 검사 실패 응답 개선 제안하드코딩된 한국어 메시지보다는 메시지 국제화(i18n)를 고려해보세요.
-return new ApiResponse<>(HttpStatus.BAD_REQUEST, false, 2001, "입력 값 유효성 검사 실패", null, errors); +return new ApiResponse<>(HttpStatus.BAD_REQUEST, false, 2001, "Validation failed", null, errors);또는 메시지를 매개변수로 받도록 오버로드 메서드를 추가하는 것을 고려해보세요.
recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)
15-16: ID 생성 전략 명시 권장
@GeneratedValue에 전략을 명시하는 것이 좋습니다.-@Id @GeneratedValue +@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (2)
15-16: ID 생성 전략 명시 권장다른 엔티티와의 일관성을 위해 생성 전략을 명시하는 것이 좋습니다.
-@Id @GeneratedValue +@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
34-36: 카테고리 필드 개선 제안카테고리도 Enum으로 관리하는 것을 고려해보세요. 또한 주석의 오타를 수정해야 합니다.
-//카테고리 추가가 +// 카테고리 추가 @Column(nullable = false) private String category;또는 Enum 사용:
-//카테고리 추가가 +// 카테고리 +@Enumerated(EnumType.STRING) @Column(nullable = false) -private String category; +private RecruitmentCategory category;recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
12-14: 생성자 추가 권장DTO의 편의성을 위해 기본 생성자와 전체 필드 생성자를 추가하는 것이 좋습니다.
@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor public class RecruitmentRequestDto {recruitment-service/.github/workflows/ci-cd-order-service.yml (3)
1-1: 워크플로우 이름을 더 구체적으로 수정하세요.현재 이름이 일반적인 "Spring Boot + MySQL with Docker Compose"로 되어 있어 order-service를 위한 워크플로우임을 명확히 하지 못합니다.
다음과 같이 수정하는 것을 권장합니다:
-name: CI/CD for Spring Boot + MySQL with Docker Compose +name: CI/CD for Order Service - Spring Boot + MySQL with Docker Compose
10-10: 환경변수 주석을 현재 서비스에 맞게 수정하세요.주석에서 "예: order-service"라고 되어 있는데, 실제로는 order-service를 설정하고 있어 혼란을 줄 수 있습니다.
주석을 다음과 같이 수정하세요:
- SERVICE_NAME: order-service # 예: order-service + SERVICE_NAME: order-service # 현재 서비스명- PROJECT_PATH: /home/ec2-user/delivery-system/order-service # 예: /home/ec2-user/delivery-system/order-service + PROJECT_PATH: /home/ec2-user/delivery-system/order-service # order-service 배포 경로Also applies to: 18-18
67-71: 배포 스크립트에 오류 처리 및 헬스체크 추가를 고려하세요.현재 배포 스크립트에는 오류 처리나 배포 후 헬스체크가 없어 배포 실패 시 감지하기 어렵습니다.
다음과 같이 개선할 수 있습니다:
script: | + set -e # 오류 발생 시 스크립트 중단 cd ${{ env.PROJECT_PATH }} sudo docker compose pull sudo docker compose down sudo docker compose up -d + # 서비스 헬스체크 (선택사항) + sleep 30 + sudo docker compose psrecruitment-service/.github/workflows/ci-cd-user-service.yml (1)
10-10: 환경변수 주석의 일관성 문제를 수정하세요.SERVICE_NAME은 user-service로 설정되어 있지만 주석에는 여전히 "예: order-service"라고 되어 있어 일관성이 없습니다.
- SERVICE_NAME: user-service # 예: order-service + SERVICE_NAME: user-service # 현재 서비스명- PROJECT_PATH: /home/ec2-user/delivery-system/user-service # 예: /home/ec2-user/delivery-system/order-service + PROJECT_PATH: /home/ec2-user/delivery-system/user-service # user-service 배포 경로Also applies to: 18-18
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)
16-22: CustomException 처리에도 로깅을 고려하세요.비즈니스 로직 예외도 모니터링을 위해 로깅하는 것이 좋습니다.
@ExceptionHandler(CustomException.class) public ResponseEntity<ApiResponse<Object>> handleCustomException(CustomException e) { ErrorCode ec = e.getErrorCode(); + logger.warn("비즈니스 예외 발생: 코드={}, 메시지={}", ec.getCode(), ec.getMessage()); return ResponseEntity .status(ec.getStatus()) .body(ApiResponse.fail(ec.getCode(), ec.getMessage(), ec.getStatus())); }recruitment-service/.github/workflows/ci-cd-store-service.yml (1)
10-10: 주석 언어 일관성 개선 필요영문 워크플로우 파일에 한글 주석이 혼재되어 있습니다. 협업과 유지보수를 위해 영문으로 통일하는 것이 좋습니다.
- SERVICE_NAME: store-service # 예: order-service + SERVICE_NAME: store-service # e.g., order-service- PROJECT_PATH: /home/ec2-user/delivery-system/store-service # 예: /home/ec2-user/delivery-system/order-service + PROJECT_PATH: /home/ec2-user/delivery-system/store-service # e.g., /home/ec2-user/delivery-system/order-serviceAlso applies to: 18-18
recruitment-service/.github/workflows/ci-cd-recruitment-service.yml (3)
35-35: YAML 포맷팅 오류 수정 필요정적 분석 도구가 감지한 trailing spaces를 제거해야 합니다.
- distribution: 'temurin' - + distribution: 'temurin' +- echo "${{ env.APPLICATION }}" > "${{ env.SERVICE_NAME }}/src/main/resources/application.properties" - + echo "${{ env.APPLICATION }}" > "${{ env.SERVICE_NAME }}/src/main/resources/application.properties" +Also applies to: 40-40
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 35-35: trailing spaces
(trailing-spaces)
37-37: YAML 콜론 앞 공백 수정 필요YAML 표준에 따라 콜론 앞의 불필요한 공백을 제거해야 합니다.
- run : | + run: |🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 37-37: too many spaces before colon
(colons)
10-10: 주석 언어 일관성 개선 필요다른 워크플로우 파일과 일관성을 위해 영문 주석으로 통일하는 것이 좋습니다.
- SERVICE_NAME: recruitment-service # 예: order-service + SERVICE_NAME: recruitment-service # e.g., order-service- PROJECT_PATH: /home/kmj02dev/delivery-system/recruitment-service # 예: /home/ec2-user/delivery-system/order-service + PROJECT_PATH: /home/kmj02dev/delivery-system/recruitment-service # e.g., /home/ec2-user/delivery-system/order-serviceAlso applies to: 18-18
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (2)
62-85: 메서드 명명 규칙 및 Swagger 문서화메서드 이름
getRecruitmentDetail은 일반적인 REST 컨벤션에 따라getRecruitment또는getById가 더 적절합니다.+ @Operation(summary = "모집글 상세 조회", description = "모집글의 상세 정보와 참여자 목록을 조회합니다.") @GetMapping("/{recruitmentId}") -public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) { + public ResponseEntity<RecruitmentDetailDto> getRecruitment(@PathVariable Long recruitmentId) {
87-90: 들여쓰기 일관성 문제메서드 선언의 들여쓰기가 일관되지 않습니다.
@GetMapping("/user/{userId}/created-recruitments") -public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) { + public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) { return recruitmentRepository.findByUserId(userId); -} + } -// 특정 유저가 참여한 모집글 조회 + // 특정 유저가 참여한 모집글 조회 @GetMapping("/user/{userId}/joined-recruitments") -public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) { + public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) {Also applies to: 93-99
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
recruitment-service/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (39)
.github/workflows/ci-cd-recruitment-service.yml(1 hunks)recruitment-service/.gitattributes(1 hunks)recruitment-service/.github/workflows/ci-cd-order-service.yml(1 hunks)recruitment-service/.github/workflows/ci-cd-payment-service.yml(1 hunks)recruitment-service/.github/workflows/ci-cd-recruitment-service.yml(1 hunks)recruitment-service/.github/workflows/ci-cd-store-service.yml(1 hunks)recruitment-service/.github/workflows/ci-cd-user-service.yml(1 hunks)recruitment-service/.gitignore(1 hunks)recruitment-service/build.gradle(1 hunks)recruitment-service/gradle/wrapper/gradle-wrapper.properties(1 hunks)recruitment-service/gradlew(1 hunks)recruitment-service/gradlew.bat(1 hunks)recruitment-service/settings.gradle(1 hunks)recruitment-service/src/main/java/com/example/recruitment/RecruitmentApplication.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/Order.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/Store.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/User.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/StoreRepository.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/UserRepository.java(1 hunks)recruitment-service/src/main/resources/application.properties(1 hunks)recruitment-service/src/test/java/com/example/recruitment/RecruitmentApplicationTests.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java (1)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
RestController(22-212)
recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (2)
recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java (1)
RestController(12-28)recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
RestController(22-212)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (1)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
Getter(12-33)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
Entity(9-37)recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
Entity(6-17)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (3)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
Getter(12-33)recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java (1)
Getter(6-11)recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java (1)
Getter(5-17)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (4)
recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
Entity(6-17)recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
Entity(8-29)recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)
Entity(6-18)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
Getter(12-33)
recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (2)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
Entity(9-37)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
Getter(12-33)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (3)
recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java (1)
RestController(12-28)recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (1)
RestController(12-28)recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java (1)
RestController(6-13)
🪛 YAMLlint (1.37.1)
recruitment-service/.github/workflows/ci-cd-recruitment-service.yml
[error] 35-35: trailing spaces
(trailing-spaces)
[warning] 37-37: too many spaces before colon
(colons)
[error] 40-40: trailing spaces
(trailing-spaces)
🔇 Additional comments (21)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (2)
1-6: 패키지 및 import 구성이 적절합니다.
org.springframework.http.HttpStatus를 사용해 HTTP 상태를 명시한 점이 좋습니다.
7-9: Lombok 어노테이션 사용으로 코드가 간결해졌습니다.
@Getter,@AllArgsConstructor를 활용해 getter 생성 및 생성자 정의를 자동화한 부분이 적절합니다.recruitment-service/.gitattributes (1)
1-3: 라인 엔딩 설정 적절합니다.
스크립트와 배치 파일, 바이너리 파일에 대한 EOL 강제화 규칙이 잘 구성되어 있습니다.recruitment-service/gradle/wrapper/gradle-wrapper.properties (1)
1-7: Gradle Wrapper 설정이 표준에 맞습니다.
버전(8.13), 바이너리 분배, 네트워크 타임아웃 등이 적절하게 지정되어 있어 일관된 빌드를 보장합니다.recruitment-service/src/main/java/com/example/recruitment/repository/UserRepository.java (1)
1-7: 표준 JPA 리포지토리 설정
UserRepository가JpaRepository<User, Long>를 올바르게 확장하여 기본 CRUD 및 페이징 기능을 제공합니다. Spring Boot의 컴포넌트 스캔(com.example.recruitment패키지 기준)이 이 인터페이스를 자동 감지하므로 별도의 애노테이션이 필요 없습니다.recruitment-service/src/main/java/com/example/recruitment/repository/StoreRepository.java (1)
1-7: Store 엔티티용 JPA 리포지토리
StoreRepository가JpaRepository<Store, Long>를 확장해 생성, 조회, 수정, 삭제 및 페이징 API를 제공하며, 애노테이션 없이도 Spring Data JPA가 자동 구성합니다.recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java (1)
1-7: Order 엔티티용 JPA 리포지토리
OrderRepository가JpaRepository<Order, Long>를 상속하여 주문 데이터를 위한 표준 CRUD 및 페이징 메서드를 제공합니다. 패키지 구조 및 스캔 범위가 올바르게 설정되어 있습니다.recruitment-service/src/main/java/com/example/recruitment/RecruitmentApplication.java (1)
1-13: Spring Boot 진입점 설정
@SpringBootApplication이 적용된RecruitmentApplication클래스가 올바르게 구성되어 있습니다.SpringApplication.run()을 통해 애플리케이션이 정상적으로 부팅되며, 패키지 스캔 경로도com.example.recruitment로 적절히 설정되어 있습니다.recruitment-service/src/main/resources/application.properties (1)
7-9:Details
❓ Verification inconclusive
JPA 설정 검토 권장
개발 환경에서는 적절하지만, 프로덕션 환경에서는 다음 사항을 고려해야 합니다:
ddl-auto=update는 프로덕션에서 데이터 손실 위험이 있습니다show-sql=true는 성능에 영향을 줄 수 있습니다환경별 프로파일 설정을 확인하기 위한 스크립트:
🏁 Script executed:
#!/bin/bash # Description: 환경별 application 설정 파일 확인 # Test: application-{profile}.properties 파일 존재 여부 확인 fd "application-.*\.properties" --type fLength of output: 42
프로덕션 환경 JPA 설정 검토 및 프로파일 분리 확인 필요
application.properties( Lines 7–9 )에 아래와 같이 설정되어 있습니다:
- spring.jpa.hibernate.ddl-auto=update → 프로덕션에서 데이터 손실 위험
- spring.jpa.show-sql=true → 성능 저하 우려
현재
application-*.properties파일이 확인되지 않아 별도 프로덕션 프로파일 설정 유무를 알 수 없습니다.
프로덕션용 설정을 분리하고 다음을 적용해 주세요:
spring.jpa.hibernate.ddl-auto를none또는validate로 변경spring.jpa.show-sql을false로 설정• 프로파일별 설정 파일 존재 여부 및 CI/CD 환경변수 관리 방식 검증
• 프로덕션 배포 전 위 변경사항이 반영되었는지 확인recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java (1)
5-17:Details
✅ Verification successful
커스텀 예외 클래스 구현이 적절함
표준적인 커스텀 예외 패턴을 잘 따르고 있으며, ErrorCode enum을 활용한 구조화된 에러 처리가 좋습니다.
ErrorCode enum의 존재를 확인하고, 선택적으로 예외 체이닝 지원을 고려해보세요:
예외 체이닝 지원을 위한 선택적 개선:
public class CustomException extends RuntimeException { private final ErrorCode errorCode; public CustomException(ErrorCode errorCode) { this.errorCode = errorCode; } + public CustomException(ErrorCode errorCode, Throwable cause) { + super(cause); + this.errorCode = errorCode; + } @Override public String getMessage() { return errorCode.getMessage(); } }
🏁 Script executed:
#!/bin/bash # Description: ErrorCode enum 클래스 존재 여부 확인 # Test: ErrorCode.java 파일이 존재하는지 확인 fd "ErrorCode.java" --type fLength of output: 111
CustomException 클래스: ErrorCode enum 확인 및 예외 체이닝 지원 제안
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java파일이 존재함을 확인했습니다.- CustomException 구현은 표준 패턴을 잘 따르고 있으며, ErrorCode enum을 활용한 구조화된 예외 처리가 적절합니다.
- 예외 체이닝을 지원하려면 선택적으로 아래 생성자를 추가할 수 있습니다.
제안되는 변경사항 (선택 사항):
public class CustomException extends RuntimeException { private final ErrorCode errorCode; public CustomException(ErrorCode errorCode) { this.errorCode = errorCode; } + public CustomException(ErrorCode errorCode, Throwable cause) { + super(cause); + this.errorCode = errorCode; + } @Override public String getMessage() { return errorCode.getMessage(); } }recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1)
6-10: 엔티티 구조 승인@embeddable 어노테이션과 Lombok 어노테이션들이 적절히 사용되었습니다. 전반적인 엔티티 구조가 깔끔하고 용도에 맞게 설계되었습니다.
recruitment-service/.gitignore (1)
1-37: 표준적인 .gitignore 설정 승인Spring Boot 프로젝트에 적합한 .gitignore 파일입니다. 빌드 아티팩트, IDE 관련 파일들이 적절히 제외되어 있고, 주요 IDE들(STS, IntelliJ IDEA, NetBeans, VS Code)에 대한 설정이 포함되어 있습니다.
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java (1)
9-11: 리포지토리 인터페이스 승인Spring Data JPA 규칙에 맞는 적절한 쿼리 메서드들이 정의되어 있습니다.
findByStatus와findByUserId메서드는 모집글 조회 기능에 필요한 핵심 기능들을 제공합니다.recruitment-service/gradlew.bat (1)
1-95: 표준 Gradle wrapper 스크립트입니다.이 파일은 Gradle에서 제공하는 표준 Windows 배치 스크립트로, 크로스 플랫폼 빌드 일관성을 보장합니다. 코드 구조와 로직이 표준 Gradle 배포본과 일치하며, 적절한 라이선스 헤더도 포함되어 있습니다.
recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java (2)
8-15: API 응답 구조가 잘 설계되었습니다.제네릭 record 클래스를 사용하여 일관된 API 응답 형식을 제공하는 좋은 접근 방식입니다.
@JsonIgnore를 통해 HTTP 상태코드를 JSON 응답에서 제외하는 것도 적절합니다.
16-22: 성공 응답 팩토리 메서드가 잘 구현되었습니다.
ok와created메서드가 각각 HTTP 200과 201 상태코드에 대응하며, 고유한 코드(1000, 1001)를 사용하는 것이 좋습니다.recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)
24-26:Details
✅ Verification successful
MenuItem 엔티티 정의 확인 필요
@ElementCollection을 사용한MenuItem이 임베디드 타입인지 확인이 필요합니다. 만약MenuItem이 별도의 엔티티라면@OneToMany관계를 고려해야 합니다.다음 스크립트로 MenuItem 클래스 정의를 확인해주세요:
🏁 Script executed:
#!/bin/bash # MenuItem 클래스가 @Embeddable인지 @Entity인지 확인 ast-grep --pattern 'class MenuItem { $$$ }'Length of output: 47
다음 스크립트로
MenuItem클래스 파일 위치와 어노테이션 여부를 확인해주세요:
🏁 Script executed:
#!/bin/bash # 1. MenuItem 클래스가 정의된 파일 경로 확인 rg -l 'class\s\+MenuItem' -R src/main/java # 2. 클래스 선언 전후 3줄씩 출력하여 @Entity 또는 @Embeddable 어노테이션 확인 rg -n -C3 'class\s\+MenuItem' -R src/main/javaLength of output: 903
다음 스크립트로
MenuItem.java파일 위치와 클래스 선언부 전후에 어노테이션이 있는지 재확인 부탁드립니다:
🏁 Script executed:
#!/bin/bash # 1. MenuItem.java 파일 경로 확인 find src/main/java -type f -name '*MenuItem.java' # 2. 클래스 선언부 전후 3줄씩 출력하여 @Entity 또는 @Embeddable 어노테이션 확인 grep -R -n -C3 'class MenuItem' src/main/javaLength of output: 195
다음 스크립트로
MenuItem.java파일 위치와 클래스 선언부 전후의 어노테이션 여부를 한번에 확인해주세요:#!/bin/bash # 1. MenuItem.java 파일 위치 찾기 find . -type f -name 'MenuItem.java' # 2. 해당 파일에서 @Entity, @Embeddable 어노테이션과 class 선언부 함께 출력 FILE=$(find . -type f -name 'MenuItem.java') grep -n -C3 '@Entity\|@Embeddable\|class MenuItem' "$FILE"
다음 스크립트로
recruitment-service모듈 내에서MenuItem.java위치와 어노테이션 여부를 확인해주세요:#!/bin/bash # 1. recruitment-service 모듈 내 MenuItem.java 파일 찾기 FILE=$(find recruitment-service -type f -name 'MenuItem.java') echo "Found: $FILE" # 2. 클래스 선언부 및 @Entity, @Embeddable 어노테이션 전후 3줄씩 출력 grep -n -C3 '@Entity\|@Embeddable\|class MenuItem' "$FILE"
아래 스크립트로 전체 코드베이스에서
MenuItem정의와@Entity/@Embeddable어노테이션 적용 여부를 다시 한번 확인해주세요:
🏁 Script executed:
#!/bin/bash # 1. 전체 코드베이스에서 MenuItem 클래스 선언 탐색 grep -R -n 'class MenuItem' . # 2. 전체 코드베이스에서 @Entity 어노테이션 탐색 grep -R -n '@Entity' . # 3. 전체 코드베이스에서 @Embeddable 어노테이션 탐색 grep -R -n '@Embeddable' .Length of output: 746
검증 완료:
MenuItem이@Embeddable로 정의되어@ElementCollection사용이 적합합니다
recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java파일의 6행에@Embeddable어노테이션이 적용되어 있습니다.- 따라서 별도의
@Entity가 아니므로@OneToMany로 변경할 필요가 없습니다.recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)
13-14: 글로벌 예외 핸들러 구현이 잘 되어 있습니다.
@RestControllerAdvice를 사용한 중앙집중식 예외 처리 구조가 잘 설계되어 있고, 다양한 예외 타입에 대한 적절한 처리가 구현되어 있습니다.recruitment-service/.github/workflows/ci-cd-store-service.yml (1)
1-72: 워크플로우 구성이 적절합니다.CI/CD 파이프라인이 Spring Boot 애플리케이션의 빌드, Docker 이미지 생성 및 EC2 배포를 위해 잘 구성되어 있습니다.
recruitment-service/gradlew (1)
1-252: 표준 Gradle Wrapper 스크립트이는 Gradle에서 제공하는 표준 wrapper 스크립트입니다. 프로젝트의 빌드 환경을 일관되게 유지하는 데 필요한 파일입니다.
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
22-25:⚠️ Potential issueSwagger 문서화 어노테이션 누락
PR 목적이 "Swagger 설정 및 모집글 API 문서화 완료"인데, 컨트롤러에 Swagger/OpenAPI 관련 어노테이션이 없습니다. API 문서화를 위해
@Operation,@ApiResponse등의 어노테이션을 추가해야 합니다.예시:
@Tag(name = "모집글 API", description = "모집글 관련 CRUD 및 참여 기능") @RestController @RequiredArgsConstructor @RequestMapping("/api/recruitments") public class RecruitmentController {Likely an incorrect or invalid review comment.
| package com.example.recruitment; | ||
|
|
||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RestController; |
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
패키지 구조 일관성 개선 필요
다른 컨트롤러들이 com.example.recruitment.controller 패키지에 위치하는 반면, 이 컨트롤러는 루트 패키지에 있어 일관성이 떨어집니다.
-package com.example.recruitment;
+package com.example.recruitment.controller;📝 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.
| package com.example.recruitment; | |
| import org.springframework.web.bind.annotation.GetMapping; | |
| import org.springframework.web.bind.annotation.RestController; | |
| package com.example.recruitment.controller; | |
| import org.springframework.web.bind.annotation.GetMapping; | |
| import org.springframework.web.bind.annotation.RestController; |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
at the top of the file, the package declaration places this controller in the
root package com.example.recruitment, which is inconsistent with other
controllers located in com.example.recruitment.controller. To fix this, change
the package declaration to com.example.recruitment.controller and move the file
into the corresponding directory structure to maintain package consistency.
| @GetMapping("/") | ||
| public String hello() { | ||
| return "서버 정상 작동 중!"; | ||
| } |
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
엔드포인트 경로 및 Swagger 문서화 개선 필요
현재 루트 경로 "/"를 사용하고 있어 다른 엔드포인트와 충돌할 수 있습니다. 또한 PR 목표가 Swagger 문서화인데 OpenAPI 어노테이션이 없습니다.
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+@Tag(name = "Health Check", description = "서버 상태 확인 API")
+@RestController
+public class HealthCheckController {
- @GetMapping("/")
+ @GetMapping("/health")
+ @Operation(summary = "서버 상태 확인", description = "서버가 정상적으로 작동 중인지 확인합니다.")
public String hello() {
return "서버 정상 작동 중!";
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
around lines 9 to 12, the endpoint uses the root path "/" which may conflict
with other endpoints, and it lacks OpenAPI annotations for Swagger
documentation. Change the endpoint path to a more specific one to avoid
conflicts, and add appropriate OpenAPI annotations such as @Operation and
@ApiResponse to document the endpoint for Swagger.
| @RestController | ||
| public class RecruitmentControllerTest { |
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
클래스명 변경 필요
RecruitmentControllerTest라는 이름은 테스트 클래스로 오해될 수 있습니다. 이는 실제 프로덕션 컨트롤러이므로 더 명확한 이름을 사용해야 합니다.
-public class RecruitmentControllerTest {
+public class HealthCheckController {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
around lines 6 to 7, the class name RecruitmentControllerTest is misleading as
it suggests a test class. Rename the class to RecruitmentController to clearly
indicate it is a production controller and avoid confusion.
| EC2_SSH_USER: kmj02dev | ||
| PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }} | ||
| TIMEZONE: Asia/Seoul | ||
| DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| DOCKER_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | ||
| PROJECT_PATH: /home/ec2-user/delivery-system/recruitment-service # 예: /home/ec2-user/delivery-system/order-service |
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.
EC2_SSH_USER와 PROJECT_PATH 간 불일치
EC2_SSH_USER를 kmj02dev로 변경했으나, PROJECT_PATH에 /home/ec2-user/...가 남아 있습니다. SSH 사용자와 디렉토리 경로가 다르면 배포 실패로 이어지니, 아래처럼 수정하세요.
- EC2_SSH_USER: kmj02dev
+ EC2_SSH_USER: kmj02dev
- PROJECT_PATH: /home/ec2-user/delivery-system/recruitment-service
+ PROJECT_PATH: /home/${{ env.EC2_SSH_USER }}/delivery-system/recruitment-service🤖 Prompt for AI Agents
In .github/workflows/ci-cd-recruitment-service.yml around lines 13 to 18, the
EC2_SSH_USER is set to kmj02dev but the PROJECT_PATH still uses /home/ec2-user,
causing a mismatch. Update the PROJECT_PATH to use
/home/kmj02dev/delivery-system/recruitment-service to match the SSH user and
avoid deployment failures.
| spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul | ||
| spring.datasource.username=root | ||
| spring.datasource.password=040320 |
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.
보안 취약점: 데이터베이스 자격 증명이 평문으로 노출됨
데이터베이스 연결 정보가 하드코딩되어 있어 보안상 매우 위험합니다. 특히 패스워드가 평문으로 저장되어 있습니다.
다음과 같이 환경 변수나 외부 설정을 사용하도록 수정하세요:
-spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul
-spring.datasource.username=root
-spring.datasource.password=040320
+spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul}
+spring.datasource.username=${DB_USERNAME:root}
+spring.datasource.password=${DB_PASSWORD}또는 application-{profile}.properties 파일을 사용하여 환경별로 설정을 분리하세요.
📝 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.
| spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul | |
| spring.datasource.username=root | |
| spring.datasource.password=040320 | |
| spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul} | |
| spring.datasource.username=${DB_USERNAME:root} | |
| spring.datasource.password=${DB_PASSWORD} |
🤖 Prompt for AI Agents
In recruitment-service/src/main/resources/application.properties lines 2 to 4,
the database credentials are hardcoded in plain text, posing a security risk.
Remove the plain text password and username from this file and instead configure
them to be loaded from environment variables or external configuration sources.
Alternatively, use profile-specific properties files like
application-{profile}.properties to separate sensitive settings per environment,
ensuring credentials are not exposed in the codebase.
| // 모집글 생성 | ||
| @PostMapping | ||
| public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | ||
| User user = userRepository.findById(dto.getUserId()).orElseThrow(); | ||
| Store store = storeRepository.findById(dto.getStoreId()).orElseThrow(); | ||
|
|
||
| Recruitment recruitment = new Recruitment(); | ||
| recruitment.setUser(user); | ||
| recruitment.setStore(store); | ||
| recruitment.setTitle(dto.getTitle()); | ||
| recruitment.setDescription(dto.getDescription()); | ||
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | ||
| recruitment.setStatus("RECRUITING"); | ||
|
|
||
| return recruitmentRepository.save(recruitment); | ||
| } | ||
|
|
||
| // 모집글 전체 조회 | ||
| @GetMapping | ||
| public List<Recruitment> getAll() { | ||
| return recruitmentRepository.findAll(); | ||
| } | ||
|
|
||
| // 상태별 조회 (ex. /api/recruitments?status=RECRUITING) | ||
| @GetMapping(params = "status") | ||
| public List<Recruitment> getByStatus(@RequestParam String status) { | ||
| return recruitmentRepository.findByStatus(status); | ||
| } | ||
|
|
||
| @GetMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| // 참여자 목록 조회 | ||
| List<RecruitmentParticipant> participantEntities = | ||
| participantRepository.findByRecruitmentId(recruitmentId); | ||
|
|
||
| List<User> participants = participantEntities.stream() | ||
| .map(RecruitmentParticipant::getUser) | ||
| .toList(); | ||
|
|
||
| // DTO 구성 | ||
| RecruitmentDetailDto dto = new RecruitmentDetailDto(); | ||
| dto.setId(recruitment.getId()); | ||
| dto.setTitle(recruitment.getTitle()); | ||
| dto.setDescription(recruitment.getDescription()); | ||
| dto.setStatus(recruitment.getStatus()); | ||
| dto.setDeadlineTime(recruitment.getDeadlineTime()); | ||
| dto.setUser(recruitment.getUser()); | ||
| dto.setStore(recruitment.getStore()); | ||
| dto.setParticipants(participants); | ||
|
|
||
| return ResponseEntity.ok(dto); | ||
| } | ||
|
|
||
| @GetMapping("/user/{userId}/created-recruitments") | ||
| public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) { | ||
| return recruitmentRepository.findByUserId(userId); | ||
| } | ||
|
|
||
| // 특정 유저가 참여한 모집글 조회 | ||
| @GetMapping("/user/{userId}/joined-recruitments") | ||
| public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) { | ||
| List<RecruitmentParticipant> participantList = participantRepository.findByUserId(userId); | ||
| return participantList.stream() | ||
| .map(RecruitmentParticipant::getRecruitment) | ||
| .toList(); | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
| // 모집글 참여 | ||
| @PostMapping("/{recruitmentId}/join") | ||
| public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) { | ||
| User user = userRepository.findById(userId).orElseThrow(); | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| boolean alreadyJoined = participantRepository | ||
| .findByUserIdAndRecruitmentId(userId, recruitmentId) | ||
| .isPresent(); | ||
|
|
||
| if (alreadyJoined) { | ||
| return ResponseEntity.badRequest().body("이미 참여한 모집입니다."); | ||
| } | ||
|
|
||
| RecruitmentParticipant participant = new RecruitmentParticipant(); | ||
| participant.setUser(user); | ||
| participant.setRecruitment(recruitment); | ||
| participant.setJoinedAt(LocalDateTime.now()); | ||
|
|
||
| participantRepository.save(participant); | ||
| return ResponseEntity.ok("모집글 참여 완료"); | ||
| } | ||
|
|
||
| // 모집 상태 업데이트 (CONFIRMED 또는 FAILED) | ||
| @PatchMapping("/{recruitmentId}/status") | ||
| public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| LocalDateTime now = LocalDateTime.now(); | ||
|
|
||
| long participantCount = participantRepository.countByRecruitmentId(recruitmentId); | ||
|
|
||
| if (now.isAfter(recruitment.getDeadlineTime())) { | ||
| if (participantCount >= 3) { | ||
| recruitment.setStatus("CONFIRMED"); | ||
| } else { | ||
| recruitment.setStatus("FAILED"); | ||
| } | ||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다."); | ||
| } else { | ||
| return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다."); | ||
| } | ||
| } | ||
|
|
||
| // 주문 수락 상태로 변경 (ACCEPTED) | ||
| @PatchMapping("/{recruitmentId}/accept") | ||
| public ResponseEntity<?> acceptRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| if (!"CONFIRMED".equals(recruitment.getStatus())) { | ||
| return ResponseEntity.badRequest().body("주문 수락은 CONFIRMED 상태에서만 가능합니다."); | ||
| } | ||
|
|
||
| recruitment.setStatus("ACCEPTED"); | ||
| recruitmentRepository.save(recruitment); | ||
|
|
||
| return ResponseEntity.ok("상태가 ACCEPTED로 변경되었습니다."); | ||
| } | ||
|
|
||
| // 배달 완료 상태로 변경 (DELIVERED) | ||
| @PatchMapping("/{recruitmentId}/deliver") | ||
| public ResponseEntity<?> completeDelivery(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| if (!"ACCEPTED".equals(recruitment.getStatus())) { | ||
| return ResponseEntity.badRequest().body("배달 완료는 ACCEPTED 상태에서만 가능합니다."); | ||
| } | ||
|
|
||
| recruitment.setStatus("DELIVERED"); | ||
| recruitmentRepository.save(recruitment); | ||
|
|
||
| return ResponseEntity.ok("상태가 DELIVERED로 변경되었습니다."); | ||
| } | ||
| // 모집글 삭제 | ||
| @DeleteMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> deleteRecruitment(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| recruitmentRepository.delete(recruitment); | ||
| return ResponseEntity.ok("모집글이 삭제되었습니다."); | ||
| } | ||
|
|
||
| // 모집글 수정 | ||
| @PutMapping("/{recruitmentId}") | ||
| public ResponseEntity<?> updateRecruitment(@PathVariable Long recruitmentId, | ||
| @Valid @RequestBody RecruitmentRequestDto dto) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| // (선택) 본인 작성한 글인지 확인 | ||
| if (!recruitment.getUser().getId().equals(dto.getUserId())) { | ||
| return ResponseEntity.status(403).body("작성자만 수정할 수 있습니다."); | ||
| } | ||
|
|
||
| // 수정할 항목들 업데이트 | ||
| recruitment.setTitle(dto.getTitle()); | ||
| recruitment.setDescription(dto.getDescription()); | ||
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | ||
|
|
||
| // (선택) 가게 변경도 허용 | ||
| if (dto.getStoreId() != null) { | ||
| Store store = storeRepository.findById(dto.getStoreId()).orElseThrow(); | ||
| recruitment.setStore(store); | ||
| } | ||
|
|
||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("모집글이 수정되었습니다."); | ||
| } | ||
|
|
||
| } |
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
트랜잭션 관리 누락
컨트롤러의 여러 메서드에서 다중 데이터베이스 작업을 수행하지만 @Transactional 어노테이션이 없습니다. 데이터 일관성을 위해 서비스 레이어로 비즈니스 로직을 분리하고 트랜잭션을 적용하는 것이 좋습니다.
서비스 레이어 도입을 권장합니다:
@Service
@Transactional
public class RecruitmentService {
// 비즈니스 로직을 서비스 레이어로 이동
}🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
from lines 32 to 212, the controller methods perform multiple database
operations without transaction management, risking data inconsistency. Refactor
by creating a RecruitmentService class annotated with @Service and
@Transactional, move all business logic from the controller into this service
layer, and update the controller methods to call the service methods. This
ensures proper transaction management and separation of concerns.
| @PostMapping("/{recruitmentId}/join") | ||
| public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) { | ||
| User user = userRepository.findById(userId).orElseThrow(); | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| boolean alreadyJoined = participantRepository | ||
| .findByUserIdAndRecruitmentId(userId, recruitmentId) | ||
| .isPresent(); | ||
|
|
||
| if (alreadyJoined) { | ||
| return ResponseEntity.badRequest().body("이미 참여한 모집입니다."); | ||
| } | ||
|
|
||
| RecruitmentParticipant participant = new RecruitmentParticipant(); | ||
| participant.setUser(user); | ||
| participant.setRecruitment(recruitment); | ||
| participant.setJoinedAt(LocalDateTime.now()); | ||
|
|
||
| participantRepository.save(participant); | ||
| return ResponseEntity.ok("모집글 참여 완료"); | ||
| } |
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.
보안 취약점: 사용자 인증 부재
현재 userId를 요청 파라미터로 받고 있어 누구나 다른 사용자로 위장하여 모집글에 참여할 수 있습니다. Spring Security를 통한 인증된 사용자 정보를 사용해야 합니다.
+ @Operation(summary = "모집글 참여", description = "인증된 사용자가 모집글에 참여합니다.")
@PostMapping("/{recruitmentId}/join")
- public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) {
+ public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
+ @AuthenticationPrincipal UserDetails userDetails) {
+ Long userId = getCurrentUserId(userDetails);
User user = userRepository.findById(userId).orElseThrow();Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 105 to 125, the joinRecruitment method accepts userId as a request
parameter, which allows impersonation and is a security risk. Remove the userId
parameter and instead obtain the authenticated user's identity from Spring
Security's SecurityContext. Use this authenticated user information to fetch the
User entity and proceed with the participation logic, ensuring only the
logged-in user can join recruitments on their own behalf.
| @GetMapping | ||
| public List<Recruitment> getAll() { | ||
| return recruitmentRepository.findAll(); | ||
| } | ||
|
|
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
페이징 처리 추가 권장
전체 모집글을 조회할 때 데이터가 많아질 경우 성능 문제가 발생할 수 있습니다. 페이징 처리를 추가하는 것을 권장합니다.
+ @Operation(summary = "모집글 목록 조회", description = "모집글 목록을 페이징하여 조회합니다.")
@GetMapping
- public List<Recruitment> getAll() {
- return recruitmentRepository.findAll();
+ public Page<Recruitment> getAll(@PageableDefault(size = 20) Pageable pageable) {
+ return recruitmentRepository.findAll(pageable);
}📝 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.
| @GetMapping | |
| public List<Recruitment> getAll() { | |
| return recruitmentRepository.findAll(); | |
| } | |
| @Operation(summary = "모집글 목록 조회", description = "모집글 목록을 페이징하여 조회합니다.") | |
| @GetMapping | |
| public Page<Recruitment> getAll(@PageableDefault(size = 20) Pageable pageable) { | |
| return recruitmentRepository.findAll(pageable); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 50 to 54, the getAll method returns all recruitment entries without
pagination, which can cause performance issues with large data sets. Modify the
method to accept pagination parameters (such as page number and size) and use
the repository's paging capabilities to return a paged list of recruitment
entries instead of all at once.
| @PostMapping | ||
| public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | ||
| User user = userRepository.findById(dto.getUserId()).orElseThrow(); | ||
| Store store = storeRepository.findById(dto.getStoreId()).orElseThrow(); | ||
|
|
||
| Recruitment recruitment = new Recruitment(); | ||
| recruitment.setUser(user); | ||
| recruitment.setStore(store); | ||
| recruitment.setTitle(dto.getTitle()); | ||
| recruitment.setDescription(dto.getDescription()); | ||
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | ||
| recruitment.setStatus("RECRUITING"); | ||
|
|
||
| return recruitmentRepository.save(recruitment); | ||
| } |
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.
예외 처리 및 Swagger 문서화 개선 필요
orElseThrow()가 구체적인 예외 메시지 없이 사용되고 있습니다.- Swagger 문서화를 위한 어노테이션이 누락되었습니다.
+ @Operation(summary = "모집글 생성", description = "새로운 모집글을 생성합니다.")
+ @ApiResponse(responseCode = "200", description = "모집글 생성 성공")
+ @ApiResponse(responseCode = "404", description = "사용자 또는 가게를 찾을 수 없음")
@PostMapping
public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
- User user = userRepository.findById(dto.getUserId()).orElseThrow();
- Store store = storeRepository.findById(dto.getStoreId()).orElseThrow();
+ User user = userRepository.findById(dto.getUserId())
+ .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
+ Store store = storeRepository.findById(dto.getStoreId())
+ .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));📝 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.
| @PostMapping | |
| public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | |
| User user = userRepository.findById(dto.getUserId()).orElseThrow(); | |
| Store store = storeRepository.findById(dto.getStoreId()).orElseThrow(); | |
| Recruitment recruitment = new Recruitment(); | |
| recruitment.setUser(user); | |
| recruitment.setStore(store); | |
| recruitment.setTitle(dto.getTitle()); | |
| recruitment.setDescription(dto.getDescription()); | |
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | |
| recruitment.setStatus("RECRUITING"); | |
| return recruitmentRepository.save(recruitment); | |
| } | |
| @Operation(summary = "모집글 생성", description = "새로운 모집글을 생성합니다.") | |
| @ApiResponse(responseCode = "200", description = "모집글 생성 성공") | |
| @ApiResponse(responseCode = "404", description = "사용자 또는 가게를 찾을 수 없음") | |
| @PostMapping | |
| public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | |
| User user = userRepository.findById(dto.getUserId()) | |
| .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); | |
| Store store = storeRepository.findById(dto.getStoreId()) | |
| .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND)); | |
| Recruitment recruitment = new Recruitment(); | |
| recruitment.setUser(user); | |
| recruitment.setStore(store); | |
| recruitment.setTitle(dto.getTitle()); | |
| recruitment.setDescription(dto.getDescription()); | |
| recruitment.setDeadlineTime(dto.getDeadlineTime()); | |
| recruitment.setStatus("RECRUITING"); | |
| return recruitmentRepository.save(recruitment); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 33 to 47, the calls to orElseThrow() lack specific exception
messages, which reduces clarity when an entity is not found. Replace
orElseThrow() with versions that throw exceptions including descriptive messages
for missing User or Store. Additionally, add Swagger annotations such as
@ApiOperation and @ApiResponses to document the createRecruitment endpoint for
better API documentation.
| @PatchMapping("/{recruitmentId}/status") | ||
| public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
| LocalDateTime now = LocalDateTime.now(); | ||
|
|
||
| long participantCount = participantRepository.countByRecruitmentId(recruitmentId); | ||
|
|
||
| if (now.isAfter(recruitment.getDeadlineTime())) { | ||
| if (participantCount >= 3) { | ||
| recruitment.setStatus("CONFIRMED"); | ||
| } else { | ||
| recruitment.setStatus("FAILED"); | ||
| } | ||
| recruitmentRepository.save(recruitment); | ||
| return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다."); | ||
| } else { | ||
| return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다."); | ||
| } | ||
| } |
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
매직 넘버 사용 및 비즈니스 로직 외부화 필요
최소 참여자 수(3명)가 하드코딩되어 있습니다. 설정 가능한 값으로 변경하거나 상수로 정의하는 것이 좋습니다.
+ private static final int MINIMUM_PARTICIPANTS = 3;
+
@PatchMapping("/{recruitmentId}/status")
public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();
LocalDateTime now = LocalDateTime.now();
long participantCount = participantRepository.countByRecruitmentId(recruitmentId);
if (now.isAfter(recruitment.getDeadlineTime())) {
- if (participantCount >= 3) {
+ if (participantCount >= MINIMUM_PARTICIPANTS) {
recruitment.setStatus("CONFIRMED");
} else {📝 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.
| @PatchMapping("/{recruitmentId}/status") | |
| public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | |
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | |
| LocalDateTime now = LocalDateTime.now(); | |
| long participantCount = participantRepository.countByRecruitmentId(recruitmentId); | |
| if (now.isAfter(recruitment.getDeadlineTime())) { | |
| if (participantCount >= 3) { | |
| recruitment.setStatus("CONFIRMED"); | |
| } else { | |
| recruitment.setStatus("FAILED"); | |
| } | |
| recruitmentRepository.save(recruitment); | |
| return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다."); | |
| } else { | |
| return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다."); | |
| } | |
| } | |
| // add this near the top of the class | |
| private static final int MINIMUM_PARTICIPANTS = 3; | |
| @PatchMapping("/{recruitmentId}/status") | |
| public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) { | |
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | |
| LocalDateTime now = LocalDateTime.now(); | |
| long participantCount = participantRepository.countByRecruitmentId(recruitmentId); | |
| if (now.isAfter(recruitment.getDeadlineTime())) { | |
| if (participantCount >= MINIMUM_PARTICIPANTS) { | |
| recruitment.setStatus("CONFIRMED"); | |
| } else { | |
| recruitment.setStatus("FAILED"); | |
| } | |
| recruitmentRepository.save(recruitment); | |
| return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다."); | |
| } else { | |
| return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다."); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 128 to 146, the minimum participant count of 3 is hardcoded as a
magic number. Define this value as a constant at the class level or load it from
a configuration file to make it configurable and improve code readability.
Replace the hardcoded '3' with the defined constant or configuration property in
the participant count comparison.
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: 18
♻️ Duplicate comments (11)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (8)
19-23: 외래키 제약조건 추가 필요데이터 무결성을 위해
@JoinColumn설정을 추가해야 합니다.-@ManyToOne +@ManyToOne +@JoinColumn(name = "user_id", nullable = false) private User user; -@ManyToOne +@ManyToOne +@JoinColumn(name = "store_id", nullable = false) private Store store;
28-29: 필수 필드에 제약조건 추가 필요제목과 설명은 필수 필드이므로 제약조건을 추가해야 합니다.
+@Column(nullable = false, length = 100) +@NotBlank(message = "제목은 필수입니다.") private String title; +@Column(nullable = false, length = 1000) +@NotBlank(message = "설명은 필수입니다.") private String description;
31-31: 상태 필드를 Enum으로 변경 권장문자열 대신 Enum을 사용하여 타입 안전성을 확보하는 것이 좋습니다.
-private String status; // RECRUITING, CONFIRMED 등 +@Enumerated(EnumType.STRING) +@Column(nullable = false) +private RecruitmentStatus status; // RECRUITING, CONFIRMED 등
33-33: 마감시간 제약조건 추가 필요마감시간은 현재 시간보다 미래여야 하므로 유효성 검사를 추가해야 합니다.
+@Column(nullable = false) +@Future(message = "마감시간은 현재 시간보다 미래여야 합니다.") private LocalDateTime deadlineTime;
19-23: 외래키 제약조건 추가 필요이전 리뷰에서 지적된 사항이 아직 적용되지 않았습니다. 데이터 무결성을 위해
@JoinColumn설정을 추가해야 합니다.
28-29: 필수 필드에 제약조건 추가 필요이전 리뷰에서 권장된 제목과 설명 필드의 validation 어노테이션이 아직 적용되지 않았습니다.
31-31: 상태 필드를 Enum으로 변경 권장이전 리뷰에서 제안된 상태 필드의 Enum 변경이 아직 적용되지 않았습니다. 타입 안전성을 위해 적용을 권장합니다.
33-33: 마감시간 제약조건 추가 필요이전 리뷰에서 제안된 마감시간 유효성 검사가 아직 적용되지 않았습니다.
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (3)
17-21: ID 필드 유효성 검사 강화 필요이전 리뷰에서 제안된
@Positive어노테이션이 아직 적용되지 않았습니다. ID 필드들이 양수여야 하므로 추가 검사가 필요합니다.@NotNull(message = "userId는 필수입니다.") +@Positive(message = "userId는 양수여야 합니다.") private Long userId; @NotNull(message = "storeId는 필수입니다.") +@Positive(message = "storeId는 양수여야 합니다.") private Long storeId;
26-27: 설명 필드 유효성 검사 개선 필요이전 리뷰에서 제안된
@NotBlank어노테이션이 아직 적용되지 않았습니다. 빈 문자열이나 공백만 있는 경우를 방지하기 위해 필요합니다.+@NotBlank(message = "설명은 비어 있을 수 없습니다.") @Size(min = 10, message = "설명은 최소 10자 이상이어야 합니다.") private String description;
29-30: 마감시간 유효성 검사 추가 필요이전 리뷰에서 제안된
@Future어노테이션이 아직 적용되지 않았습니다. 마감시간이 현재 시간보다 미래인지 확인하는 검사가 필요합니다.@NotNull(message = "마감 시간은 필수입니다.") +@Future(message = "마감시간은 현재 시간보다 미래여야 합니다.") private LocalDateTime deadlineTime;
🧹 Nitpick comments (6)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)
22-26: 외부 서비스 호출 안정성 개선 필요외부 서비스 호출에 재시도 메커니즘이나 서킷 브레이커 패턴을 적용하는 것을 고려해보세요.
Spring Retry나 Resilience4j를 사용한 개선 방안:
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000)) public Long createOrder(OrderRequestDto requestDto) { // 기존 로직 }recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (2)
35-35: 오타 수정 필요주석에 오타가 있습니다.
-//카테고리 추가가 +// 카테고리 필드
45-47: addOrderId 메서드에 null 체크 추가null 값이 추가되는 것을 방지하기 위한 검증이 필요합니다.
public void addOrderId(Long orderId) { + if (orderId == null) { + throw new IllegalArgumentException("orderId는 null일 수 없습니다."); + } this.orderIds.add(orderId); }recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
86-90: 빌더 패턴 사용으로 가독성 향상RecruitmentParticipant 엔티티 생성에도 빌더 패턴을 사용하면 코드 가독성이 향상됩니다.
- // 참여자 등록 + 주문 ID 저장 - RecruitmentParticipant participant = new RecruitmentParticipant(); - participant.setRecruitment(recruitment); - participant.setUser(user); - participant.setOrderId(orderId); - participantRepository.save(participant); + // 참여자 등록 + 주문 ID 저장 + RecruitmentParticipant participant = RecruitmentParticipant.builder() + .recruitment(recruitment) + .user(user) + .orderId(orderId) + .build(); + participantRepository.save(participant);recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
27-28: 자동 타임스탬프 설정 추가 권장
joinedAt필드가 엔티티 생성 시 자동으로 설정되도록@PrePersist어노테이션을 추가하는 것이 좋습니다.+@PrePersist +protected void onCreate() { + if (joinedAt == null) { + joinedAt = LocalDateTime.now(); + } +} + @Column(name = "joined_at", nullable = false) private LocalDateTime joinedAt;recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1)
15-15: 메서드 매개변수 타입 일관성 개선 필요같은 목적의 메서드들이 서로 다른 매개변수 타입을 사용하고 있습니다. 일관성을 위해 하나의 방식으로 통일하는 것이 좋습니다.
// 옵션 1: ID 기반으로 통일 -boolean existsByRecruitmentAndUser(Recruitment recruitment, User user); +boolean existsByRecruitmentIdAndUserId(Long recruitmentId, Long userId); // 또는 옵션 2: 엔티티 기반으로 통일 -Optional<RecruitmentParticipant> findByUserIdAndRecruitmentId(Long userId, Long recruitmentId); +Optional<RecruitmentParticipant> findByUserAndRecruitment(User user, Recruitment recruitment);ID 기반 접근을 권장합니다. 엔티티 객체가 이미 로드되지 않은 경우 불필요한 조회를 방지할 수 있습니다.
Also applies to: 25-25
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java(1 hunks)recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java(1 hunks)recruitment-service/src/main/resources/application.properties(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java
- recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java
🚧 Files skipped from review as they are similar to previous changes (3)
- recruitment-service/src/main/resources/application.properties
- recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java
- recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
Service(17-92)
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)
Service(11-42)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (4)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
Entity(8-32)recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)
Entity(6-18)recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
Entity(6-17)recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
Getter(13-46)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java (3)
Getter(8-32)Getter(17-24)Getter(26-31)
🔇 Additional comments (3)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2)
19-25: 관계 매핑 검증 완료사용자와 모집글에 대한 @manytoone 관계가 올바르게 설정되었습니다. @joincolumn에 nullable = false 제약조건도 적절히 추가되어 데이터 무결성이 보장됩니다.
19-25: 관계 설정이 잘 구현되었습니다
User와Recruitment엔티티와의 관계에서@JoinColumn과nullable = false설정이 적절히 구현되어 데이터 무결성을 보장합니다.recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1)
12-26: 메서드 명명 규칙이 잘 준수되었습니다Spring Data JPA의 메서드 명명 규칙을 잘 따르고 있으며, 필요한 쿼리 메서드들이 적절히 정의되어 있습니다.
| @Entity | ||
| @Getter @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class RecruitmentParticipant { |
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
테이블명 명시 및 인덱스 추가 권장
엔티티 클래스에 명시적인 테이블명과 복합 인덱스를 추가하는 것이 좋습니다.
@Entity
+@Table(name = "recruitment_participant",
+ indexes = {
+ @Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id"),
+ @Index(name = "idx_recruitment", columnList = "recruitment_id"),
+ @Index(name = "idx_user", columnList = "user_id")
+ })
@Getter @Setter📝 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.
| @Entity | |
| @Getter @Setter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class RecruitmentParticipant { | |
| @Entity | |
| @Table( | |
| name = "recruitment_participant", | |
| indexes = { | |
| @Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id"), | |
| @Index(name = "idx_recruitment", columnList = "recruitment_id"), | |
| @Index(name = "idx_user", columnList = "user_id") | |
| } | |
| ) | |
| @Getter @Setter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class RecruitmentParticipant { | |
| // … existing fields and mappings … | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
around lines 8 to 12, the entity class lacks an explicit table name and
composite index definitions. Add the @Table annotation above the class
declaration specifying the table name and define any necessary composite indexes
using the indexes attribute to improve database clarity and performance.
| @Column(name = "joined_at", nullable = false) | ||
| private LocalDateTime joinedAt; |
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
자동 타임스탬프 설정 추가 필요
참여 시점을 자동으로 설정하도록 개선해야 합니다.
+@CreationTimestamp
@Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt;또는 엔티티에 @PrePersist 메서드를 추가:
+@PrePersist
+protected void onCreate() {
+ if (joinedAt == null) {
+ joinedAt = LocalDateTime.now();
+ }
+}📝 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.
| @Column(name = "joined_at", nullable = false) | |
| private LocalDateTime joinedAt; | |
| // Add this import for Hibernate’s annotation | |
| // import org.hibernate.annotations.CreationTimestamp; | |
| // And for JPA lifecycle callbacks: | |
| // import javax.persistence.PrePersist; | |
| @CreationTimestamp | |
| @Column(name = "joined_at", nullable = false) | |
| private LocalDateTime joinedAt; | |
| @PrePersist | |
| protected void onCreate() { | |
| if (joinedAt == null) { | |
| joinedAt = LocalDateTime.now(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
at lines 27-28, the joinedAt field lacks automatic timestamp setting. To fix
this, implement automatic setting of joinedAt when the entity is persisted by
adding a method annotated with @PrePersist that assigns LocalDateTime.now() to
joinedAt before insertion.
| @Service | ||
| public class OrderClient { | ||
|
|
||
| private final RestTemplate restTemplate = new RestTemplate(); |
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
RestTemplate을 의존성 주입으로 변경 권장
RestTemplate을 직접 인스턴스화하는 대신 의존성 주입을 사용하는 것이 좋습니다.
-private final RestTemplate restTemplate = new RestTemplate();
+private final RestTemplate restTemplate;
+
+public OrderClient(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+}그리고 Configuration 클래스에서 RestTemplate 빈을 등록:
@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 14, the RestTemplate is instantiated directly which is not recommended.
Refactor the code to remove the direct instantiation and instead inject
RestTemplate as a dependency via constructor or field injection. Additionally,
create a configuration class (e.g., RestConfig) to define a RestTemplate bean
annotated with @Bean so that Spring can manage its lifecycle and inject it where
needed.
🛠️ Refactor suggestion
RestTemplate 주입 방식으로 변경 권장
RestTemplate을 직접 생성하는 대신 Spring Bean으로 주입받아 사용하는 것이 좋습니다. 이렇게 하면 설정(타임아웃, 커넥션 풀 등)을 중앙에서 관리할 수 있습니다.
-private final RestTemplate restTemplate = new RestTemplate();
+private final RestTemplate restTemplate;
+
+public OrderClient(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+}별도의 Configuration 클래스에서 RestTemplate Bean을 정의해야 합니다:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 14, the RestTemplate is instantiated directly which prevents centralized
configuration management. Refactor the code to remove the direct instantiation
and instead inject RestTemplate as a Spring Bean. Create a separate
configuration class annotated with @Configuration that defines a RestTemplate
@Bean, then use @Autowired or constructor injection in OrderClient to receive
the RestTemplate instance.
| throw new RuntimeException("응답에 orderId 없음"); | ||
| } | ||
|
|
||
| throw new RuntimeException("주문 생성 실패: " + response.getStatusCode()); | ||
| } |
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
구체적인 예외 처리 및 로깅 추가 필요
현재 RuntimeException은 너무 일반적이며, 로깅이 부족합니다.
+private static final Logger logger = LoggerFactory.getLogger(OrderClient.class);
+logger.error("주문 서버 응답에 orderId가 없습니다. Response: {}", responseBody);
throw new RuntimeException("응답에 orderId 없음");
+logger.error("주문 생성 실패. Status: {}, Response: {}", response.getStatusCode(), response.getBody());
throw new RuntimeException("주문 생성 실패: " + response.getStatusCode());더 구체적인 커스텀 예외 사용을 권장합니다.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 37 to 41, replace the generic RuntimeException with a more specific
custom exception class that clearly indicates the error type. Additionally, add
proper logging before throwing the exception to record the error details,
including the response status code or missing orderId information, to improve
error traceability and debugging.
| ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class); | ||
|
|
||
| if (response.getStatusCode().is2xxSuccessful()) { | ||
| Map<String, Object> responseBody = response.getBody(); | ||
|
|
||
| if (responseBody != null && responseBody.containsKey("data")) { | ||
| Map<String, Object> data = (Map<String, Object>) responseBody.get("data"); | ||
|
|
||
| if (data != null && data.containsKey("orderId")) { | ||
| return Long.valueOf(data.get("orderId").toString()); | ||
| } |
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
타입 안전성 및 응답 파싱 개선 필요
현재 Map 타입 캐스팅은 타입 안전하지 않고 런타임 오류 위험이 있습니다.
-ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+ResponseEntity<OrderResponseDto> response = restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class);
-if (response.getStatusCode().is2xxSuccessful()) {
- Map<String, Object> responseBody = response.getBody();
- if (responseBody != null && responseBody.containsKey("data")) {
- Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
- if (data != null && data.containsKey("orderId")) {
- return Long.valueOf(data.get("orderId").toString());
- }
- }
+if (response.getStatusCode().is2xxSuccessful()) {
+ OrderResponseDto responseBody = response.getBody();
+ if (responseBody != null && responseBody.getData() != null) {
+ return responseBody.getData().getOrderId();
+ }OrderResponseDto 클래스 생성이 필요합니다.
📝 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.
| ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class); | |
| if (response.getStatusCode().is2xxSuccessful()) { | |
| Map<String, Object> responseBody = response.getBody(); | |
| if (responseBody != null && responseBody.containsKey("data")) { | |
| Map<String, Object> data = (Map<String, Object>) responseBody.get("data"); | |
| if (data != null && data.containsKey("orderId")) { | |
| return Long.valueOf(data.get("orderId").toString()); | |
| } | |
| ResponseEntity<OrderResponseDto> response = | |
| restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class); | |
| if (response.getStatusCode().is2xxSuccessful()) { | |
| OrderResponseDto responseBody = response.getBody(); | |
| if (responseBody != null && responseBody.getData() != null) { | |
| return responseBody.getData().getOrderId(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 25 to 35, the current use of raw Map and unchecked casting for
parsing the response is not type-safe and risks runtime errors. To fix this,
create an OrderResponseDto class that models the expected response structure
with proper fields, then update the restTemplate call to deserialize directly
into this DTO instead of Map. Adjust the code to access orderId through the
DTO's getter methods, ensuring type safety and clearer response parsing.
| private String category; | ||
|
|
||
| // 주문용 메뉴 정보 | ||
| private List<OrderRequestDto.MenuDto> menus; |
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
메뉴 리스트 유효성 검사 추가
메뉴 리스트에 대한 유효성 검사가 없습니다. 비어있지 않은지, 각 메뉴 항목이 유효한지 확인이 필요합니다.
// 주문용 메뉴 정보
+@NotEmpty(message = "메뉴는 최소 1개 이상 선택해야 합니다.")
+@Valid
private List<OrderRequestDto.MenuDto> menus;📝 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.
| private List<OrderRequestDto.MenuDto> menus; | |
| // 주문용 메뉴 정보 | |
| @NotEmpty(message = "메뉴는 최소 1개 이상 선택해야 합니다.") | |
| @Valid | |
| private List<OrderRequestDto.MenuDto> menus; |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java
at line 36, the menus list lacks validation. Add validation annotations or logic
to ensure the menus list is not empty and that each menu item is valid, such as
using @NotEmpty on the list and @Valid on the menu items to enforce nested
validation.
| public OrderRequestDto toOrderRequestDto() { | ||
| OrderRequestDto dto = new OrderRequestDto(); | ||
| dto.setUserId(this.userId); | ||
| dto.setStoreId(this.storeId); | ||
| dto.setMenus(this.menus); | ||
| return dto; | ||
| } |
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
null 안전성 개선 및 방어적 프로그래밍
toOrderRequestDto 메서드에서 null 체크가 없어 NPE가 발생할 수 있습니다.
//OrderRequestDto 변환 메서드
public OrderRequestDto toOrderRequestDto() {
+ if (this.userId == null || this.storeId == null) {
+ throw new IllegalStateException("userId와 storeId는 필수값입니다.");
+ }
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
dto.setStoreId(this.storeId);
dto.setMenus(this.menus);
return dto;
}📝 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 OrderRequestDto toOrderRequestDto() { | |
| OrderRequestDto dto = new OrderRequestDto(); | |
| dto.setUserId(this.userId); | |
| dto.setStoreId(this.storeId); | |
| dto.setMenus(this.menus); | |
| return dto; | |
| } | |
| public OrderRequestDto toOrderRequestDto() { | |
| if (this.userId == null || this.storeId == null) { | |
| throw new IllegalStateException("userId와 storeId는 필수값입니다."); | |
| } | |
| OrderRequestDto dto = new OrderRequestDto(); | |
| dto.setUserId(this.userId); | |
| dto.setStoreId(this.storeId); | |
| dto.setMenus(this.menus); | |
| return dto; | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java
around lines 39 to 45, the toOrderRequestDto method lacks null checks which can
cause NullPointerException. Add null checks for fields like userId, storeId, and
menus before setting them on the OrderRequestDto object, and handle null cases
appropriately to ensure null safety and defensive programming.
| if (responseBody != null && responseBody.containsKey("data")) { | ||
| Map<String, Object> data = (Map<String, Object>) responseBody.get("data"); | ||
|
|
||
| if (data != null && data.containsKey("orderId")) { | ||
| return Long.valueOf(data.get("orderId").toString()); | ||
| } | ||
| } |
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
응답 파싱 안전성 강화 필요
중첩된 Map 구조를 파싱할 때 타입 안전성과 null 체크가 부족합니다.
if (responseBody != null && responseBody.containsKey("data")) {
- Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
+ Object dataObj = responseBody.get("data");
+ if (dataObj instanceof Map) {
+ Map<String, Object> data = (Map<String, Object>) dataObj;
- if (data != null && data.containsKey("orderId")) {
- return Long.valueOf(data.get("orderId").toString());
+ if (data.containsKey("orderId")) {
+ Object orderIdObj = data.get("orderId");
+ if (orderIdObj != null) {
+ try {
+ return Long.valueOf(orderIdObj.toString());
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("orderId 형식이 올바르지 않습니다: " + orderIdObj);
+ }
+ }
}
+ }
}📝 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 (responseBody != null && responseBody.containsKey("data")) { | |
| Map<String, Object> data = (Map<String, Object>) responseBody.get("data"); | |
| if (data != null && data.containsKey("orderId")) { | |
| return Long.valueOf(data.get("orderId").toString()); | |
| } | |
| } | |
| if (responseBody != null && responseBody.containsKey("data")) { | |
| Object dataObj = responseBody.get("data"); | |
| if (dataObj instanceof Map) { | |
| @SuppressWarnings("unchecked") | |
| Map<String, Object> data = (Map<String, Object>) dataObj; | |
| if (data.containsKey("orderId")) { | |
| Object orderIdObj = data.get("orderId"); | |
| if (orderIdObj != null) { | |
| try { | |
| return Long.valueOf(orderIdObj.toString()); | |
| } catch (NumberFormatException e) { | |
| throw new RuntimeException("orderId 형식이 올바르지 않습니다: " + orderIdObj, e); | |
| } | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 30 to 36, the current code lacks sufficient type safety and null
checks when parsing the nested Map structure from the responseBody. To fix this,
add explicit type checks before casting, verify that the "data" object is indeed
a Map, and ensure all accessed keys and values are non-null before usage to
prevent ClassCastException and NullPointerException.
| public Long createOrder(OrderRequestDto requestDto) { | ||
| String fullUrl = serverUrl + path; | ||
|
|
||
| ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class); |
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
예외 처리 및 로깅 개선 필요
외부 서비스 호출 시 네트워크 예외나 타임아웃에 대한 처리가 없습니다. 또한 디버깅을 위한 로깅도 필요합니다.
+@Slf4j
@Service
public class OrderClient {
+ public Long createOrder(OrderRequestDto requestDto) {
+ String fullUrl = serverUrl + path;
+ log.info("주문 생성 요청: URL={}, 요청데이터={}", fullUrl, requestDto);
+
+ try {
- ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+ ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+ log.info("주문 생성 응답: 상태코드={}, 응답데이터={}", response.getStatusCode(), response.getBody());
+ } catch (Exception e) {
+ log.error("주문 생성 중 오류 발생: URL={}, 오류={}", fullUrl, e.getMessage(), e);
+ throw new RuntimeException("주문 서비스 호출 실패", e);
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 25, the call to restTemplate.postForEntity lacks exception handling for
network errors and timeouts, and there is no logging for debugging. Wrap the
call in a try-catch block to handle exceptions like RestClientException, log the
exception details using a logger, and ensure the method handles or propagates
the error appropriately to improve robustness and traceability.
| @Column(name = "order_id") | ||
| private Long orderId; | ||
|
|
||
| @ElementCollection | ||
| private List<Long> orderIds = new ArrayList<>(); |
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
orderId와 orderIds 필드의 목적 명확화 필요
단일 orderId 필드와 컬렉션 orderIds 필드가 동시에 존재하여 혼란을 야기할 수 있습니다. 각각의 사용 목적을 명확히 하거나 하나로 통합하는 것을 고려해보세요.
+// 옵션 1: 단일 orderId만 사용
-@Column(name = "order_id")
-private Long orderId;
-
-@ElementCollection
-private List<Long> orderIds = new ArrayList<>();
+@Column(name = "order_id")
+private Long orderId;
+// 옵션 2: 컬렉션만 사용 (여러 주문 지원)
-@Column(name = "order_id")
-private Long orderId;
-
+@ElementCollection
+@CollectionTable(name = "recruitment_order_ids")
+@Column(name = "order_id")
+private List<Long> orderIds = new ArrayList<>();Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java
around lines 39 to 43, there are two fields, orderId and orderIds, which may
cause confusion due to overlapping purposes. Clarify the intended use of each
field by adding comments or renaming them for clarity, or consolidate them into
a single field if they represent the same concept to avoid redundancy and
improve code maintainability.
기능: Swagger 문서화
Summary by CodeRabbit
신규 기능
CI/CD 및 배포
예외 처리 및 응답 표준화
기타