Skip to content

Conversation

@ehddms0320
Copy link

@ehddms0320 ehddms0320 commented May 30, 2025

  • 모집 생성 시 Order 서버에 주문 요청 후 orderId 저장
  • 모집 참여 시 개별 주문 요청 및 orderId 저장
  • OrderClient로 주문 서버 연동
  • Swagger 테스트 완료

Summary by CodeRabbit

  • 신규 기능

    • 주문 요청 및 응답을 위한 DTO(OrderRequestDto, OrderResponseDto)와 주문 관련 엔티티 필드가 추가되었습니다.
    • 외부 주문 서비스와 연동하는 기능이 도입되어, 모집글 생성 및 참여 시 주문이 자동으로 생성됩니다.
    • Swagger 연동 테스트를 위한 간단한 엔드포인트가 추가되었습니다.
  • 기능 개선

    • 모집글 생성 및 참여 로직이 서비스 계층으로 이동하여, 컨트롤러가 더 간결해졌습니다.
    • 모집글 상세 조회, 수정, 삭제 등 기존 엔드포인트의 응답 및 처리 방식이 일부 개선되었습니다.
    • 모집글의 카테고리 및 메뉴 정보 입력이 가능해졌습니다.
  • 버그 수정

    • Swagger 관련 요청 시 예외 처리 로직이 개선되어, Swagger 문서 접근이 정상적으로 동작합니다.

@ehddms0320 ehddms0320 had a problem deploying to recruitment-service May 30, 2025 17:00 — with GitHub Actions Failure
@coderabbitai
Copy link

coderabbitai bot commented May 30, 2025

Walkthrough

이 변경사항은 모집 서비스에 주문 연동 기능을 도입하고, 비즈니스 로직을 컨트롤러에서 서비스 계층으로 이전합니다. Swagger 테스트 컨트롤러와 주문 관련 DTO, 서비스, 클라이언트가 추가되었으며, 예외 처리 및 엔티티 필드가 확장되었습니다. 빌드 설정과 API 경로도 일부 수정되었습니다.

Changes

파일/경로 요약 변경 내용 요약
build.gradle Spring Boot 및 dependency management 플러그인 버전 다운그레이드, springdoc-openapi 의존성 추가
controller/RecruitmentController.java API 경로 변경, 비즈니스 로직 서비스 위임, 주문 참여 엔드포인트 추가, 불필요 코드 제거, 리팩토링
controller/SwaggerTestController.java Swagger 연동 테스트용 신규 REST 컨트롤러 추가
dto/RecruitmentRequestDto.java category, menus 필드 및 toOrderRequestDto() 메서드 추가
dto/order/OrderRequestDto.java, OrderResponseDto.java 주문 요청/응답 DTO 신규 추가, 메뉴/옵션 구조 포함
entity/Recruitment.java, RecruitmentParticipant.java category, orderId, orderIds 필드 및 orderId 추가/저장 메서드 등 엔티티 확장
exception/GlobalExceptionHandler.java Swagger 경로 예외시 기본 처리 위임, 메서드 시그니처 및 import 변경
repository/RecruitmentParticipantRepository.java 엔티티 기반 참여 여부 확인 메서드 추가
service/OrderClient.java, RecruitmentService.java 주문 서비스 연동 클라이언트 및 모집/참여 비즈니스 로직 서비스 신규 도입
application.properties 마지막 줄 공백 추가 (실질적 설정 변경 없음)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant RecruitmentController
    participant RecruitmentService
    participant OrderClient
    participant OrderService

    User->>RecruitmentController: 모집글 생성 요청(RecruitmentRequestDto)
    RecruitmentController->>RecruitmentService: createRecruitment(dto)
    RecruitmentService->>OrderClient: createOrder(OrderRequestDto)
    OrderClient->>OrderService: POST /api/v1/orders
    OrderService-->>OrderClient: 주문 생성 결과(orderId)
    OrderClient-->>RecruitmentService: orderId 반환
    RecruitmentService->>RecruitmentService: 모집글에 orderId 저장
    RecruitmentService-->>RecruitmentController: 처리 완료

    User->>RecruitmentController: 모집 참여 요청(joinRecruitment)
    RecruitmentController->>RecruitmentService: joinRecruitment(recruitmentId, userId, orderDto)
    RecruitmentService->>OrderClient: createOrder(OrderRequestDto)
    OrderClient->>OrderService: POST /api/v1/orders
    OrderService-->>OrderClient: 주문 생성 결과(orderId)
    OrderClient-->>RecruitmentService: orderId 반환
    RecruitmentService->>RecruitmentParticipantRepository: 참여자 저장
    RecruitmentService-->>RecruitmentController: 처리 완료
