Skip to content

Sisc1 121 be 베팅시 획득 포인트 베팅 인원수 추가#128

Merged
discipline24 merged 6 commits intomainfrom
SISC1-121-BE-베팅시-획득-포인트-베팅-인원수-추가
Nov 20, 2025

Hidden character warning

The head ref may contain hidden characters: "SISC1-121-BE-\ubca0\ud305\uc2dc-\ud68d\ub4dd-\ud3ec\uc778\ud2b8-\ubca0\ud305-\uc778\uc6d0\uc218-\ucd94\uac00"
Merged

Sisc1 121 be 베팅시 획득 포인트 베팅 인원수 추가#128
discipline24 merged 6 commits intomainfrom
SISC1-121-BE-베팅시-획득-포인트-베팅-인원수-추가

Conversation

@cksdid202
Copy link
Contributor

@cksdid202 cksdid202 commented Nov 20, 2025

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 베팅 라운드 통계 정보 추가 (상향/하향 베팅 수, 포인트, 예상 수익률)
    • 개선된 API 응답 구조로 더 명확한 베팅 정보 제공
  • Bug Fixes

    • 활성 베팅 라운드 없을 시 적절한 오류 응답 처리
  • Refactor

    • 베팅 관련 API 응답 데이터 구조 최적화

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

전체 요약 (Walkthrough)

이 PR은 베팅 API 응답을 엔티티 타입에서 데이터 전송 객체(DTO)로 변환합니다. BetRoundResponse 및 UserBetResponse DTO를 도입하고, 베팅 통계 추적을 BetRound 엔티티에 추가하며, 컨트롤러와 서비스를 DTO 기반 응답으로 업데이트합니다.

변경사항 (Changes)

응집 / 파일(들) 변경 요약
DTO 도입
backend/src/main/java/org/sejongisc/backend/betting/dto/BetRoundResponse.java, backend/src/main/java/org/sejongisc/backend/betting/dto/UserBetResponse.java
새로운 DTO 클래스 추가: BetRoundResponse 및 UserBetResponse. Lombok 주석 처리(@Getter, @Builder)하고 static factory 메서드 from()으로 엔티티에서 매핑
컨트롤러 레이어 업데이트
backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java
메서드 반환 타입 변경: getTodayBetRound (BetRound → BetRoundResponse), postUserBet (UserBet → UserBetResponse), getAllUserBets (List<UserBet> → List<UserBetResponse>). 서비스 호출 변경 및 404 응답 추가
엔티티 및 통계
backend/src/main/java/org/sejongisc/backend/betting/entity/BetRound.java
베팅 통계 필드 추가: upBetCount, upTotalPoints, downBetCount, downTotalPoints (@Builder.Default 포함). getEstimatedRewardMultiplier(BetOption option) 메서드 추가
리포지토리 확장
backend/src/main/java/org/sejongisc/backend/betting/repository/BetRoundRepository.java
원자적 업데이트 메서드 추가: incrementUpStats(UUID, long) 및 incrementDownStats(UUID, long). @Modifying@Query 주석 사용
서비스 레이어 업데이트
backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java
메서드 반환 타입 변경: getAllMyBets (List<UserBet> → List<UserBetResponse>), postUserBet (UserBet → UserBetResponse). 새 메서드 추가: getActiveRoundResponse(). DTO 매핑 및 통계 업데이트 로직 통합
설정 업데이트
backend/src/main/java/org/sejongisc/backend/common/config/PrimaryDataSourceConfig.java
JPA 엔티티 스캔 패키지에 betting.entity 추가 (중복 항목 포함)

