-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Swagger 설정 및 모집글 API 문서화 완료 #50
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이번 변경에서는 주문(Order) 서버와의 연동을 위한 클라이언트 및 DTO가 추가되고, 리크루트(Recruitment) 생성 및 참여 로직이 서비스 계층으로 이동되었습니다. Swagger(OpenAPI) UI 연동을 위한 의존성 및 테스트 컨트롤러가 추가되었으며, 관련 엔티티와 예외 처리, 컨트롤러, 레포지토리에도 필드 및 메서드가 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant RecruitmentController
participant RecruitmentService
participant OrderClient
participant RecruitmentRepository
participant UserRepository
participant StoreRepository
participant RecruitmentParticipantRepository
participant OrderServer
User->>RecruitmentController: POST /api/v1/recruitments
RecruitmentController->>RecruitmentService: createRecruitment(dto)
RecruitmentService->>UserRepository: findById(userId)
RecruitmentService->>StoreRepository: findById(storeId)
RecruitmentService->>RecruitmentRepository: save(Recruitment)
RecruitmentService->>OrderClient: createOrder(OrderRequestDto)
OrderClient->>OrderServer: POST /order (OrderRequestDto)
OrderServer-->>OrderClient: { orderId }
OrderClient-->>RecruitmentService: orderId
RecruitmentService->>RecruitmentRepository: save(orderId)
RecruitmentService-->>RecruitmentController: 완료
User->>RecruitmentController: POST /api/v1/recruitments/{id}/join
RecruitmentController->>RecruitmentService: joinRecruitment(id, userId, orderDto)
RecruitmentService->>RecruitmentRepository: findById(id)
RecruitmentService->>UserRepository: findById(userId)
RecruitmentService->>RecruitmentParticipantRepository: existsByRecruitmentAndUser
RecruitmentService->>OrderClient: createOrder(orderDto)
OrderClient->>OrderServer: POST /order (OrderRequestDto)
OrderServer-->>OrderClient: { orderId }
OrderClient-->>RecruitmentService: orderId
RecruitmentService->>RecruitmentParticipantRepository: save(RecruitmentParticipant)
RecruitmentService-->>RecruitmentController: 완료
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: 5
🧹 Nitpick comments (6)
recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java (1)
1-7: 표준 JPA 저장소 패턴이 올바르게 구현되었습니다.기본 CRUD 및 페이징 작업을 위한 표준 Spring Data JPA 저장소 인터페이스가 정확하게 구현되었습니다. Order 엔티티와의 연동도 적절합니다.
향후 주문 관련 비즈니스 로직이 복잡해질 경우, 커스텀 쿼리 메소드 추가를 고려해보세요 (예:
findByRecruitmentId,findByUserId등).recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
34-34: 주석의 오타를 수정해주세요.주석에 오타가 있습니다. "카테고리 추가가" → "카테고리 추가"로 수정해주세요.
- //카테고리 추가가 + //카테고리 추가recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1)
6-14: 임베디드 엔티티 구조가 적절하게 구현되었습니다.
@Embeddable어노테이션과 Lombok을 사용한 구현이 깔끔합니다. 주문 내 메뉴 항목을 표현하기에 적절한 설계입니다.향후 개선을 위한 제안사항:
- 가격 필드 개선: 통화 처리의 정확성을 위해
BigDecimal사용을 권장합니다.- private int price; + private BigDecimal price;
- 검증 어노테이션 추가: 데이터 무결성을 위한 검증 추가를 고려해보세요.
@NotBlank private String name; @DecimalMin(value = "0.0", inclusive = false) private BigDecimal price;recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)
24-26: ElementCollection의 성능 영향을 검토해주세요.
@ElementCollection은 별도 테이블을 생성하고 항상 eager loading됩니다. 메뉴 아이템이 많아질 경우 성능 문제가 발생할 수 있습니다.대안으로 별도 엔티티로 분리하는 것을 고려해보세요:
- @ElementCollection - @CollectionTable(name = "order_menu_items", joinColumns = @JoinColumn(name = "order_id")) - private List<MenuItem> menuItems; // 메뉴 리스트 (내장 객체로 구성) + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List<OrderMenuItem> orderMenuItems; // 메뉴 리스트recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1)
7-14: 테스트 컨트롤러의 운영 환경 사용에 대해 고려해주세요.Swagger UI 테스트를 위한 컨트롤러가 적절하게 구현되었습니다. 그러나 이러한 테스트 전용 컨트롤러가 운영 환경에 배포되는 것이 적절한지 검토해주세요.
운영 환경에서는 이런 테스트 엔드포인트를 제거하는 것을 고려해보세요:
+@Profile("!prod") // 운영 환경에서는 비활성화 @RestController @RequestMapping("/swagger-test") public class SwaggerTestController {또는 별도의 설정을 통해 조건부로 활성화하는 방안도 있습니다.
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)
46-47: 불필요한 빈 줄을 제거해주세요.코드 가독성을 위해 불필요한 빈 줄들을 제거하는 것이 좋겠습니다.
} - - - return ResponseEntity
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
recruitment-service/build.gradle(2 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(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/exception/GlobalExceptionHandler.java(3 hunks)recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java(1 hunks)
🔇 Additional comments (6)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
35-36: 카테고리 필드가 적절하게 구현되었습니다.
@Column(nullable = false)어노테이션으로 데이터베이스 레벨에서 필수값을 보장하는 것이 좋습니다. DTO의@NotBlank검증과도 일치합니다.recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
31-32: 카테고리 필드가 일관성 있게 추가되었습니다.엔티티의 category 필드와 일치하며,
@NotBlank검증과 한국어 오류 메시지가 다른 필드들과 일관성을 유지하고 있습니다. 데이터 검증이 적절하게 구현되었습니다.recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)
24-26:Details
✅ Verification successful
MenuItem 클래스의 정의를 확인해주세요.
MenuItem클래스가@Embeddable로 정의되어 있는지 확인이 필요합니다.MenuItem 클래스가 올바르게 정의되었는지 확인하기 위해 다음 스크립트를 실행해주세요:
🏁 Script executed:
#!/bin/bash # MenuItem 클래스 정의 확인 ast-grep --pattern 'class MenuItem { $$$ }' # @Embeddable 어노테이션 확인 rg -A 10 "@Embeddable.*MenuItem"Length of output: 82
다음 스크립트로
MenuItem클래스 정의와@Embeddable어노테이션 존재 여부를 재확인해주세요:
🏁 Script executed:
#!/bin/bash # MenuItem.java 파일 경로 검색 file=$(fd -e java MenuItem.java) echo ">> MenuItem.java 위치: $file" # 클래스 선언 확인 rg -n "^\\s*(public\\s+)?class\\s+MenuItem" "$file" # @Embeddable 어노테이션 확인 (클래스 바로 위 주석 포함 3줄) rg -C3 "@Embeddable" "$file"Length of output: 695
MenuItem 클래스에 @embeddable 어노테이션이 올바르게 적용되어 있습니다
recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java파일에서@Embeddable이 확인되었으므로,@ElementCollection매핑이 정상적으로 동작합니다. 추가 수정은 필요하지 않습니다.recruitment-service/build.gradle (2)
31-31: OpenAPI 의존성 추가가 적절합니다.Swagger UI 통합을 위한 springdoc-openapi-starter-webmvc-ui 의존성이 적절하게 추가되었습니다. 버전 2.2.0은 Spring Boot 3.x와 호환됩니다.
3-4:Details
❓ Verification inconclusive
버전 다운그레이드에 대한 검증이 필요합니다.
Spring Boot와 dependency management 플러그인의 버전이 다운그레이드되었습니다. 이러한 변경사항이 필요한 이유와 보안 취약점 및 호환성 문제가 없는지 확인해주세요.
다음 스크립트를 실행하여 해당 버전들의 보안 취약점을 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash # Description: Check for security advisories for Spring Boot versions # Check for Spring Boot 3.1.5 security issues curl -s "https://api.github.com/repos/spring-projects/spring-boot/releases" | jq '[.[] | select(.tag_name | startswith("v3.1")) | {tag_name, published_at, body}] | .[0:5]' # Check for Spring Boot 3.4.5 security issues curl -s "https://api.github.com/repos/spring-projects/spring-boot/releases" | jq '[.[] | select(.tag_name | startswith("v3.4")) | {tag_name, published_at, body}] | .[0:5]'Length of output: 36620
버전 다운그레이드 보안·호환성 검증 필요
Spring Boot 및 의존성 관리 플러그인 버전이 하향 조정되어 보안 취약점 및 호환성 리스크가 발생할 수 있습니다. 공식 CVE/NVD 데이터베이스와 Spring Boot 3.1.5 릴리스 노트를 검토해 주세요.
· recruitment-service/build.gradle
- org.springframework.boot 플러그인 3.4.5 → 3.1.5 (3행)
- io.spring.dependency-management 플러그인 1.1.7 → 1.1.3 (4행)
· 보안·호환성 검증 권장
- NVD/CVE 데이터베이스에서 Spring Boot 3.1.5 취약점 확인
- Spring Boot 릴리스 노트(v3.1.5) 및 GitHub Releases(
?page=파라미터 활용) 검토recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)
39-44: Swagger 요청 처리 로직이 적절하게 구현되었습니다.Swagger 관련 요청에 대해 글로벌 예외 처리를 건너뛰는 로직이 올바르게 구현되었습니다. 이는 OpenAPI 문서화가 정상적으로 작동하도록 보장합니다.
다만 경로 검사 로직을 더욱 견고하게 만들 수 있습니다:
- 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에게 맡김 }
| private String title; | ||
| private String description; | ||
|
|
||
| private String status; // RECRUITING, CONFIRMED 등 |
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
상태 필드에 열거형(Enum) 사용을 권장합니다.
문자열 대신 열거형을 사용하여 타입 안전성을 향상시키고 유효하지 않은 상태 값 입력을 방지할 수 있습니다.
다음과 같이 열거형을 정의하고 사용하는 것을 권장합니다:
+@Enumerated(EnumType.STRING)
+private RecruitmentStatus status; // RECRUITING, CONFIRMED 등
-private String status; // RECRUITING, CONFIRMED 등별도의 enum 클래스를 생성하세요:
public enum RecruitmentStatus {
RECRUITING, CONFIRMED, CLOSED
}🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java
at line 30, replace the String type status field with an enum type to improve
type safety and prevent invalid values. Create a separate enum class named
RecruitmentStatus with constants RECRUITING, CONFIRMED, and CLOSED, then change
the status field's type to RecruitmentStatus and update any related code
accordingly.
| @ManyToOne | ||
| private Recruitment recruitment; // 어떤 모집글에 대한 주문인지 | ||
|
|
||
| @ManyToOne | ||
| private User user; // 누가 주문했는지 |
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
관계 매핑에 fetch 타입과 cascade 설정을 추가해주세요.
@ManyToOne 관계에서 fetch 타입과 cascade 옵션을 명시적으로 설정하면 성능과 데이터 일관성을 보장할 수 있습니다.
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "recruitment_id", nullable = false)
private Recruitment recruitment; // 어떤 모집글에 대한 주문인지
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
private User user; // 누가 주문했는지Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In recruitment-service/src/main/java/com/example/recruitment/entity/Order.java
around lines 18 to 22, the @ManyToOne annotations for recruitment and user
fields lack explicit fetch type and cascade settings. To improve performance and
data consistency, add fetch=FetchType.LAZY and appropriate cascade options
(e.g., CascadeType.PERSIST or CascadeType.MERGE) to both @ManyToOne annotations.
| @CollectionTable(name = "order_menu_items", joinColumns = @JoinColumn(name = "order_id")) | ||
| private List<MenuItem> menuItems; // 메뉴 리스트 (내장 객체로 구성) | ||
|
|
||
| private int totalPrice; // 총 가격 |
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
통화 처리를 위해 BigDecimal 사용을 고려해주세요.
int 타입은 통화 계산에서 정밀도 문제를 일으킬 수 있습니다. 금융 관련 계산에는 BigDecimal을 사용하는 것이 권장됩니다.
+import java.math.BigDecimal;
+
- private int totalPrice; // 총 가격
+ private BigDecimal totalPrice; // 총 가격📝 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 int totalPrice; // 총 가격 | |
| // at the top of the file, alongside the other imports | |
| import java.math.BigDecimal; | |
| // inside the Order entity | |
| - private int totalPrice; // 총 가격 | |
| + private BigDecimal totalPrice; // 총 가격 |
🤖 Prompt for AI Agents
In recruitment-service/src/main/java/com/example/recruitment/entity/Order.java
at line 28, the totalPrice field is currently an int, which can cause precision
issues in currency calculations. Change the type of totalPrice from int to
BigDecimal to ensure accurate financial computations. Also, update any related
code that sets or gets this field to handle BigDecimal properly.
| @Id @GeneratedValue | ||
| private Long id; |
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
ID 생성 전략을 명시적으로 지정해주세요.
@GeneratedValue에 전략을 명시하지 않으면 JPA 구현체에 따라 다른 전략이 사용될 수 있습니다. 명시적으로 전략을 지정하는 것이 좋습니다.
- @Id @GeneratedValue
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;📝 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.
| @Id @GeneratedValue | |
| private Long id; | |
| @Id @GeneratedValue(strategy = GenerationType.IDENTITY) | |
| private Long id; |
🤖 Prompt for AI Agents
In recruitment-service/src/main/java/com/example/recruitment/entity/Order.java
around lines 15 to 16, the @GeneratedValue annotation on the id field lacks an
explicit generation strategy. Specify the generation strategy explicitly by
adding the strategy attribute to @GeneratedValue, such as
GenerationType.IDENTITY or GenerationType.AUTO, to ensure consistent ID
generation behavior across different JPA implementations.
| @Entity | ||
| @Table(name = "orders") | ||
| @Getter @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class Order { |
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
검증 어노테이션과 감사 필드 추가를 고려해주세요.
엔티티에 데이터 검증과 생성/수정 시간 추적을 위한 필드가 없습니다.
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import java.time.LocalDateTime;
+
@Entity
@Table(name = "orders")
+@EntityListeners(AuditingEntityListener.class)
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class Order {
+
+ // 기존 필드들...
+
+ @CreatedDate
+ private LocalDateTime createdAt;
+
+ @LastModifiedDate
+ private LocalDateTime updatedAt;📝 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 | |
| @Table(name = "orders") | |
| @Getter @Setter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class Order { | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Positive; | |
| import org.springframework.data.annotation.CreatedDate; | |
| import org.springframework.data.annotation.LastModifiedDate; | |
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | |
| import java.time.LocalDateTime; | |
| @Entity | |
| @Table(name = "orders") | |
| @EntityListeners(AuditingEntityListener.class) | |
| @Getter @Setter | |
| @NoArgsConstructor | |
| @AllArgsConstructor | |
| public class Order { | |
| // 기존 필드들... | |
| @CreatedDate | |
| private LocalDateTime createdAt; | |
| @LastModifiedDate | |
| private LocalDateTime updatedAt; | |
| } |
🤖 Prompt for AI Agents
In recruitment-service/src/main/java/com/example/recruitment/entity/Order.java
around lines 8 to 13, the entity lacks validation annotations and audit fields
for tracking creation and modification timestamps. Add appropriate validation
annotations (e.g., @NotNull, @Size) to relevant fields to enforce data
integrity. Also, include audit fields such as createdAt and updatedAt with
annotations like @CreatedDate and @LastModifiedDate, and enable auditing by
adding @EntityListeners(AuditingEntityListener.class) to the entity 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.
Actionable comments posted: 10
♻️ Duplicate comments (1)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
31-31: 🛠️ Refactor suggestion상태 필드에 열거형(Enum) 사용을 권장합니다.
이전 리뷰에서 언급된 것처럼, 문자열 대신 열거형을 사용하여 타입 안전성을 향상시키고 유효하지 않은 상태 값 입력을 방지할 수 있습니다.
🧹 Nitpick comments (5)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
36-36: 메뉴 정보 필드에 대한 검증 고려사항주문 생성 시 메뉴 정보가 필수인 경우, 해당 필드에도 검증 어노테이션을 추가하는 것을 고려해보세요.
+@NotEmpty(message = "메뉴 정보는 필수입니다.") private List<OrderRequestDto.MenuDto> menus;recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
35-37: 카테고리 필드에 검증 제약 조건 추가를 고려하세요.데이터 무결성을 위해 길이 제한이나 허용되는 값에 대한 제약 조건을 추가하는 것을 권장합니다.
-@Column(nullable = false) +@Column(nullable = false, length = 50) +@Enumerated(EnumType.STRING) // 또는 허용된 카테고리 값들을 enum으로 정의 private String category;recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
80-91: 주문 생성 실패 시 에러 처리와 엔티티 생성 방식 개선이 필요합니다.- // 주문 서버에 주문 생성 요청 - orderDto.setGroupId(recruitmentId); - orderDto.setUserId(userId); - Long orderId = orderClient.createOrder(orderDto); - - // 참여자 등록 + 주문 ID 저장 - RecruitmentParticipant participant = new RecruitmentParticipant(); - participant.setRecruitment(recruitment); - participant.setUser(user); - participant.setOrderId(orderId); - participantRepository.save(participant); + // 주문 서버에 주문 생성 요청 + orderDto.setGroupId(recruitmentId); + orderDto.setUserId(userId); + + try { + Long orderId = orderClient.createOrder(orderDto); + + // 참여자 등록 + 주문 ID 저장 + RecruitmentParticipant participant = RecruitmentParticipant.builder() + .recruitment(recruitment) + .user(user) + .orderId(orderId) + .build(); + participantRepository.save(participant); + } catch (Exception e) { + log.error("주문 생성 실패. recruitmentId: {}, userId: {}", recruitmentId, userId, e); + throw new OrderCreationException("주문 생성에 실패했습니다.", e); + }recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (2)
51-55: 엔드포인트 설계를 더 명확하게 개선하세요.현재 방식은 Swagger 문서화 시 혼란을 줄 수 있습니다.
- // 상태별 조회 - @GetMapping(params = "status") - public List<Recruitment> getByStatus(@RequestParam String status) { + // 상태별 조회 + @GetMapping("/by-status") + public List<Recruitment> getByStatus(@RequestParam String status) {
1-168: 전역 예외 처리 및 일관된 응답 형식 구현을 권장합니다.현재 각 메서드에서 개별적으로 예외를 처리하고 있어 일관성이 부족합니다.
다음과 같은 전역 예외 처리기 구현을 고려하세요:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RecruitmentNotFoundException.class) public ResponseEntity<ErrorResponse> handleRecruitmentNotFound(RecruitmentNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("RECRUITMENT_NOT_FOUND", e.getMessage())); } @ExceptionHandler(ValidationException.class) public ResponseEntity<ErrorResponse> handleValidation(ValidationException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ErrorResponse("VALIDATION_ERROR", e.getMessage())); } }또한 일관된 응답 형식을 위한 wrapper 클래스도 고려하세요.
📜 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(4 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)
✅ Files skipped from review due to trivial changes (4)
- recruitment-service/src/main/resources/application.properties
- recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java
- recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
- recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java
🚧 Files skipped from review as they are similar to previous changes (1)
- recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
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/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)
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)
🔇 Additional comments (5)
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1)
24-25: 엔티티 참조 기반 중복 확인 메서드가 잘 구현되었습니다.Spring Data JPA의 명명 규칙을 올바르게 따르고 있으며, RecruitmentService에서 중복 참여 검증을 위해 적절히 활용되고 있습니다.
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (2)
32-33: 카테고리 필드 추가가 적절히 구현되었습니다.
@NotBlank검증 어노테이션을 사용하여 필수 입력값 검증이 올바르게 설정되었습니다.
35-46: 주문 통합을 위한 변환 메서드가 잘 설계되었습니다.OrderRequestDto로의 변환 로직이 명확하고 간단하게 구현되어 있으며, RecruitmentService에서 효과적으로 활용되고 있습니다.
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
17-26: 서비스 클래스 구조가 적절합니다.의존성 주입과 애노테이션 사용이 올바르게 구성되어 있습니다.
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
22-28: API 버저닝과 서비스 레이어 도입이 잘 적용되었습니다.관심사의 분리가 개선되었고, API 버전 관리가 추가되어 향후 변경에 유연하게 대응할 수 있습니다.
| @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을 직접 인스턴스화하는 대신 Spring 설정 클래스에서 빈으로 정의하여 의존성 주입을 받도록 개선할 수 있습니다.
Configuration 클래스에 다음과 같이 추가하세요:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
}그리고 OrderClient에서는 의존성 주입을 받도록 수정하세요:
-private final RestTemplate restTemplate = new RestTemplate();
+private final RestTemplate restTemplate;
+public OrderClient(RestTemplate restTemplate) {
+ this.restTemplate = 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.
To fix this, define a RestTemplate bean in a separate configuration class using
RestTemplateBuilder with appropriate timeouts, then modify OrderClient to
receive the RestTemplate via dependency injection instead of creating it
manually.
| 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()); | ||
| } | ||
| } | ||
| 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
타입 안전성 개선 및 더 구체적인 예외 처리가 필요합니다.
현재 Map 타입을 사용한 응답 처리는 타입 안전성이 부족하고, 런타임 에러 발생 가능성이 있습니다.
OrderResponseDto를 활용하여 타입 안전성을 개선하세요:
-ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+try {
+ ResponseEntity<OrderResponseDto> response = restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class);
+
+ if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
+ return response.getBody().getOrderId();
+ }
+
+ throw new OrderCreationException("주문 응답이 유효하지 않습니다.");
+
+} catch (RestClientException e) {
+ throw new OrderCreationException("주문 서버 통신 실패: " + e.getMessage(), e);
+}OrderCreationException과 같은 커스텀 예외를 정의하여 더 구체적인 에러 처리를 구현하는 것을 권장합니다.
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 25 to 41, the current use of raw Map for response handling lacks
type safety and risks runtime errors. Refactor the code to use a strongly typed
OrderResponseDto class that matches the expected response structure to improve
type safety. Additionally, define and throw a custom exception such as
OrderCreationException instead of generic RuntimeException to provide more
specific error handling and clearer error semantics.
| @Column(name = "order_id") | ||
| private Long orderId; | ||
|
|
||
| @ElementCollection | ||
| private List<Long> orderIds = new ArrayList<>(); | ||
|
|
||
| public void addOrderId(Long orderId) { | ||
| this.orderIds.add(orderId); | ||
| } |
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
주문 ID 필드 설계를 재검토해야 합니다.
orderId와 orderIds 필드가 동시에 존재하는 것이 혼란을 야기할 수 있습니다. 모집글당 하나의 주문 ID만 필요한지, 아니면 여러 주문을 지원해야 하는지 명확히 하여 설계를 단순화하는 것을 권장합니다.
RecruitmentService의 구현을 보면 orderId 필드만 사용되고 있으므로, 다음 중 하나를 선택하세요:
옵션 1: 단일 주문 ID만 지원하는 경우
-@Column(name = "order_id")
-private Long orderId;
-
-@ElementCollection
-private List<Long> orderIds = new ArrayList<>();
-
-public void addOrderId(Long orderId) {
- this.orderIds.add(orderId);
-}
+@Column(name = "order_id")
+private Long orderId;옵션 2: 다중 주문 ID를 지원하는 경우
-@Column(name = "order_id")
-private Long orderId;
-
-@ElementCollection
-private List<Long> orderIds = new ArrayList<>();
+@ElementCollection
+@Column(name = "order_id")
+private List<Long> orderIds = new ArrayList<>();📝 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 = "order_id") | |
| private Long orderId; | |
| @ElementCollection | |
| private List<Long> orderIds = new ArrayList<>(); | |
| public void addOrderId(Long orderId) { | |
| this.orderIds.add(orderId); | |
| } | |
| @Column(name = "order_id") | |
| private Long orderId; |
| @Column(name = "order_id") | |
| private Long orderId; | |
| @ElementCollection | |
| private List<Long> orderIds = new ArrayList<>(); | |
| public void addOrderId(Long orderId) { | |
| this.orderIds.add(orderId); | |
| } | |
| @ElementCollection | |
| @Column(name = "order_id") | |
| private List<Long> orderIds = new ArrayList<>(); | |
| public void addOrderId(Long orderId) { | |
| this.orderIds.add(orderId); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java
around lines 39 to 47, there is confusion due to having both orderId and
orderIds fields. Decide whether to support a single order ID or multiple order
IDs per recruitment entity. If only one order ID is needed, remove the orderIds
list and related methods. If multiple order IDs are required, remove the single
orderId field and ensure all code uses the orderIds list consistently.
| User user = userRepository.findById(dto.getUserId()) | ||
| .orElseThrow(() -> new RuntimeException("사용자 없음")); | ||
| Store store = storeRepository.findById(dto.getStoreId()) | ||
| .orElseThrow(() -> new RuntimeException("가게 없음")); | ||
|
|
||
| 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()); |
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대신 도메인 특화 예외 클래스 사용을 권장합니다.- "RECRUITING" 같은 상태값은 상수나 enum으로 관리하세요.
- setter 대신 빌더 패턴이나 정적 팩토리 메서드 사용을 고려하세요.
- 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 UserNotFoundException("사용자를 찾을 수 없습니다. ID: " + dto.getUserId()));
+ Store store = storeRepository.findById(dto.getStoreId())
+ .orElseThrow(() -> new StoreNotFoundException("가게를 찾을 수 없습니다. ID: " + dto.getStoreId()));상태값을 enum으로 관리하는 예시:
public enum RecruitmentStatus {
RECRUITING("RECRUITING"),
CONFIRMED("CONFIRMED"),
FAILED("FAILED"),
ACCEPTED("ACCEPTED"),
DELIVERED("DELIVERED");
private final String value;
// constructor, getter...
}🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 36 to 48, replace the generic RuntimeException with domain-specific
exception classes for user and store not found cases. Manage the recruitment
status using an enum instead of a hardcoded string "RECRUITING". Refactor the
Recruitment object creation to use a builder pattern or a static factory method
instead of multiple setter calls for better immutability and clarity.
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | ||
| .orElseThrow(() -> new RuntimeException("모집 없음")); | ||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(() -> new RuntimeException("사용자 없음")); | ||
|
|
||
| boolean alreadyJoined = participantRepository.existsByRecruitmentAndUser(recruitment, user); | ||
| if (alreadyJoined) { | ||
| throw new RuntimeException("이미 참여한 모집입니다."); | ||
| } |
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
예외 처리 개선이 필요합니다.
도메인 특화 예외를 사용하여 더 명확한 에러 처리를 구현하세요.
- Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
- .orElseThrow(() -> new RuntimeException("모집 없음"));
- User user = userRepository.findById(userId)
- .orElseThrow(() -> new RuntimeException("사용자 없음"));
+ Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
+ .orElseThrow(() -> new RecruitmentNotFoundException("모집글을 찾을 수 없습니다. ID: " + recruitmentId));
+ User user = userRepository.findById(userId)
+ .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다. ID: " + userId));
- throw new RuntimeException("이미 참여한 모집입니다.");
+ throw new DuplicateParticipationException("이미 참여한 모집입니다. recruitmentId: " + recruitmentId + ", userId: " + userId);📝 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.
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | |
| .orElseThrow(() -> new RuntimeException("모집 없음")); | |
| User user = userRepository.findById(userId) | |
| .orElseThrow(() -> new RuntimeException("사용자 없음")); | |
| boolean alreadyJoined = participantRepository.existsByRecruitmentAndUser(recruitment, user); | |
| if (alreadyJoined) { | |
| throw new RuntimeException("이미 참여한 모집입니다."); | |
| } | |
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId) | |
| .orElseThrow(() -> new RecruitmentNotFoundException("모집글을 찾을 수 없습니다. ID: " + recruitmentId)); | |
| User user = userRepository.findById(userId) | |
| .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다. ID: " + userId)); | |
| boolean alreadyJoined = participantRepository.existsByRecruitmentAndUser(recruitment, user); | |
| if (alreadyJoined) { | |
| throw new DuplicateParticipationException("이미 참여한 모집입니다. recruitmentId: " | |
| + recruitmentId + ", userId: " + userId); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 70 to 78, replace the generic RuntimeException with domain-specific
custom exceptions for cases like "모집 없음", "사용자 없음", and "이미 참여한 모집입니다." Define
and throw meaningful exceptions such as RecruitmentNotFoundException,
UserNotFoundException, and ParticipantAlreadyExistsException to improve clarity
and error handling.
| recruitmentRepository.save(recruitment); | ||
|
|
||
| // 주문 서버에 주문 생성 요청 | ||
| OrderRequestDto orderDto = dto.toOrderRequestDto(); | ||
| orderDto.setGroupId(recruitment.getId()); | ||
| Long orderId = orderClient.createOrder(orderDto); | ||
|
|
||
| // orderId를 모집글에 저장 | ||
| recruitment.setOrderId(orderId); | ||
| 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.
🛠️ Refactor suggestion
중복 저장 및 트랜잭션 처리 개선이 필요합니다.
현재 구현은 recruitment를 두 번 저장하고 있으며, 외부 API 호출 실패 시 불완전한 데이터가 남을 수 있습니다.
- 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.setStatus(RecruitmentStatus.RECRUITING.getValue());
+ recruitment.setCategory(dto.getCategory());
+
+ // 주문 서버에 주문 생성 요청
+ OrderRequestDto orderDto = dto.toOrderRequestDto();
+
+ try {
+ // 임시 저장하여 ID 생성
+ recruitment = recruitmentRepository.save(recruitment);
+ orderDto.setGroupId(recruitment.getId());
+
+ Long orderId = orderClient.createOrder(orderDto);
+ recruitment.setOrderId(orderId);
+ recruitmentRepository.save(recruitment);
+ } catch (Exception e) {
+ // 주문 생성 실패 시 로깅
+ log.error("주문 생성 실패. recruitmentId: {}", recruitment.getId(), e);
+ throw new OrderCreationException("주문 생성에 실패했습니다.", e);
+ }🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 49 to 59, the recruitment entity is saved twice, which is redundant
and risks data inconsistency if the external orderClient.createOrder call fails.
Refactor to save the recruitment entity only once after successfully obtaining
the orderId from the external API. Additionally, ensure the method is wrapped in
a transaction to maintain atomicity, so that either all operations succeed or
none are persisted.
| //모집글 참여 (Order 서버에 주문 생성 포함) | ||
| @PostMapping("/{recruitmentId}/join") | ||
| public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, | ||
| @RequestParam Long userId, | ||
| @RequestBody OrderRequestDto orderRequestDto) { | ||
| recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto); | ||
| 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
API 설계 개선이 필요합니다.
userId를 쿼리 파라미터로 받는 것보다 인증 정보에서 추출하는 것이 보안상 더 안전합니다.
- @PostMapping("/{recruitmentId}/join")
- public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
- @RequestParam Long userId,
- @RequestBody OrderRequestDto orderRequestDto) {
- recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
+ @PostMapping("/{recruitmentId}/join")
+ public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
+ @RequestBody OrderRequestDto orderRequestDto,
+ @AuthenticationPrincipal UserDetails userDetails) {
+ Long userId = getUserIdFromAuth(userDetails);
+ recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);향후 Spring Security를 통한 인증 구현을 고려하세요.
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 36 to 43, the userId is currently received as a request parameter,
which is less secure. Modify the method to extract the userId from the
authentication principal or security context instead of accepting it as a query
parameter. This involves integrating Spring Security or a similar authentication
mechanism to obtain the authenticated user's ID securely within the controller
method.
| if (dto.getStoreId() != null) { | ||
| recruitment.setStore(recruitment.getStore()); | ||
| } |
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.
치명적인 버그: Store 업데이트가 작동하지 않습니다.
현재 코드는 자기 자신을 설정하고 있어 아무런 변경이 일어나지 않습니다.
if (dto.getStoreId() != null) {
- recruitment.setStore(recruitment.getStore());
+ Store newStore = storeRepository.findById(dto.getStoreId())
+ .orElseThrow(() -> new StoreNotFoundException("가게를 찾을 수 없습니다. ID: " + dto.getStoreId()));
+ recruitment.setStore(newStore);
}이를 위해 컨트롤러에 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.
| if (dto.getStoreId() != null) { | |
| recruitment.setStore(recruitment.getStore()); | |
| } | |
| if (dto.getStoreId() != null) { | |
| Store newStore = storeRepository.findById(dto.getStoreId()) | |
| .orElseThrow(() -> new StoreNotFoundException("가게를 찾을 수 없습니다. ID: " + dto.getStoreId())); | |
| recruitment.setStore(newStore); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 161 to 163, the code incorrectly sets
recruitment.setStore(recruitment.getStore()), which does not update the store.
To fix this, inject StoreRepository into the controller or delegate the store
update logic to the service layer, then fetch the Store entity by
dto.getStoreId() and set it on recruitment properly.
| // 모집글 상세 조회 | ||
| @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(); | ||
| public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) { | ||
| Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow(); | ||
|
|
||
| boolean alreadyJoined = participantRepository | ||
| .findByUserIdAndRecruitmentId(userId, recruitmentId) | ||
| .isPresent(); | ||
|
|
||
| if (alreadyJoined) { | ||
| return ResponseEntity.badRequest().body("이미 참여한 모집입니다."); | ||
| } | ||
| List<RecruitmentParticipant> participants = | ||
| participantRepository.findByRecruitmentId(recruitmentId); | ||
| List<User> participantUsers = participants.stream() | ||
| .map(RecruitmentParticipant::getUser) | ||
| .toList(); | ||
|
|
||
| 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(participantUsers); | ||
|
|
||
| return ResponseEntity.ok(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.
💡 Verification agent
🧩 Analysis chain
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(participantUsers);
+ 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();N+1 쿼리 문제 확인:
🏁 Script executed:
#!/bin/bash
# RecruitmentParticipantRepository에서 fetch join 사용 여부 확인
rg -A 5 "findByRecruitmentId" --glob "*.java"Length of output: 1776
🏁 Script executed:
#!/bin/bash
# RecruitmentDetailDto 클래스 정의 및 Lombok @Builder 어노테이션 확인
rg -A 5 "class RecruitmentDetailDto" --glob "*.java"
rg "@Builder" --glob "*RecruitmentDetailDto.java"Length of output: 814
Builder 패턴 적용 및 N+1 쿼리 최적화 필요
RecruitmentDetailDto에 Lombok @builder가 적용되어 있지 않아 제안한 builder 생성 코드가 동작하지 않으며, findByRecruitmentId 호출 시 RecruitmentParticipant.user가 LAZY 로딩일 경우 participants 수만큼 추가 쿼리가 발생할 수 있습니다.
• RecruitmentDetailDto 클래스(recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java)
– Lombok @builder, @AllArgsConstructor, @NoArgsConstructor 어노테이션 추가 또는 직접 Builder 구현
예시:
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentDetailDto { … }• RecruitmentParticipantRepository 인터페이스(recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java)
– fetch join을 사용해 N+1 문제 방지
@Query("SELECT p FROM RecruitmentParticipant p JOIN FETCH p.user WHERE p.recruitment.id = :recruitmentId")
List<RecruitmentParticipant> findByRecruitmentIdFetchUser(@Param("recruitmentId") Long recruitmentId);또는 Spring Data JPA EntityGraph 사용
위 사항을 반영해 DTO 생성과 데이터 조회 방식을 개선해주세요.
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
lines 57-79, refactor the DTO creation to use the Lombok @Builder pattern by
first adding @Builder, @NoArgsConstructor, and @AllArgsConstructor annotations
to the RecruitmentDetailDto class. Also, modify the participantRepository query
to use a fetch join to eagerly load the User entities and avoid N+1 queries by
defining a new method like findByRecruitmentIdFetchUser with a JPQL query using
JOIN FETCH. Then update the controller to call this new repository method and
build the RecruitmentDetailDto using the builder for cleaner and more efficient
code.
| //모집글 생성 (Order 서버에 주문 생성 포함) | ||
| @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"); | ||
| public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | ||
| recruitmentService.createRecruitment(dto); | ||
| 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
RESTful API 모범 사례를 따르도록 개선이 필요합니다.
생성된 리소스의 정보를 반환하고 적절한 HTTP 상태 코드를 사용하세요.
- public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
- recruitmentService.createRecruitment(dto);
- return ResponseEntity.ok("모집글 생성 완료");
+ public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
+ Long recruitmentId = recruitmentService.createRecruitment(dto);
+ return ResponseEntity.created(URI.create("/api/v1/recruitments/" + recruitmentId))
+ .body(Map.of("id", recruitmentId, "message", "모집글이 생성되었습니다"));서비스 메서드도 생성된 ID를 반환하도록 수정이 필요합니다.
📝 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.
| //모집글 생성 (Order 서버에 주문 생성 포함) | |
| @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"); | |
| public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | |
| recruitmentService.createRecruitment(dto); | |
| return ResponseEntity.ok("모집글 생성 완료"); | |
| } | |
| //모집글 생성 (Order 서버에 주문 생성 포함) | |
| @PostMapping | |
| public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) { | |
| Long recruitmentId = recruitmentService.createRecruitment(dto); | |
| return ResponseEntity.created(URI.create("/api/v1/recruitments/" + recruitmentId)) | |
| .body(Map.of("id", recruitmentId, "message", "모집글이 생성되었습니다")); | |
| } |
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 29 to 34, the createRecruitment method currently returns a generic
success message with HTTP 200. Modify the recruitmentService.createRecruitment
method to return the ID of the newly created resource, then update the
controller to return this ID in the response body with HTTP status 201 Created,
following RESTful best practices.
springdoc-openapi-starter-webmvc-ui 적용, 모집글 Controller 기반 Swagger UI 확인 완료
Summary by CodeRabbit
신규 기능
기능 개선
환경 설정
버그 수정 및 기타