Loading

Possibly related PRs

  • GCU-Project-Space/Server#41: 전역 예외 처리기(GlobalExceptionHandler) 도입 및 일관된 예외 응답 구조 도입과 관련된 PR로, 이번 PR의 Swagger 예외 처리 개선과 코드 레벨에서 연관성이 있습니다.
  • GCU-Project-Space/Server#23: 주문 서비스의 응답 구조 표준화, 예외 처리, API 문서화 개선 등 주문 연동 및 DTO 구조와 관련된 변경이 있어, 이번 PR의 주문 연동 로직과 직접적으로 관련됩니다.

Poem

🐇
새로 온 주문의 길,
모집글 따라 흐르는 thrill.
서비스가 분리되어 더 깔끔해,
컨트롤러는 가벼워졌네.
Swagger도 "works!"라 외치니,
토끼는 기뻐 깡총깡총 뛰네!

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ehddms0320 ehddms0320 temporarily deployed to recruitment-service May 30, 2025 17:24 — with GitHub Actions Inactive
@kmj02dev kmj02dev closed this May 30, 2025
@ehddms0320 ehddms0320 temporarily deployed to recruitment-service May 30, 2025 17:27 — with GitHub Actions Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (6)
recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1)

7-14: 테스트용 컨트롤러 개선 제안

Swagger 테스트용 엔드포인트가 올바르게 구현되었습니다. 다음과 같은 개선사항을 고려해보세요:

  1. 프로덕션 환경에서는 비활성화 검토
  2. 프로젝트의 ApiResponse 패턴과 일관성 유지

다음과 같이 개선할 수 있습니다:

+import com.example.recruitment.common.ApiResponse;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.ResponseEntity;

+@Profile("!prod")  // 프로덕션 환경에서 비활성화
 @RestController
 @RequestMapping("/swagger-test")
 public class SwaggerTestController {
     @GetMapping
-    public String test() {
-        return "Swagger works!";
+    public ResponseEntity<ApiResponse<String>> test() {
+        return ResponseEntity.ok(ApiResponse.success("Swagger works!", "Swagger 테스트가 성공적으로 완료되었습니다."));
     }
 }
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)

39-44: Swagger 요청 처리 로직이 올바르게 구현되었습니다.

HttpServletRequest를 통해 요청 URI를 확인하고 Swagger 관련 경로에 대해서는 예외 처리를 우회하는 로직이 적절합니다.

경로 검사 로직을 더욱 견고하게 만들 수 있습니다:

-        if (path != null && (path.contains("/v3/api-docs") || path.contains("/swagger"))) {
+        if (path != null && (path.startsWith("/v3/api-docs") || path.startsWith("/swagger-ui") || path.contains("/swagger"))) {
                 throw e; // Swagger 요청은 예외 처리하지 않고 Spring에게 맡김
 }

이렇게 하면 더 정확한 경로 매칭이 가능합니다.

recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java (1)

1-10: 간단하고 명확한 응답 DTO입니다.

현재 요구사항에 맞는 간결한 구현입니다. Lombok을 사용하여 보일러플레이트 코드를 줄인 것도 좋습니다.

향후 확장성을 고려한다면 다음과 같은 개선을 고려해볼 수 있습니다:

 @Getter
 @Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class OrderResponseDto {
+    @NotNull
     private Long orderId;
+    // 향후 주문 상태, 생성 시간 등 필드 추가 가능
 }
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)

35-35: 주석의 오타를 수정하세요.

주석에 불필요한 "가"가 추가되어 있습니다.

-    //카테고리 추가가
+    //카테고리 추가
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (2)

