Sisc1 121 be 베팅시 획득 포인트 베팅 인원수 추가#128
Hidden character warning
Conversation
전체 요약 (Walkthrough)이 PR은 베팅 API 응답을 엔티티 타입에서 데이터 전송 객체(DTO)로 변환합니다. BetRoundResponse 및 UserBetResponse DTO를 도입하고, 베팅 통계 추적을 BetRound 엔티티에 추가하며, 컨트롤러와 서비스를 DTO 기반 응답으로 업데이트합니다. 변경사항 (Changes)
시퀀스 다이어그램 (Sequence Diagram)sequenceDiagram
participant Client as 클라이언트
participant Controller as BettingController
participant Service as BettingService
participant Repo as BetRoundRepository
participant DB as 데이터베이스
Client->>Controller: POST /user-bet (사용자 베팅 요청)
Controller->>Service: postUserBet(userId, request)
Service->>Repo: 베팅 통계 업데이트
Repo->>DB: incrementUpStats() 또는 incrementDownStats()
DB-->>Repo: 업데이트 완료
Service->>Service: UserBet 엔티티 저장
Service->>Service: UserBet을 UserBetResponse로 변환
Service-->>Controller: UserBetResponse 반환
Controller-->>Client: 200 OK + UserBetResponse DTO
Client->>Controller: GET /bet-round (현재 베팅 라운드 조회)
Controller->>Service: getActiveRoundResponse()
Service->>Repo: 활성 라운드 쿼리
Repo-->>Service: BetRound 엔티티 반환
Service->>Service: BetRound을 BetRoundResponse로 변환
alt 라운드 존재
Service-->>Controller: Optional\<BetRoundResponse\>
Controller-->>Client: 200 OK + BetRoundResponse DTO
else 라운드 없음
Service-->>Controller: Optional.empty()
Controller-->>Client: 404 Not Found
end
코드 리뷰 예상 시간 (Estimated code review effort)🎯 3 (Moderate) | ⏱️ ~25 분 주의 사항:
관련 PR (Possibly related PRs)
추천 리뷰어 (Suggested reviewers)
시를 읽어주세요 (Poem)
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java (1)
80-91: JPA 엔티티 패키지 중복 등록 제거 제안
"org.sejongisc.backend.betting.entity"가 두 번 등록되어 있어서 동작에는 문제 없지만 혼란을 줄 수 있습니다. 한 번만 두는 쪽이 깔끔할 것 같습니다.- "org.sejongisc.backend.stock.entity", - "org.sejongisc.backend.template.entity", - "org.sejongisc.backend.betting.entity", - "org.sejongisc.backend.user.entity" + "org.sejongisc.backend.stock.entity", + "org.sejongisc.backend.template.entity", + "org.sejongisc.backend.user.entity"backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java (1)
80-97: 베팅 통계 컬럼 DDL 및 예상 배당률 계산 로직 검토 제안
- 통계 컬럼
upBetCount/downBetCount는integer default 0이고,upTotalPoints/downTotalPoints는nullable = false만 설정되어 있습니다.- 기존 테이블에 데이터가 있는 상태에서 Hibernate
ddl-auto=update또는 별도 마이그레이션을 사용한다면,NOT NULL컬럼 추가 시 초기값(0) 세팅이 잘 되는지 한 번 확인해 보시는 게 좋겠습니다. 필요하면columnDefinition = "bigint default 0"같은 형태로 명시하는 것도 고려 가능합니다.getEstimatedRewardMultiplier
- 현재는
(double) totalPool / optionPool을BigDecimal.valueOf로 감싸고 있어 금액/배당 계산 관점에서 이진 부동소수점 오차가 그대로 들어올 수 있습니다.BigDecimal연산(BigDecimal.valueOf(totalPool).divide(..., scale, RoundingMode.HALF_UP))으로 바꾸는 것을 추천드립니다.optionPool == 0인 경우(해당 옵션에 아직 베팅이 아무도 없는 경우) 항상1.0을 반환하는데, 기획 의도가 “기본 배당 배율(baseMultiplier) 적용”인지, “극단적인 고배당”인지 등 도메인 정의와 맞는지 한 번만 더 확인해 주세요. 현재는baseMultiplier필드는 이 메서드에서 사용되지 않습니다.예시(정확한 scale/반올림은 기획에 맞춰 조정 필요):
public BigDecimal getEstimatedRewardMultiplier(BetOption option) { long totalPool = upTotalPoints + downTotalPoints; long optionPool = (option == BetOption.RISE) ? upTotalPoints : downTotalPoints; if (optionPool == 0) { return BigDecimal.ONE; // 또는 baseMultiplier 등 기획에 맞게 } return BigDecimal.valueOf(totalPool) .divide(BigDecimal.valueOf(optionPool), 4, RoundingMode.HALF_UP); }Also applies to: 118-127
backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetResponse.java (1)
14-39: isCorrect 필드 매핑 및 이름 일관성 확인 필요전체적인 Entity → DTO 매핑은 자연스럽습니다만, 아래 두 부분은 한 번 더 점검해 주시면 좋겠습니다.
isCorrect필드 매핑:
- DTO 필드:
private Boolean isCorrect;- 매핑:
.isCorrect(bet.isCollect())- 주석에는 “boolean 타입의 Getter는 isCorrect()”라고 되어 있어서, 엔티티 쪽 필드/게터 이름이
collect인지correct인지 혼재되어 보입니다. 의도한 필드명이 무엇인지 확인 후, 필요하면bet.isCorrect()또는 DTO 필드명을 정리해 주세요.- Lombok @Getter와 Boolean 필드
Boolean isCorrect는 Lombok 기준으로 게터 이름이getIsCorrect()가 됩니다. Jackson 등에서 JSON 필드를isCorrect로 유지하는 것이 목표라면, 현재 설정으로 문제가 없는지(혹은@JsonProperty("isCorrect")등의 명시가 필요한지)도 한 번만 체크해 보시면 좋겠습니다.backend/src/main/java/org/sejongisc/backend/betting/dto/BetRoundResponse.java (1)
28-47: expected*Reward 필드 의미와 배당 계산 방식 정합성 확인
- 주석에는 “예상 획득 포인트 (100포인트 베팅 기준 예시)”라고 되어 있지만, 실제 값은
round.getEstimatedRewardMultiplier(...)그대로라서 “포인트”가 아니라 “배당 배율”에 더 가깝습니다.
- 정말 100포인트 기준 예상 획득 포인트를 내려주려면
multiplier.multiply(BigDecimal.valueOf(100))형태가 되어야 하고,- 현재 구현대로 multiplier만 내리고 싶다면 필드명을
expectedUpMultiplier등으로 바꾸는 게 더 명확할 수 있습니다.getEstimatedRewardMultiplier가 내부에서 double 연산을 사용하고 있으므로, 금액/배당 값으로 그대로 노출할 때BigDecimal연산으로 옮기는 것도 고려해 보시는 게 좋겠습니다.기획에서 이 필드들이 “배당률”인지 “예상 포인트”인지 명확히 정해진 뒤, 이름/계산식을 맞춰 주시면 혼란이 줄어들 것 같습니다.
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (2)
89-99: getAllMyBets의 DTO 변환 및 N+1 가능성
@Transactional(readOnly = true)안에서UserBet리스트를 조회 후UserBetResponse::from으로 DTO로 변환하는 구조는 Lazy 로딩과LazyInitializationException방지 측면에서 좋습니다.다만
UserBetResponse.from에서bet.getRound()를 접근하므로,findAllByUserIdOrderByRound_SettleAtDesc가JOIN FETCH round또는@EntityGraph(attributePaths = "round")로 구현되어 있지 않다면 사용자별 조회 시 N+1 쿼리가 발생할 수 있습니다. 해당 리포지토리 메서드 구현을 확인하시고, 필요시 fetch join/EntityGraph 적용을 고려해 주세요.
138-198: postUserBet 흐름은 명확하나 몇 가지 동작 조건 재확인 제안전반적인 플로우(라운드 조회 → 중복 검증 → 상태 검증 → stake 계산 → 통계 업데이트 → 포인트 차감 → UserBet 저장 및 DTO 변환)는 명확하고,
통계 업데이트를 JPQLUPDATE로 처리해 동시성(Lost Update)을 줄인 점도 좋습니다. 다만 몇 가지 부분은 의도와 맞는지 한 번 더 확인해 보시면 좋겠습니다.
- 무료 베팅의 인원수/포인트 집계
stake = isFree ? 0 : userBetRequest.getStakePoints();- 무료 베팅도
incrementUpStats/incrementDownStats가 호출되기 때문에,upBetCount/downBetCount에는 인원 수가 포함되지만upTotalPoints/downTotalPoints는 0만 더해집니다.- 요구사항이 “무료/유료 구분 없이 참여 인원수를 보고 싶다”라면 현재 구현이 맞고, “유료 베팅 인원만 보고 싶다”라면
if (!userBetRequest.isFree())안에서만 통계 업데이트를 하는 게 맞습니다.- 유효성 검증과 통계 업데이트 순서
- 현재는 통계 업데이트(라인 159~163) 후에
isStakePointsValid()를 검사합니다.CustomException이 RuntimeException이라면 전체 트랜잭션이 롤백되어 최종 DB 상태는 깨지지 않겠지만, 불필요한 UPDATE를 줄이려면 아래처럼 포인트 유효성 검증을 통계 업데이트 전에 두는 걸 추천드립니다.- // 4. 베팅 포인트(stake) 결정 - int stake = userBetRequest.isFree() ? 0 : userBetRequest.getStakePoints(); - - // 5. 라운드 통계 업데이트 (동시성 해결: DB 직접 업데이트) - if (userBetRequest.getOption() == BetOption.RISE) { - betRoundRepository.incrementUpStats(betRound.getBetRoundID(), stake); - } else { - betRoundRepository.incrementDownStats(betRound.getBetRoundID(), stake); - } - - // 6. 포인트 차감 및 이력 생성 (유료 베팅인 경우) - if (!userBetRequest.isFree()) { - if (!userBetRequest.isStakePointsValid()) { - throw new CustomException(ErrorCode.BET_POINT_TOO_LOW); - } + // 4. 베팅 포인트(stake) 결정 및 유효성 검증 + int stake = userBetRequest.isFree() ? 0 : userBetRequest.getStakePoints(); + if (!userBetRequest.isFree() && !userBetRequest.isStakePointsValid()) { + throw new CustomException(ErrorCode.BET_POINT_TOO_LOW); + } + + // 5. 라운드 통계 업데이트 (동시성 해결: DB 직접 업데이트) + if (userBetRequest.getOption() == BetOption.RISE) { + betRoundRepository.incrementUpStats(betRound.getBetRoundID(), stake); + } else { + betRoundRepository.incrementDownStats(betRound.getBetRoundID(), stake); + } + + // 6. 포인트 차감 및 이력 생성 (유료 베팅인 경우) + if (!userBetRequest.isFree()) { pointHistoryService.createPointHistory( userId, -stake, // 포인트 차감backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (2)
95-101: postUserBet가 UserBetResponse DTO를 반환하도록 변경된 점이 요구사항과 잘 맞습니다
principal.getUserId()와UserBetRequest를 받아UserBetResponse를 그대로 반환하는 구조라, 베팅 시 획득 포인트·베팅 인원수 등 추가 정보가 응답에 자연스럽게 포함될 수 있어 보입니다. 기존 200 OK 상태 코드는 유지되므로 클라이언트의 HTTP 레벨 호환성도 깨지지 않습니다.
추가로, 리소스를 새로 생성하는 성격을 더 명확히 드러내고 싶다면 나중에201 Created+ Location 헤더를 고려해볼 수 있겠지만, 현재 구현도 충분히 실용적입니다.
139-145: 내 베팅 이력을 UserBetResponse 리스트로 반환하는 일관성이 좋습니다
bettingService.getAllMyBets(principal.getUserId())에서 바로List<UserBetResponse>를 받아ResponseEntity.ok로 감싸는 형태라, POST 응답과 동일한 DTO를 공유하면서 프론트엔드가 같은 모델로 단건/목록을 모두 처리하기 용이합니다.
추가로,/bet-rounds/history는 여전히List<BetRound>엔티티를 직접 반환하고 있으니, 외부 노출 모델의 일관성을 더 강화하고 싶다면 추후BetRoundResponse기반으로 맞추는 것도 고려해볼 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java(5 hunks)backend/src/main/java/org/sejongisc/backend/betting/dto/BetRoundResponse.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetResponse.java(1 hunks)backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java(3 hunks)backend/src/main/java/org/sejongisc/backend/betting/repository/BetRoundRepository.java(2 hunks)backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java(5 hunks)backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java(1 hunks)
🔇 Additional comments (5)
backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java (1)
48-51: status 기본값 빌더 반영 좋습니다
@Builder.Default로 빌더 사용 시에도status=false가 유지되어, 실수로 열린 상태로 생성되는 문제를 막아줘서 좋습니다.backend/src/main/java/org/sejongisc/backend/betting/repository/BetRoundRepository.java (1)
24-32: 통계 증가용 @Modifying 쿼리 트랜잭션 사용 확인
incrementUpStats/incrementDownStats를 JPQLUPDATE+@Modifying(clearAutomatically = true)로 구현해서 Lost Update 문제를 회피한 점은 좋습니다.다만 이 메서드들은 반드시
@Transactional컨텍스트 안에서 호출되어야 하므로, 현재처럼BettingService.postUserBet에서만 쓰이는지, 혹은 향후 스케줄러 등 다른 곳에서 사용할 때도 서비스/상위 계층에@Transactional이 붙어 있는지 한 번 더 확인 부탁드립니다.backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java (1)
200-204: getActiveRoundResponse 래핑 메서드 구조 적절엔티티를 그대로 노출하던 메서드에서 DTO(
BetRoundResponse)로 감싸는 전용 메서드를 분리한 구조는 Controller 단 책임 분리에 도움이 되고, Lazy 로딩 이슈도 줄여줘서 좋습니다. 별다른 문제 없어 보입니다.backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java (2)
10-21: 베팅용 DTO 임포트 추가 방향 적절합니다컨트롤러가 엔티티 대신
BetRoundResponse,UserBetResponseDTO를 반환하도록 바뀐 흐름과 잘 맞고, 두 DTO 모두 이 클래스 안에서 실제로 사용되고 있어 불필요한 임포트도 없습니다.
52-61: Optional를 활용한 200/404 분기 처리가 명확합니다
bettingService.getActiveRoundResponse(scope)에서Optional<BetRoundResponse>를 받아map(ResponseEntity::ok)/orElseGet(ResponseEntity.notFound().build())로 처리한 구조가 간결하고, 기존 스펙(활성 라운드 없으면 404)과도 정확히 일치합니다. 컨트롤러 레벨에서 불필요한 null 체크가 사라져 가독성도 좋아졌습니다.
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.