시퀀스 다이어그램 (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
Loading

코드 리뷰 예상 시간 (Estimated code review effort)

🎯 3 (Moderate) | ⏱️ ~25 분

주의 사항:

  • 중복 설정 항목: PrimaryDataSourceConfig에서 org.sejongisc.backend.betting.entity 패키지가 두 번 나타남 - 정의된 이유와 의도 확인 필요
  • 트랜잭션 경계: getAllMyBets에서 read-only 트랜잭션 처리 및 Lazy Loading 관계 확인
  • 동시성: incrementUpStats/incrementDownStats의 원자성 및 경쟁 조건 처리 검증
  • DTO 팩토리 메서드: from() 메서드에서 중첩된 엔티티 접근(getRound()) 시 영속성 컨텍스트 상태 확인

관련 PR (Possibly related PRs)

추천 리뷰어 (Suggested reviewers)

  • discipline24
  • msciki7

시를 읽어주세요 (Poem)

🐰 DTO로 깔끔하게 정리하고,
통계는 원자적으로 증가하고,
응답은 더욱 가벼워졌네!
베팅의 흐름이 한층 매끄러워진,
아름다운 리팩토링의 춤이로다!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경사항의 핵심 내용인 베팅 통계 정보(포인트, 인원수) 추가 및 DTO 기반 응답 구조로의 전환을 명확하게 설명합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SISC1-121-BE-베팅시-획득-포인트-베팅-인원수-추가

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 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/downBetCountinteger default 0이고, upTotalPoints/downTotalPointsnullable = false만 설정되어 있습니다.
    • 기존 테이블에 데이터가 있는 상태에서 Hibernate ddl-auto=update 또는 별도 마이그레이션을 사용한다면, NOT NULL 컬럼 추가 시 초기값(0) 세팅이 잘 되는지 한 번 확인해 보시는 게 좋겠습니다. 필요하면 columnDefinition = "bigint default 0" 같은 형태로 명시하는 것도 고려 가능합니다.
  • getEstimatedRewardMultiplier
    • 현재는 (double) totalPool / optionPoolBigDecimal.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_SettleAtDescJOIN FETCH round 또는 @EntityGraph(attributePaths = "round")로 구현되어 있지 않다면 사용자별 조회 시 N+1 쿼리가 발생할 수 있습니다. 해당 리포지토리 메서드 구현을 확인하시고, 필요시 fetch join/EntityGraph 적용을 고려해 주세요.


138-198: postUserBet 흐름은 명확하나 몇 가지 동작 조건 재확인 제안

전반적인 플로우(라운드 조회 → 중복 검증 → 상태 검증 → stake 계산 → 통계 업데이트 → 포인트 차감 → UserBet 저장 및 DTO 변환)는 명확하고,
통계 업데이트를 JPQL UPDATE로 처리해 동시성(Lost Update)을 줄인 점도 좋습니다. 다만 몇 가지 부분은 의도와 맞는지 한 번 더 확인해 보시면 좋겠습니다.

  1. 무료 베팅의 인원수/포인트 집계
    • stake = isFree ? 0 : userBetRequest.getStakePoints();
    • 무료 베팅도 incrementUpStats / incrementDownStats가 호출되기 때문에, upBetCount/downBetCount에는 인원 수가 포함되지만 upTotalPoints/downTotalPoints는 0만 더해집니다.
    • 요구사항이 “무료/유료 구분 없이 참여 인원수를 보고 싶다”라면 현재 구현이 맞고, “유료 베팅 인원만 보고 싶다”라면 if (!userBetRequest.isFree()) 안에서만 통계 업데이트를 하는 게 맞습니다.
  2. 유효성 검증과 통계 업데이트 순서
    • 현재는 통계 업데이트(라인 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

📥 Commits

Reviewing files that changed from the base of the PR and between 84d2797 and 49ddcb4.

📒 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를 JPQL UPDATE + @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, UserBetResponse DTO를 반환하도록 바뀐 흐름과 잘 맞고, 두 DTO 모두 이 클래스 안에서 실제로 사용되고 있어 불필요한 임포트도 없습니다.


52-61: Optional를 활용한 200/404 분기 처리가 명확합니다

bettingService.getActiveRoundResponse(scope)에서 Optional<BetRoundResponse>를 받아 map(ResponseEntity::ok) / orElseGet(ResponseEntity.notFound().build())로 처리한 구조가 간결하고, 기존 스펙(활성 라운드 없으면 404)과도 정확히 일치합니다. 컨트롤러 레벨에서 불필요한 null 체크가 사라져 가독성도 좋아졌습니다.

@discipline24 discipline24 merged commit cfe3a1c into main Nov 20, 2025
1 check passed
@discipline24 discipline24 deleted the SISC1-121-BE-베팅시-획득-포인트-베팅-인원수-추가 branch November 20, 2025 16:16
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.

2 participants