29-34: 서비스 계층으로의 비즈니스 로직 위임이 좋습니다

컨트롤러에서 직접 repository를 조작하던 것을 RecruitmentService로 위임한 것은 좋은 아키텍처 개선입니다. 관심사 분리가 잘 이루어졌습니다.

반환 타입의 타입 안전성을 개선할 수 있습니다:

-public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
+public ResponseEntity<String> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {

58-79: 상세 조회 로직이 개선되었습니다

불필요한 조인 상태 체크와 엔티티 생성 로직이 제거되어 코드가 더 명확해졌습니다.

DTO 수동 매핑을 개선할 수 있습니다:

-        RecruitmentDetailDto dto = new RecruitmentDetailDto();
-        dto.setId(recruitment.getId());
-        dto.setTitle(recruitment.getTitle());
-        // ... 기타 setter 호출들
+        RecruitmentDetailDto dto = RecruitmentDetailDto.builder()
+                .id(recruitment.getId())
+                .title(recruitment.getTitle())
+                .description(recruitment.getDescription())
+                .status(recruitment.getStatus())
+                .deadlineTime(recruitment.getDeadlineTime())
+                .user(recruitment.getUser())
+                .store(recruitment.getStore())
+                .participants(participantUsers)
+                .build();

또는 MapStruct 같은 매핑 라이브러리 사용을 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45de156 and 74d994e.

📒 Files selected for processing (13)
  • recruitment-service/build.gradle (2 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (4 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (2 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 (2 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 (3 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)
🧰 Additional context used
🧬 Code Graph Analysis (2)
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 (10-40)
🔇 Additional comments (14)
recruitment-service/src/main/resources/application.properties (1)

10-10: 화이트스페이스만 변경됨 — 기능에 영향 없음

application.properties 파일 끝에 불필요한 빈 줄이 추가되었습니다. 설정 동작에는 영향을 주지 않으므로 이 변경은 무시해도 됩니다.

recruitment-service/build.gradle (2)

31-31: Swagger 의존성 추가가 적절합니다.

SpringDoc OpenAPI 의존성이 올바르게 추가되어 Swagger UI 통합을 지원합니다.


3-4:

Details

❓ Verification inconclusive

버전 다운그레이드에 대한 검증이 필요합니다.

Spring Boot 버전이 3.4.5에서 3.1.5로, Spring dependency management가 1.1.7에서 1.1.3으로 다운그레이드되었습니다. 일반적으로 최신 버전을 사용하는 것이 보안 및 버그 수정 측면에서 권장됩니다.

다음 스크립트를 통해 버전 호환성과 보안 이슈를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 설명: Spring Boot 버전별 보안 취약점 및 최신 버전 확인

# Spring Boot 최신 버전 확인
curl -s https://api.github.com/repos/spring-projects/spring-boot/releases/latest | jq '.tag_name'

# Spring Boot 버전별 보안 공지사항 확인
curl -s "https://api.github.com/repos/spring-projects/spring-boot/security/advisories" | jq '.[] | select(.severity == "high" or .severity == "critical") | {summary, severity, published_at}'

Length of output: 366


버전 다운그레이드 호환성 및 보안 검증 필요

Spring Boot 플러그인 버전이 3.4.53.1.5로, Spring dependency-management 플러그인이 1.1.71.1.3으로 낮아졌습니다. 현재 GitHub 릴리스 기준 최신 Spring Boot는 v3.5.0이며, 다운그레이드된 버전들은 보안 패치나 API 변경사항에서 뒤처질 수 있습니다. 다음 항목을 확인해주세요:

  • recruitment-service/build.gradle (3–4행):
    org.springframework.boot 플러그인 버전
    io.spring.dependency-management 플러그인 버전
  • 해당 버전들의 보안 취약점 내역 및 패치 현황
  • 버전 변경이 서비스 기능(특히 Spring MVC, AOP, 데이터 바인딩 등)에 미치는 영향

위 사항을 검증하신 후 결과를 공유해 주세요.

recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)

5-5: import 추가가 적절합니다.

HttpServletRequest import가 새로운 기능을 위해 올바르게 추가되었습니다.

recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (2)

3-3: 필요한 엔티티 import 추가가 적절합니다.

새로운 메서드에서 사용할 RecruitmentUser 엔티티 import가 추가되었습니다.

Also applies to: 5-5


24-25: 중복 참여 방지를 위한 메서드 추가가 우수합니다.

엔티티 객체를 직접 받는 existsByRecruitmentAndUser 메서드 추가가 잘 구현되었습니다. ID 대신 엔티티 객체를 사용하여 타입 안전성을 높였고, exists 메서드를 사용하여 성능도 최적화했습니다.

recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (3)

3-3: 주문 DTO import 추가가 적절합니다.

주문 연동 기능을 위한 OrderRequestDto import가 추가되었습니다.


32-33: 카테고리 필드 추가와 검증이 잘 구현되었습니다.

@NotBlank 검증과 함께 카테고리 필드가 추가되어 데이터 무결성이 보장됩니다.


38-45: OrderRequestDto 변환 메서드가 잘 구현되었습니다.

필수 필드들을 적절히 매핑하는 깔끔한 변환 메서드입니다. 코드의 가독성과 유지보수성이 좋습니다.

recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)

52-52:

Details

✅ Verification successful

toOrderRequestDto() 메서드의 존재 여부를 확인하세요.

dto.toOrderRequestDto() 메서드가 RecruitmentRequestDto에 정의되어 있는지 확인이 필요합니다.

다음 스크립트를 실행하여 해당 메서드가 존재하는지 확인하세요:


🏁 Script executed:

#!/bin/bash
# Description: RecruitmentRequestDto에 toOrderRequestDto 메서드가 있는지 확인

# toOrderRequestDto 메서드 검색
rg -A 10 "toOrderRequestDto" --type java

Length of output: 2302


toOrderRequestDto() 메서드 존재 확인
RecruitmentRequestDto 클래스에 toOrderRequestDto()가 이미 정의되어 있어 별도의 수정이 필요 없습니다.

recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (4)

5-5: 의존성 추가와 API 버전 관리가 적절합니다

주문 연동을 위한 OrderRequestDto import와 비즈니스 로직 분리를 위한 RecruitmentService 의존성 추가가 좋습니다. API 버전을 v1으로 명시한 것도 향후 호환성 관리에 도움이 될 것입니다.

Also applies to: 11-11, 22-22, 25-25


82-94: 사용자 모집글 조회 메서드들이 일관성 있게 개선되었습니다

메서드명과 포맷팅이 일관성 있게 개선되었고, 기존 기능은 잘 유지되었습니다.


96-138: 상태 관리 메서드들이 잘 유지되었습니다

기존의 상태 업데이트 로직이 잘 유지되었고, 주석과 포맷팅이 개선되었습니다.


140-146: 삭제 메서드가 일관성 있게 포맷팅되었습니다

기존 기능이 잘 유지되고 포맷팅이 일관성 있게 개선되었습니다.

Comment on lines +29 to +31

@Column(name = "order_id")
private Long orderId;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

주문 ID 필드 추가가 적절합니다.

새로운 주문 연동 기능을 위한 orderId 필드 추가가 잘 구현되었습니다. nullable로 설정하여 기존 데이터와의 호환성도 고려되었습니다.

다만 주문 생성 실패 시 데이터 일관성을 보장하기 위한 처리 로직이 서비스 계층에 구현되어 있는지 확인이 필요합니다.

주문 생성 실패 시나리오에 대한 처리가 적절히 구현되어 있는지 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# 주문 생성 관련 서비스 로직에서 예외 처리 확인
ast-grep --pattern $'class RecruitmentService {
  $$$
  joinRecruitment($$$) {
    $$$
    try {
      $$$
    } catch ($$$) {
      $$$
    }
    $$$
  }
  $$$
}'

Length of output: 164


서비스 계층의 joinRecruitment 메소드와 그 안의 예외 처리 로직을 직접 확인하기 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# RecruitmentService 클래스 정의 위치 확인
rg -n "class RecruitmentService" -A3 .

# joinRecruitment 메소드 구현 내용 및 컨텍스트 확인 (5줄 추가)
rg -n "joinRecruitment" -A5 .

# 해당 파일에서 try/catch 블록 존재 여부 확인
rg -n "try" -A3 src/main/java/com/example/recruitment/service/RecruitmentService.java || echo "try/catch 블록이 없습니다."

Length of output: 3100


서비스 계층에 주문 생성 예외 처리 로직 추가 필요

현재 recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.javajoinRecruitment(약 69행) 메소드에는 주문 생성 연동 및 예외 처리 로직이 없습니다. 주문 서비스 호출 실패 시 데이터 일관성이 깨질 수 있으므로 아래 사항을 반영해주세요.

• 파일: recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
– 메소드: public void joinRecruitment(Long recruitmentId, Long userId, OrderRequestDto orderDto)
– 현황: 주문 생성(orderService 호출) 및 try/catch 블록 부재

제안하는 수정 예시:

     public void joinRecruitment(Long recruitmentId, Long userId, OrderRequestDto orderDto) {
         Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
-                .orElseThrow(() -> new RuntimeException("모집 없음"));
+                .orElseThrow(() -> new RuntimeException("모집 없음"));

         User user = userRepository.findById(userId)
-                .orElseThrow(() -> new RuntimeException("사용자 없음"));
+                .orElseThrow(() -> new RuntimeException("사용자 없음"));

+        try {
+            Long orderId = orderService.createOrder(orderDto);
+            recruitmentParticipant.setOrderId(orderId);
+        } catch (OrderException e) {
+            // TODO: 롤백 또는 보상 트랜잭션 처리
+            throw new RuntimeException("주문 생성 실패로 참여 처리 취소", e);
+        }
         // 나머지 참여 로직
     }

주문 생성 실패 시에도 참여 데이터가 불완전하게 저장되지 않도록 적절한 예외 처리 및 트랜잭션 관리 코드를 구현해주세요.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around line 69, the joinRecruitment method currently lacks order creation and
exception handling logic. To fix this, add a call to the orderService to create
an order within a try block, and catch any exceptions that occur during order
creation. Ensure that if order creation fails, the recruitment participation
data is not partially saved by rolling back the transaction or handling the
exception appropriately. This will maintain data consistency between recruitment
participation and order creation.

Comment on lines +35 to +36
// 주문용 메뉴 정보
private List<OrderRequestDto.MenuDto> menus;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

메뉴 필드에 검증 추가를 권장합니다.

메뉴 정보는 주문 생성에 필수적인 데이터이므로 적절한 검증이 필요합니다.

메뉴 필드에 검증 어노테이션을 추가하는 것을 권장합니다:

 // 주문용 메뉴 정보
+@NotEmpty(message = "메뉴 정보는 필수입니다.")
+@Valid
 private List<OrderRequestDto.MenuDto> menus;
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java
at lines 35-36, the menus field lacks validation annotations. Since menu
information is essential for order creation, add appropriate validation
annotations such as @NotNull and @NotEmpty to the menus field to ensure it is
not null or empty during data binding or request processing.

public Long createOrder(OrderRequestDto requestDto) {
String fullUrl = serverUrl + path;

ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

타입 안전성을 위해 응답 DTO를 사용하세요.

Raw Map 타입 대신 구체적인 응답 DTO를 사용하면 타입 안전성과 코드 가독성이 향상됩니다.

-        ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+        ResponseEntity<OrderResponseDto> response = restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class);

그리고 OrderResponseDto 클래스를 생성하세요:

@Getter
@Setter
public class OrderResponseDto {
    private OrderDataDto data;
    
    @Getter
    @Setter
    public static class OrderDataDto {
        private Long orderId;
    }
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 22, replace the raw Map response type in restTemplate.postForEntity with
a specific response DTO class to improve type safety and readability. Create an
OrderResponseDto class with nested OrderDataDto as described, then update the
method to use ResponseEntity<OrderResponseDto> instead of ResponseEntity<Map>.
Adjust the code to handle the DTO fields accordingly.

Comment on lines +24 to +38
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());
}
}

