Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Pattern;
import lombok.RequiredArgsConstructor;
import org.sejongisc.backend.betting.dto.BetRoundResponse;
import org.sejongisc.backend.betting.dto.UserBetRequest;
import org.sejongisc.backend.betting.entity.BetRound;
import org.sejongisc.backend.betting.entity.Scope;
Expand Down Expand Up @@ -47,11 +48,11 @@ public class BettingController {
}
)
@GetMapping("/bet-rounds/{scope}")
public ResponseEntity<BetRound> getTodayBetRound(
public ResponseEntity<BetRoundResponse> getTodayBetRound(
@Parameter(description = "라운드 범위 (Scope): DAILY 또는 WEEKLY", example = "DAILY")
@PathVariable Scope scope
) {
Optional<BetRound> betRound = bettingService.getActiveRound(scope);
Optional<BetRoundResponse> betRound = bettingService.getActiveRoundResponse(scope);

return betRound
.map(ResponseEntity::ok)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.sejongisc.backend.betting.dto;

import lombok.Builder;
import lombok.Getter;
import org.sejongisc.backend.betting.entity.BetOption;
import org.sejongisc.backend.betting.entity.BetRound;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@Builder
public class BetRoundResponse {
private UUID betRoundId;
private String title;
private String symbol;
private BigDecimal previousClosePrice;
private LocalDateTime openAt;
private LocalDateTime lockAt;

// 통계 정보
private int upBetCount;
private int downBetCount;
private long upTotalPoints;
private long downTotalPoints;

// 예상 획득 포인트 (100포인트 베팅 기준 예시)
private BigDecimal expectedUpReward;
private BigDecimal expectedDownReward;

public static BetRoundResponse from(BetRound round) {
return BetRoundResponse.builder()
.betRoundId(round.getBetRoundID())
.title(round.getTitle())
.symbol(round.getSymbol())
.previousClosePrice(round.getPreviousClosePrice())
.openAt(round.getOpenAt())
.lockAt(round.getLockAt())
.upBetCount(round.getUpBetCount())
.downBetCount(round.getDownBetCount())
.upTotalPoints(round.getUpTotalPoints())
.downTotalPoints(round.getDownTotalPoints())
// 예상 배당률 계산 (소수점 등 로직은 기획에 맞춰 조정)
.expectedUpReward(round.getEstimatedRewardMultiplier(BetOption.RISE))
.expectedDownReward(round.getEstimatedRewardMultiplier(BetOption.FALL))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ public class BetRound extends BasePostgresEntity {
@Schema(description = "정산 종가 (결과 비교용)")
private BigDecimal settleClosePrice;

// [추가] 상승(UP) 베팅 통계
@Column(nullable = false, columnDefinition = "integer default 0")
private int upBetCount = 0;

@Column(nullable = false)
private long upTotalPoints = 0;

// [추가] 하락(DOWN) 베팅 통계
@Column(nullable = false, columnDefinition = "integer default 0")
private int downBetCount = 0;

@Column(nullable = false)
private long downTotalPoints = 0;

// 라운드가 현재 진행 중인지 여부 반환
public boolean isOpen() {
return this.status;
Expand All @@ -96,6 +110,17 @@ public void close() {
this.status = false;
}

// [추가] 예상 배당률 조회 (예시: 전체 포인트 / 해당 옵션 포인트)
public BigDecimal getEstimatedRewardMultiplier(BetOption option) {
long totalPool = upTotalPoints + downTotalPoints;
long optionPool = (option == BetOption.RISE) ? upTotalPoints : downTotalPoints;

if (optionPool == 0) return BigDecimal.valueOf(1.0); // 기본 배율

// 공식: (전체 베팅 포인트 / 내 옵션 포인트) * 수수료 등 보정
return BigDecimal.valueOf((double) totalPool / optionPool);
}

// "베팅 가능한 상태인지 검증
public void validate() {
if (isClosed() || (lockAt != null && LocalDateTime.now().isAfter(lockAt))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package org.sejongisc.backend.betting.repository;

import org.springframework.data.repository.query.Param;
import org.sejongisc.backend.betting.entity.BetRound;
import org.sejongisc.backend.betting.entity.Scope;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -17,4 +20,14 @@ public interface BetRoundRepository extends JpaRepository<BetRound, UUID> {
List<BetRound> findByStatusTrueAndLockAtLessThanEqual(LocalDateTime now);

List<BetRound> findByStatusFalseAndSettleAtIsNullAndLockAtLessThanEqual(LocalDateTime now);

// [추가] 상승(UP) 통계 원자적 업데이트
@Modifying(clearAutomatically = true) // 쿼리 실행 후 영속성 컨텍스트 초기화 (데이터 동기화)
@Query("UPDATE BetRound b SET b.upBetCount = b.upBetCount + 1, b.upTotalPoints = b.upTotalPoints + :points WHERE b.betRoundID = :id")
void incrementUpStats(@Param("id") UUID id, @Param("points") long points);

// [추가] 하락(DOWN) 통계 원자적 업데이트
@Modifying(clearAutomatically = true)
@Query("UPDATE BetRound b SET b.downBetCount = b.downBetCount + 1, b.downTotalPoints = b.downTotalPoints + :points WHERE b.betRoundID = :id")
void incrementDownStats(@Param("id") UUID id, @Param("points") long points);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.sejongisc.backend.betting.service;

import lombok.RequiredArgsConstructor;
import org.sejongisc.backend.betting.dto.BetRoundResponse;
import org.sejongisc.backend.betting.dto.PriceResponse;
import org.sejongisc.backend.betting.dto.UserBetRequest;
import org.sejongisc.backend.betting.entity.*;
Expand Down Expand Up @@ -139,7 +140,17 @@ public UserBet postUserBet(UUID userId, UserBetRequest userBetRequest) {

betRound.validate();

int stake = 0;
// [수정] 유료 베팅인 경우 베팅 포인트 설정
int stake = userBetRequest.isFree() ? 0 : userBetRequest.getStakePoints();

// [삭제] 기존 엔티티 메서드 호출 방식 (동시성 문제 발생)
//betRound.addBetStats(userBetRequest.getOption(), stake);

if (userBetRequest.getOption() == BetOption.RISE) {
betRoundRepository.incrementUpStats(betRound.getBetRoundID(), stake);
} else {
betRoundRepository.incrementDownStats(betRound.getBetRoundID(), stake);
}

if (!userBetRequest.isFree()) {
if (!userBetRequest.isStakePointsValid()) {
Expand Down Expand Up @@ -172,6 +183,12 @@ public UserBet postUserBet(UUID userId, UserBetRequest userBetRequest) {
}
}

// [추가] getActiveRound 반환 타입 변경 대응 메서드 (Controller에서 사용)
public Optional<BetRoundResponse> getActiveRoundResponse(Scope type) {
return betRoundRepository.findTopByStatusTrueAndScopeOrderByOpenAtDesc(type)
.map(BetRoundResponse::from);
}

/**
* 사용자 베팅 취소
*/
Expand Down