throw new RuntimeException("응답에 orderId 없음");
}

throw new RuntimeException("주문 생성 실패: " + response.getStatusCode());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 처리와 응답 파싱을 개선하세요.

현재 구조는 중첩된 타입 캐스팅과 null 체크로 인해 복잡하고 오류가 발생하기 쉽습니다. 또한 일반적인 RuntimeException 대신 구체적인 예외를 사용하는 것이 좋습니다.

-        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());
-                }
-            }
-
-            throw new RuntimeException("응답에 orderId 없음");
-        }
-
-        throw new RuntimeException("주문 생성 실패: " + response.getStatusCode());
+        if (!response.getStatusCode().is2xxSuccessful()) {
+            throw new OrderServiceException("주문 생성 실패: " + response.getStatusCode());
+        }
+        
+        OrderResponseDto responseBody = response.getBody();
+        if (responseBody == null || responseBody.getData() == null || responseBody.getData().getOrderId() == null) {
+            throw new OrderServiceException("응답에 orderId가 없습니다");
+        }
+        
+        return responseBody.getData().getOrderId();

그리고 커스텀 예외 클래스를 생성하세요:

public class OrderServiceException extends RuntimeException {
    public OrderServiceException(String message) {
        super(message);
    }
    
    public OrderServiceException(String message, Throwable cause) {
        super(message, cause);
    }
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 24 to 38, the current error handling and response parsing use
nested type casting and null checks, making the code complex and error-prone.
Refactor by introducing a custom exception class named OrderServiceException
extending RuntimeException with constructors for message and cause. Replace the
generic RuntimeException throws with this custom exception to provide clearer,
more specific error handling. Simplify the response parsing by safely extracting
the orderId with proper null checks and type validation to avoid
ClassCastException.

@Service
public class OrderClient {

private final RestTemplate restTemplate = new RestTemplate();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

RestTemplate을 Bean으로 주입하세요.

새로운 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() {
        RestTemplate restTemplate = new RestTemplate();
        // 타임아웃 설정
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(5000);
        factory.setReadTimeout(10000);
        restTemplate.setRequestFactory(factory);
        return restTemplate;
    }
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 13, replace the direct instantiation of RestTemplate with a
Spring-managed Bean injection. Remove the line creating a new RestTemplate
instance and instead add a constructor or field injection to receive the
RestTemplate Bean. Additionally, create a separate configuration class annotated
with @Configuration that defines a RestTemplate Bean with appropriate timeout
settings using HttpComponentsClientHttpRequestFactory as shown, so RestTemplate
can be centrally configured and reused.

* - 생성된 orderId를 모집글에 저장
*/
@Transactional
public void createRecruitment(RecruitmentRequestDto dto) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

메서드 매개변수 검증을 추가하세요.

null 값이나 잘못된 매개변수에 대한 검증이 없어서 예상치 못한 오류가 발생할 수 있습니다.

     @Transactional
     public void createRecruitment(RecruitmentRequestDto dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("RecruitmentRequestDto는 null일 수 없습니다");
+        }
+        if (dto.getUserId() == null || dto.getStoreId() == null) {
+            throw new IllegalArgumentException("사용자 ID와 가게 ID는 필수입니다");
+        }

     @Transactional
     public void joinRecruitment(Long recruitmentId, Long userId, OrderRequestDto orderDto) {
+        if (recruitmentId == null || userId == null || orderDto == null) {
+            throw new IllegalArgumentException("모든 매개변수는 null일 수 없습니다");
+        }

또는 Bean Validation을 사용하는 것을 고려하세요:

+    @Transactional
+    public void createRecruitment(@Valid RecruitmentRequestDto dto) {

+    @Transactional  
+    public void joinRecruitment(@NotNull Long recruitmentId, @NotNull Long userId, @Valid OrderRequestDto orderDto) {

Also applies to: 69-69

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
at lines 35 and 69, the methods lack validation for their parameters, which can
lead to unexpected errors if null or invalid values are passed. Add explicit
null checks and validation logic for the method parameters to ensure they meet
expected criteria before proceeding. Alternatively, consider using Bean
Validation annotations on the DTO classes and enable validation in the service
methods to automatically enforce parameter correctness.

Comment on lines +36 to +37
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new RuntimeException("사용자 없음"));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

구체적인 예외 클래스를 사용하세요.

일반적인 RuntimeException 대신 구체적인 예외 클래스를 사용하면 예외 처리가 더 명확해지고 클라이언트에서 적절한 응답을 할 수 있습니다.

-        User user = userRepository.findById(dto.getUserId())
-                .orElseThrow(() -> new RuntimeException("사용자 없음"));
-        Store store = storeRepository.findById(dto.getStoreId())
-                .orElseThrow(() -> new RuntimeException("가게 없음"));
+        User user = userRepository.findById(dto.getUserId())
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다: " + dto.getUserId()));
+        Store store = storeRepository.findById(dto.getStoreId())
+                .orElseThrow(() -> new EntityNotFoundException("가게를 찾을 수 없습니다: " + dto.getStoreId()));

-        Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
-                .orElseThrow(() -> new RuntimeException("모집 없음"));
-        User user = userRepository.findById(userId)
-                .orElseThrow(() -> new RuntimeException("사용자 없음"));
+        Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
+                .orElseThrow(() -> new EntityNotFoundException("모집을 찾을 수 없습니다: " + recruitmentId));
+        User user = userRepository.findById(userId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다: " + userId));

-            throw new RuntimeException("이미 참여한 모집입니다.");
+            throw new BusinessException("이미 참여한 모집입니다.");

커스텀 예외 클래스들을 생성하세요:

public class EntityNotFoundException extends RuntimeException {
    public EntityNotFoundException(String message) {
        super(message);
    }
}

public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

Also applies to: 38-39, 70-71, 72-73

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 36-37, replace the generic RuntimeException thrown when a user is
not found with a more specific custom exception like EntityNotFoundException.
Create custom exception classes such as EntityNotFoundException and
BusinessException as suggested, and use them in all similar cases including
lines 38-39, 70-71, and 72-73 to improve exception clarity and handling.

Comment on lines +41 to +58
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");
recruitment.setCategory(dto.getCategory());
recruitmentRepository.save(recruitment);

// 주문 서버에 주문 생성 요청
OrderRequestDto orderDto = dto.toOrderRequestDto();
orderDto.setGroupId(recruitment.getId());
Long orderId = orderClient.createOrder(orderDto);

// orderId를 모집글에 저장
recruitment.setOrderId(orderId);
recruitmentRepository.save(recruitment);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

트랜잭션 일관성을 개선하세요.

현재 구조는 모집글을 먼저 저장한 후 주문을 생성하고 다시 저장하는데, 이는 주문 생성이 실패할 경우 불일치 상태를 만들 수 있습니다.

-        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");
-        recruitment.setCategory(dto.getCategory());
-        recruitmentRepository.save(recruitment);
-
-        // 주문 서버에 주문 생성 요청
-        OrderRequestDto orderDto = dto.toOrderRequestDto();
-        orderDto.setGroupId(recruitment.getId());
-        Long orderId = orderClient.createOrder(orderDto);
-
-        // orderId를 모집글에 저장
-        recruitment.setOrderId(orderId);
-        recruitmentRepository.save(recruitment);
+        // 주문 서버에 주문 생성 요청을 위한 임시 모집글 생성
+        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");
+        recruitment.setCategory(dto.getCategory());
+        recruitment = recruitmentRepository.save(recruitment);
+
+        try {
+            // 주문 서버에 주문 생성 요청
+            OrderRequestDto orderDto = dto.toOrderRequestDto();
+            orderDto.setGroupId(recruitment.getId());
+            Long orderId = orderClient.createOrder(orderDto);
+
+            // orderId를 모집글에 저장
+            recruitment.setOrderId(orderId);
+            recruitmentRepository.save(recruitment);
+        } catch (Exception e) {
+            // 주문 생성 실패 시 모집글 삭제 또는 상태 변경
+            recruitmentRepository.delete(recruitment);
+            throw new OrderCreationException("주문 생성 중 오류가 발생했습니다", e);
+        }
📝 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.

Suggested change
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");
recruitment.setCategory(dto.getCategory());
recruitmentRepository.save(recruitment);
// 주문 서버에 주문 생성 요청
OrderRequestDto orderDto = dto.toOrderRequestDto();
orderDto.setGroupId(recruitment.getId());
Long orderId = orderClient.createOrder(orderDto);
// orderId를 모집글에 저장
recruitment.setOrderId(orderId);
recruitmentRepository.save(recruitment);
// 주문 서버에 주문 생성 요청을 위한 임시 모집글 생성
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");
recruitment.setCategory(dto.getCategory());
recruitment = recruitmentRepository.save(recruitment);
try {
// 주문 서버에 주문 생성 요청
OrderRequestDto orderDto = dto.toOrderRequestDto();
orderDto.setGroupId(recruitment.getId());
Long orderId = orderClient.createOrder(orderDto);
// orderId를 모집글에 저장
recruitment.setOrderId(orderId);
recruitmentRepository.save(recruitment);
} catch (Exception e) {
// 주문 생성 실패 시 모집글 삭제 또는 상태 변경
recruitmentRepository.delete(recruitment);
throw new OrderCreationException("주문 생성 중 오류가 발생했습니다", e);
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 41 to 58, the current code saves the recruitment entity before
creating the order, then updates and saves it again after order creation. To
improve transaction consistency and avoid data inconsistency if order creation
fails, refactor the code to create the order first, then set the orderId on the
recruitment entity, and finally save the recruitment once. This ensures
atomicity and prevents partial updates.

Comment on lines +161 to +163
if (dto.getStoreId() != null) {
recruitment.setStore(recruitment.getStore());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

치명적인 로직 버그가 있습니다

새로운 storeId가 제공되었을 때 기존 store를 다시 설정하고 있어서 store 변경이 전혀 적용되지 않습니다.

다음과 같이 수정해야 합니다:

         if (dto.getStoreId() != null) {
-            recruitment.setStore(recruitment.getStore());
+            Store store = storeRepository.findById(dto.getStoreId())
+                    .orElseThrow(() -> new EntityNotFoundException("Store not found"));
+            recruitment.setStore(store);
         }

이를 위해 StoreRepository 의존성을 다시 주입받아야 합니다.

📝 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.

Suggested change
if (dto.getStoreId() != null) {
recruitment.setStore(recruitment.getStore());
}
if (dto.getStoreId() != null) {
Store store = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new EntityNotFoundException("Store not found"));
recruitment.setStore(store);
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 161 to 163, the code incorrectly resets the store to the existing
one when a new storeId is provided, so the store change is not applied. Fix this
by injecting StoreRepository into the controller, then fetch the new Store
entity using the provided storeId from dto and set it on the recruitment object
instead of resetting the existing store.

Comment on lines +36 to 43
//모집글 참여 (Order 서버에 주문 생성 포함)
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
@RequestParam Long userId,
@RequestBody OrderRequestDto orderRequestDto) {
recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
return ResponseEntity.ok("모집글 참여 완료");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

모집글 참여 기능이 잘 구현되었습니다

새로운 참여 엔드포인트가 RESTful 원칙에 맞게 구현되었고, 서비스 계층으로의 위임도 적절합니다.

보안상 userId를 URL 파라미터 대신 인증된 사용자 정보에서 가져오는 것을 권장합니다:

-public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
-                                         @RequestParam Long userId,
-                                         @RequestBody OrderRequestDto orderRequestDto) {
+public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
+                                         @RequestBody OrderRequestDto orderRequestDto,
+                                         Authentication authentication) {
+    Long userId = getUserIdFromAuthentication(authentication);
📝 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.

Suggested change
//모집글 참여 (Order 서버에 주문 생성 포함)
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
@RequestParam Long userId,
@RequestBody OrderRequestDto orderRequestDto) {
recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
return ResponseEntity.ok("모집글 참여 완료");
}
//모집글 참여 (Order 서버에 주문 생성 포함)
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
@RequestBody OrderRequestDto orderRequestDto,
Authentication authentication) {
Long userId = getUserIdFromAuthentication(authentication);
recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
return ResponseEntity.ok("모집글 참여 완료");
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 36 to 43, the joinRecruitment method currently accepts userId as a
request parameter, which is a security risk. Modify the method to remove the
userId parameter and instead obtain the authenticated user's ID from the
security context or authentication principal. This ensures the userId is
securely retrieved from the logged-in user's session rather than from client
input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants