金额 BigDecimal
This commit is contained in:
@@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Update;
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户Mapper
|
* 用户Mapper
|
||||||
*/
|
*/
|
||||||
@@ -16,11 +18,11 @@ public interface UserMapper extends BaseMapper<User> {
|
|||||||
* 原子增加积分
|
* 原子增加积分
|
||||||
*/
|
*/
|
||||||
@Update("UPDATE user SET points = points + #{amount} WHERE id = #{id}")
|
@Update("UPDATE user SET points = points + #{amount} WHERE id = #{id}")
|
||||||
int addPoints(@Param("id") Long id, @Param("amount") Long amount);
|
int addPoints(@Param("id") Long id, @Param("amount") BigDecimal amount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原子扣除积分(积分不足时返回0)
|
* 原子扣除积分(积分不足时返回0)
|
||||||
*/
|
*/
|
||||||
@Update("UPDATE user SET points = points - #{amount} WHERE id = #{id} AND points >= #{amount}")
|
@Update("UPDATE user SET points = points - #{amount} WHERE id = #{id} AND points >= #{amount}")
|
||||||
int subPoints(@Param("id") Long id, @Param("amount") Long amount);
|
int subPoints(@Param("id") Long id, @Param("amount") BigDecimal amount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.tailbet.model.dto;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下注请求
|
* 下注请求
|
||||||
*/
|
*/
|
||||||
@@ -25,5 +27,5 @@ public class BetDTO {
|
|||||||
/**
|
/**
|
||||||
* 下注金额
|
* 下注金额
|
||||||
*/
|
*/
|
||||||
private Integer betAmount;
|
private BigDecimal betAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.tailbet.model.dto;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 积分操作请求
|
* 积分操作请求
|
||||||
*/
|
*/
|
||||||
@@ -15,7 +17,7 @@ public class PointsDTO {
|
|||||||
/**
|
/**
|
||||||
* 积分数量
|
* 积分数量
|
||||||
*/
|
*/
|
||||||
private Long amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class BetOrder {
|
|||||||
/**
|
/**
|
||||||
* 下注金额
|
* 下注金额
|
||||||
*/
|
*/
|
||||||
private Integer betAmount;
|
private BigDecimal betAmount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赔率
|
* 赔率
|
||||||
@@ -65,7 +65,7 @@ public class BetOrder {
|
|||||||
/**
|
/**
|
||||||
* 中奖金额
|
* 中奖金额
|
||||||
*/
|
*/
|
||||||
private Long winAmount;
|
private BigDecimal winAmount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,17 +31,17 @@ public class PointsFlow {
|
|||||||
/**
|
/**
|
||||||
* 变动数值(正负)
|
* 变动数值(正负)
|
||||||
*/
|
*/
|
||||||
private Long amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变动前余额
|
* 变动前余额
|
||||||
*/
|
*/
|
||||||
private Long balanceBefore;
|
private BigDecimal balanceBefore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变动后余额
|
* 变动后余额
|
||||||
*/
|
*/
|
||||||
private Long balanceAfter;
|
private BigDecimal balanceAfter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务单号
|
* 业务单号
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户表
|
* 用户表
|
||||||
*/
|
*/
|
||||||
@@ -36,9 +38,9 @@ public class User {
|
|||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 积分余额
|
* 积分余额(与红包金额一致使用DECIMAL,避免浮点误差)
|
||||||
*/
|
*/
|
||||||
private Long points;
|
private BigDecimal points;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 运营标记:0否 1是
|
* 运营标记:0否 1是
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.tailbet.model.vo;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尾数试算结果
|
* 尾数试算结果
|
||||||
*/
|
*/
|
||||||
@@ -21,15 +23,20 @@ public class DigitTrialVo {
|
|||||||
/**
|
/**
|
||||||
* 总投注次数
|
* 总投注次数
|
||||||
*/
|
*/
|
||||||
private int totalBet;
|
private int totalBetCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总投注金额
|
||||||
|
*/
|
||||||
|
private BigDecimal totalBet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 总奖励/赔付金额
|
* 总奖励/赔付金额
|
||||||
*/
|
*/
|
||||||
private long totalAward;
|
private BigDecimal totalAward;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 净盈亏(totalAward - totalBet)
|
* 净盈亏(totalAward - totalBet)
|
||||||
*/
|
*/
|
||||||
private long profit;
|
private BigDecimal profit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.tailbet.model.vo;
|
|||||||
import com.tailbet.model.entity.User;
|
import com.tailbet.model.entity.User;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录用户信息
|
* 登录用户信息
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +33,7 @@ public class LoginUserVo {
|
|||||||
/**
|
/**
|
||||||
* 积分
|
* 积分
|
||||||
*/
|
*/
|
||||||
private Long points;
|
private BigDecimal points;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否运营
|
* 是否运营
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenIM回调接口
|
* OpenIM回调接口
|
||||||
*/
|
*/
|
||||||
@@ -101,9 +103,9 @@ public class OpenIMCallbackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String playType = parts[0];
|
String playType = parts[0];
|
||||||
Integer betAmount;
|
BigDecimal betAmount;
|
||||||
try {
|
try {
|
||||||
betAmount = Integer.parseInt(parts[1]);
|
betAmount = new BigDecimal(parts[1]);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
sendGroupMessage(groupId, "金额格式错误");
|
sendGroupMessage(groupId, "金额格式错误");
|
||||||
return;
|
return;
|
||||||
@@ -127,7 +129,7 @@ public class OpenIMCallbackController {
|
|||||||
var bet = betService.placeBet(userId, bizGroupId, msgId, playType, betAmount);
|
var bet = betService.placeBet(userId, bizGroupId, msgId, playType, betAmount);
|
||||||
|
|
||||||
if (bet != null) {
|
if (bet != null) {
|
||||||
sendGroupMessage(groupId, String.format("已记录: %s %d", playType, betAmount));
|
sendGroupMessage(groupId, String.format("已记录: %s %s", playType, betAmount.toPlainString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public interface IBetService {
|
|||||||
* 下注
|
* 下注
|
||||||
*/
|
*/
|
||||||
BetOrder placeBet(Long userId, Long groupId, String clientMsgId,
|
BetOrder placeBet(Long userId, Long groupId, String clientMsgId,
|
||||||
String playType, Integer betAmount);
|
String playType, BigDecimal betAmount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取赔率
|
* 获取赔率
|
||||||
@@ -23,5 +23,5 @@ public interface IBetService {
|
|||||||
/**
|
/**
|
||||||
* 获取用户本局下注总额
|
* 获取用户本局下注总额
|
||||||
*/
|
*/
|
||||||
Integer getUserRoundBetAmount(Long userId, Long roundId);
|
BigDecimal getUserRoundBetAmount(Long userId, Long roundId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.tailbet.service;
|
|||||||
import com.tailbet.model.entity.PointsFlow;
|
import com.tailbet.model.entity.PointsFlow;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 积分Service接口
|
* 积分Service接口
|
||||||
*/
|
*/
|
||||||
@@ -19,17 +21,17 @@ public interface IPointsService {
|
|||||||
/**
|
/**
|
||||||
* 添加积分
|
* 添加积分
|
||||||
*/
|
*/
|
||||||
void addPoints(Long userId, Long amount, String type, String bizNo, Long operatorId, String remark);
|
void addPoints(Long userId, BigDecimal amount, String type, String bizNo, Long operatorId, String remark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扣除积分
|
* 扣除积分
|
||||||
*/
|
*/
|
||||||
void subPoints(Long userId, Long amount, String type, String bizNo, Long operatorId, String remark);
|
void subPoints(Long userId, BigDecimal amount, String type, String bizNo, Long operatorId, String remark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户积分
|
* 获取用户积分
|
||||||
*/
|
*/
|
||||||
Long getPoints(Long userId);
|
BigDecimal getPoints(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询积分流水记录
|
* 查询积分流水记录
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class BetServiceImpl implements IBetService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public BetOrder placeBet(Long userId, Long groupId, String clientMsgId,
|
public BetOrder placeBet(Long userId, Long groupId, String clientMsgId,
|
||||||
String playType, Integer betAmount) {
|
String playType, BigDecimal betAmount) {
|
||||||
// 防重检查
|
// 防重检查
|
||||||
if (clientMsgId != null) {
|
if (clientMsgId != null) {
|
||||||
BetOrder existing = betOrderMapper.selectOne(new LambdaQueryWrapper<BetOrder>()
|
BetOrder existing = betOrderMapper.selectOne(new LambdaQueryWrapper<BetOrder>()
|
||||||
@@ -57,7 +57,7 @@ public class BetServiceImpl implements IBetService {
|
|||||||
if (user.getIsFake() != null && user.getIsFake() == 1) {
|
if (user.getIsFake() != null && user.getIsFake() == 1) {
|
||||||
// 假人下注只记录审计日志,不产生订单
|
// 假人下注只记录审计日志,不产生订单
|
||||||
auditService.log(groupId, null, userId, "FAKE_BET",
|
auditService.log(groupId, null, userId, "FAKE_BET",
|
||||||
String.format("{\"playType\":\"%s\",\"amount\":%d}", playType, betAmount));
|
String.format("{\"playType\":\"%s\",\"amount\":%s}", playType, betAmount.toPlainString()));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,25 +69,28 @@ public class BetServiceImpl implements IBetService {
|
|||||||
throw new RuntimeException("当前不在下注阶段");
|
throw new RuntimeException("当前不在下注阶段");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查余额
|
// 检查限额(必须为正整数,避免小数下注带来的精度问题)
|
||||||
if (user.getPoints() < betAmount) {
|
if (betAmount == null || betAmount.signum() <= 0
|
||||||
throw new RuntimeException("余额不足");
|
|| betAmount.compareTo(BigDecimal.valueOf(2000)) > 0) {
|
||||||
}
|
|
||||||
|
|
||||||
// 检查限额
|
|
||||||
if (betAmount < 1 || betAmount > 2000) {
|
|
||||||
throw new RuntimeException("单笔下注金额需在1-2000之间");
|
throw new RuntimeException("单笔下注金额需在1-2000之间");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查余额
|
||||||
|
BigDecimal points = user.getPoints() == null ? BigDecimal.ZERO : user.getPoints();
|
||||||
|
if (points.compareTo(betAmount) < 0) {
|
||||||
|
throw new RuntimeException("余额不足");
|
||||||
|
}
|
||||||
|
|
||||||
// 检查单局累计
|
// 检查单局累计
|
||||||
Integer roundTotal = betOrderMapper.selectList(new LambdaQueryWrapper<BetOrder>()
|
BigDecimal roundTotal = betOrderMapper.selectList(new LambdaQueryWrapper<BetOrder>()
|
||||||
.eq(BetOrder::getUserId, userId)
|
.eq(BetOrder::getUserId, userId)
|
||||||
.eq(BetOrder::getRoundId, round.getId())
|
.eq(BetOrder::getRoundId, round.getId())
|
||||||
.eq(BetOrder::getIsFake, 0))
|
.eq(BetOrder::getIsFake, 0))
|
||||||
.stream()
|
.stream()
|
||||||
.mapToInt(BetOrder::getBetAmount)
|
.map(BetOrder::getBetAmount)
|
||||||
.sum();
|
.filter(java.util.Objects::nonNull)
|
||||||
if (roundTotal + betAmount > 20000) {
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
if (roundTotal.add(betAmount).compareTo(BigDecimal.valueOf(20000)) > 0) {
|
||||||
throw new RuntimeException("单局累计下注不能超过20000");
|
throw new RuntimeException("单局累计下注不能超过20000");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +98,7 @@ public class BetServiceImpl implements IBetService {
|
|||||||
BigDecimal odds = getOdds(playType);
|
BigDecimal odds = getOdds(playType);
|
||||||
|
|
||||||
// 扣减积分
|
// 扣减积分
|
||||||
pointsService.subPoints(userId, betAmount.longValue(),
|
pointsService.subPoints(userId, betAmount,
|
||||||
IPointsService.TYPE_BET, null, null, "游戏下注");
|
IPointsService.TYPE_BET, null, null, "游戏下注");
|
||||||
|
|
||||||
|
|
||||||
@@ -115,8 +118,8 @@ public class BetServiceImpl implements IBetService {
|
|||||||
|
|
||||||
// 审计日志
|
// 审计日志
|
||||||
auditService.log(groupId, round.getId(), userId, "BET_PLACE",
|
auditService.log(groupId, round.getId(), userId, "BET_PLACE",
|
||||||
String.format("{\"betId\":%d,\"playType\":\"%s\",\"amount\":%d}",
|
String.format("{\"betId\":%d,\"playType\":\"%s\",\"amount\":%s}",
|
||||||
bet.getId(), playType, betAmount));
|
bet.getId(), playType, betAmount.toPlainString()));
|
||||||
|
|
||||||
return bet;
|
return bet;
|
||||||
}
|
}
|
||||||
@@ -139,13 +142,14 @@ public class BetServiceImpl implements IBetService {
|
|||||||
* 获取用户本局下注总额
|
* 获取用户本局下注总额
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Integer getUserRoundBetAmount(Long userId, Long roundId) {
|
public BigDecimal getUserRoundBetAmount(Long userId, Long roundId) {
|
||||||
return betOrderMapper.selectList(new LambdaQueryWrapper<BetOrder>()
|
return betOrderMapper.selectList(new LambdaQueryWrapper<BetOrder>()
|
||||||
.eq(BetOrder::getUserId, userId)
|
.eq(BetOrder::getUserId, userId)
|
||||||
.eq(BetOrder::getRoundId, roundId)
|
.eq(BetOrder::getRoundId, roundId)
|
||||||
.eq(BetOrder::getIsFake, 0))
|
.eq(BetOrder::getIsFake, 0))
|
||||||
.stream()
|
.stream()
|
||||||
.mapToInt(BetOrder::getBetAmount)
|
.map(BetOrder::getBetAmount)
|
||||||
.sum();
|
.filter(java.util.Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import org.springframework.data.redis.core.StringRedisTemplate;
|
|||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +58,7 @@ public class FakeUserServiceImpl implements IFakeUserService {
|
|||||||
User fake = new User();
|
User fake = new User();
|
||||||
fake.setUsername("fake_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12));
|
fake.setUsername("fake_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12));
|
||||||
fake.setNickname(generateNickname());
|
fake.setNickname(generateNickname());
|
||||||
fake.setPoints((long) (1000 + RANDOM.nextInt(49000)));
|
fake.setPoints(BigDecimal.valueOf(1000 + RANDOM.nextInt(49000)));
|
||||||
fake.setIsFake(1);
|
fake.setIsFake(1);
|
||||||
fake.setIsBot(0);
|
fake.setIsBot(0);
|
||||||
fake.setIsOps(0);
|
fake.setIsOps(0);
|
||||||
@@ -155,11 +157,11 @@ public class FakeUserServiceImpl implements IFakeUserService {
|
|||||||
// 随机玩法和金额
|
// 随机玩法和金额
|
||||||
String[] playTypes = {"单", "双", "大", "小"};
|
String[] playTypes = {"单", "双", "大", "小"};
|
||||||
String playType = playTypes[RANDOM.nextInt(playTypes.length)];
|
String playType = playTypes[RANDOM.nextInt(playTypes.length)];
|
||||||
int amount = (1 + RANDOM.nextInt(10)) * 10; // 10,20,30...100
|
BigDecimal amount = BigDecimal.valueOf((1 + RANDOM.nextInt(10)) * 10L); // 10,20,30...100
|
||||||
|
|
||||||
try {
|
try {
|
||||||
betService.placeBet(fakeId, groupId, "fake_" + UUID.randomUUID(), playType, amount);
|
betService.placeBet(fakeId, groupId, "fake_" + UUID.randomUUID(), playType, amount);
|
||||||
log.debug("假人{}下注: {} {}", fakeId, playType, amount);
|
log.debug("假人{}下注: {} {}", fakeId, playType, amount.toPlainString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("假人下注失败: {}", e.getMessage());
|
log.debug("假人下注失败: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,19 +251,20 @@ public class GameServiceImpl implements IGameService {
|
|||||||
trial.setDigit(d);
|
trial.setDigit(d);
|
||||||
trial.setPlayWin(getPlayWinDesc(d));
|
trial.setPlayWin(getPlayWinDesc(d));
|
||||||
|
|
||||||
int totalBet = 0;
|
BigDecimal totalBet = BigDecimal.ZERO;
|
||||||
long totalAward = 0;
|
BigDecimal totalAward = BigDecimal.ZERO;
|
||||||
|
|
||||||
for (BetOrder bet : bets) {
|
for (BetOrder bet : bets) {
|
||||||
totalBet += bet.getBetAmount();
|
totalBet = totalBet.add(nullToZero(bet.getBetAmount()));
|
||||||
if (isWin(bet, d)) {
|
if (isWin(bet, d)) {
|
||||||
totalAward += bet.getBetAmount() * (1 + bet.getOdds().doubleValue());
|
totalAward = totalAward.add(nullToZero(bet.getBetAmount())
|
||||||
|
.multiply(BigDecimal.ONE.add(nullToZero(bet.getOdds()))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trial.setTotalBet(totalBet);
|
trial.setTotalBet(totalBet);
|
||||||
trial.setTotalAward(totalAward);
|
trial.setTotalAward(totalAward);
|
||||||
trial.setProfit(totalBet - totalAward);
|
trial.setProfit(totalBet.subtract(totalAward));
|
||||||
result.add(trial);
|
result.add(trial);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,15 +321,16 @@ public class GameServiceImpl implements IGameService {
|
|||||||
.eq(BetOrder::getRoundId, roundId)
|
.eq(BetOrder::getRoundId, roundId)
|
||||||
.eq(BetOrder::getStatus, 0)); // 待结算
|
.eq(BetOrder::getStatus, 0)); // 待结算
|
||||||
|
|
||||||
long totalBet = 0;
|
BigDecimal totalBet = BigDecimal.ZERO;
|
||||||
long totalAward = 0;
|
BigDecimal totalAward = BigDecimal.ZERO;
|
||||||
|
|
||||||
for (BetOrder bet : bets) {
|
for (BetOrder bet : bets) {
|
||||||
totalBet += bet.getBetAmount();
|
totalBet = totalBet.add(nullToZero(bet.getBetAmount()));
|
||||||
|
|
||||||
if (isWin(bet, digit)) {
|
if (isWin(bet, digit)) {
|
||||||
// 中奖,派奖
|
// 中奖,派奖(用BigDecimal精确计算,避免double转换误差)
|
||||||
long winAmount = (long) (bet.getBetAmount() * (1 + bet.getOdds().doubleValue()));
|
BigDecimal winAmount = nullToZero(bet.getBetAmount())
|
||||||
|
.multiply(BigDecimal.ONE.add(nullToZero(bet.getOdds())));
|
||||||
bet.setStatus(1); // 已结算
|
bet.setStatus(1); // 已结算
|
||||||
bet.setWinAmount(winAmount);
|
bet.setWinAmount(winAmount);
|
||||||
betOrderMapper.updateById(bet);
|
betOrderMapper.updateById(bet);
|
||||||
@@ -337,7 +339,7 @@ public class GameServiceImpl implements IGameService {
|
|||||||
pointsService.addPoints(bet.getUserId(), winAmount, IPointsService.TYPE_AWARD,
|
pointsService.addPoints(bet.getUserId(), winAmount, IPointsService.TYPE_AWARD,
|
||||||
String.valueOf(bet.getId()), null, "游戏中奖");
|
String.valueOf(bet.getId()), null, "游戏中奖");
|
||||||
|
|
||||||
totalAward += winAmount;
|
totalAward = totalAward.add(winAmount);
|
||||||
} else {
|
} else {
|
||||||
// 未中奖
|
// 未中奖
|
||||||
bet.setStatus(2); // 未中奖
|
bet.setStatus(2); // 未中奖
|
||||||
@@ -352,9 +354,9 @@ public class GameServiceImpl implements IGameService {
|
|||||||
record.setRoundId(roundId);
|
record.setRoundId(roundId);
|
||||||
record.setDigit(digit);
|
record.setDigit(digit);
|
||||||
record.setPlayWins(getPlayWinDesc(digit));
|
record.setPlayWins(getPlayWinDesc(digit));
|
||||||
record.setTotalBet((int) totalBet);
|
record.setTotalBet(totalBet.intValue());
|
||||||
record.setTotalAward(totalAward);
|
record.setTotalAward(totalAward.longValue());
|
||||||
record.setProfit(totalBet - totalAward);
|
record.setProfit(totalBet.subtract(totalAward).longValue());
|
||||||
record.setCreateTime(LocalDateTime.now());
|
record.setCreateTime(LocalDateTime.now());
|
||||||
drawRecordMapper.insert(record);
|
drawRecordMapper.insert(record);
|
||||||
|
|
||||||
@@ -365,8 +367,9 @@ public class GameServiceImpl implements IGameService {
|
|||||||
|
|
||||||
// 审计日志
|
// 审计日志
|
||||||
auditService.log(round.getGroupId(), roundId, null, "SETTLE",
|
auditService.log(round.getGroupId(), roundId, null, "SETTLE",
|
||||||
String.format("{\"digit\":%d,\"totalBet\":%d,\"totalAward\":%d,\"profit\":%d}",
|
String.format("{\"digit\":%d,\"totalBet\":%s,\"totalAward\":%s,\"profit\":%s}",
|
||||||
digit, totalBet, totalAward, totalBet - totalAward));
|
digit, totalBet.toPlainString(), totalAward.toPlainString(),
|
||||||
|
totalBet.subtract(totalAward).toPlainString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,7 +403,7 @@ public class GameServiceImpl implements IGameService {
|
|||||||
.eq(BetOrder::getGroupId, groupId)
|
.eq(BetOrder::getGroupId, groupId)
|
||||||
.eq(BetOrder::getIsFake, 0));
|
.eq(BetOrder::getIsFake, 0));
|
||||||
stat.setTotalBetCount(allBets.size());
|
stat.setTotalBetCount(allBets.size());
|
||||||
stat.setTotalBetAmount(allBets.stream().mapToLong(BetOrder::getBetAmount).sum());
|
stat.setTotalBetAmount(allBets.stream().map(b -> b.getBetAmount() == null ? 0L : b.getBetAmount().longValue()).mapToLong(Long::longValue).sum());
|
||||||
|
|
||||||
// 统计派奖
|
// 统计派奖
|
||||||
List<DrawRecord> records = drawRecordMapper.selectList(new LambdaQueryWrapper<DrawRecord>()
|
List<DrawRecord> records = drawRecordMapper.selectList(new LambdaQueryWrapper<DrawRecord>()
|
||||||
@@ -410,4 +413,11 @@ public class GameServiceImpl implements IGameService {
|
|||||||
|
|
||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal空值防御:null视为0
|
||||||
|
*/
|
||||||
|
private static BigDecimal nullToZero(BigDecimal v) {
|
||||||
|
return v == null ? BigDecimal.ZERO : v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 积分Service实现
|
* 积分Service实现
|
||||||
*/
|
*/
|
||||||
@@ -28,13 +30,16 @@ public class PointsServiceImpl implements IPointsService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void addPoints(Long userId, Long amount, String type, String bizNo, Long operatorId, String remark) {
|
public void addPoints(Long userId, BigDecimal amount, String type, String bizNo, Long operatorId, String remark) {
|
||||||
|
if (amount == null || amount.signum() == 0) {
|
||||||
|
throw new RuntimeException("变动数值必须为正数");
|
||||||
|
}
|
||||||
User user = userMapper.selectById(userId);
|
User user = userMapper.selectById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("用户不存在");
|
throw new RuntimeException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long before = user.getPoints();
|
BigDecimal before = user.getPoints() == null ? BigDecimal.ZERO : user.getPoints();
|
||||||
|
|
||||||
// 原子增加积分
|
// 原子增加积分
|
||||||
int rows = userMapper.addPoints(userId, amount);
|
int rows = userMapper.addPoints(userId, amount);
|
||||||
@@ -42,7 +47,7 @@ public class PointsServiceImpl implements IPointsService {
|
|||||||
throw new RuntimeException("积分增加失败");
|
throw new RuntimeException("积分增加失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long after = before + amount;
|
BigDecimal after = before.add(amount);
|
||||||
|
|
||||||
// 记录流水
|
// 记录流水
|
||||||
PointsFlow flow = new PointsFlow();
|
PointsFlow flow = new PointsFlow();
|
||||||
@@ -62,16 +67,20 @@ public class PointsServiceImpl implements IPointsService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void subPoints(Long userId, Long amount, String type, String bizNo, Long operatorId, String remark) {
|
public void subPoints(Long userId, BigDecimal amount, String type, String bizNo, Long operatorId, String remark) {
|
||||||
|
if (amount == null || amount.signum() <= 0) {
|
||||||
|
throw new RuntimeException("变动数值必须为正数");
|
||||||
|
}
|
||||||
User user = userMapper.selectById(userId);
|
User user = userMapper.selectById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("用户不存在");
|
throw new RuntimeException("用户不存在");
|
||||||
}
|
}
|
||||||
if (user.getPoints() < amount) {
|
BigDecimal current = user.getPoints() == null ? BigDecimal.ZERO : user.getPoints();
|
||||||
|
if (current.compareTo(amount) < 0) {
|
||||||
throw new RuntimeException("余额不足");
|
throw new RuntimeException("余额不足");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long before = user.getPoints();
|
BigDecimal before = current;
|
||||||
|
|
||||||
// 原子扣除积分(SQL层保证余额充足才扣)
|
// 原子扣除积分(SQL层保证余额充足才扣)
|
||||||
int rows = userMapper.subPoints(userId, amount);
|
int rows = userMapper.subPoints(userId, amount);
|
||||||
@@ -79,13 +88,13 @@ public class PointsServiceImpl implements IPointsService {
|
|||||||
throw new RuntimeException("积分扣除失败");
|
throw new RuntimeException("积分扣除失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long after = before - amount;
|
BigDecimal after = before.subtract(amount);
|
||||||
|
|
||||||
// 记录流水
|
// 记录流水
|
||||||
PointsFlow flow = new PointsFlow();
|
PointsFlow flow = new PointsFlow();
|
||||||
flow.setUserId(userId);
|
flow.setUserId(userId);
|
||||||
flow.setType(type);
|
flow.setType(type);
|
||||||
flow.setAmount(-amount);
|
flow.setAmount(amount.negate());
|
||||||
flow.setBalanceBefore(before);
|
flow.setBalanceBefore(before);
|
||||||
flow.setBalanceAfter(after);
|
flow.setBalanceAfter(after);
|
||||||
flow.setBizNo(bizNo);
|
flow.setBizNo(bizNo);
|
||||||
@@ -99,9 +108,9 @@ public class PointsServiceImpl implements IPointsService {
|
|||||||
* 获取用户积分
|
* 获取用户积分
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Long getPoints(Long userId) {
|
public BigDecimal getPoints(Long userId) {
|
||||||
User user = userMapper.selectById(userId);
|
User user = userMapper.selectById(userId);
|
||||||
return user != null ? user.getPoints() : 0L;
|
return user != null && user.getPoints() != null ? user.getPoints() : BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 扣积分
|
// 扣积分
|
||||||
pointsService.subPoints(userId, totalAmount.longValue(),
|
pointsService.subPoints(userId, totalAmount,
|
||||||
IPointsService.TYPE_RP_SEND, null, null, "发送红包");
|
IPointsService.TYPE_RP_SEND, null, null, "发送红包");
|
||||||
|
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加积分
|
// 加积分
|
||||||
pointsService.addPoints(userId, amount.longValue(),
|
pointsService.addPoints(userId, amount,
|
||||||
IPointsService.TYPE_RP_RECV, String.valueOf(rpId), null, "领取红包");
|
IPointsService.TYPE_RP_RECV, String.valueOf(rpId), null, "领取红包");
|
||||||
|
|
||||||
// 记录领取
|
// 记录领取
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +55,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
|||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
user.setPassword(passwordUtil.encode(password));
|
user.setPassword(passwordUtil.encode(password));
|
||||||
user.setNickname(username);
|
user.setNickname(username);
|
||||||
user.setPoints(0L);
|
user.setPoints(BigDecimal.ZERO);
|
||||||
user.setStatus(1);
|
user.setStatus(1);
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS user (
|
|||||||
password VARCHAR(255) NOT NULL COMMENT '密码(BCrypt)',
|
password VARCHAR(255) NOT NULL COMMENT '密码(BCrypt)',
|
||||||
nickname VARCHAR(50) DEFAULT '' COMMENT '昵称',
|
nickname VARCHAR(50) DEFAULT '' COMMENT '昵称',
|
||||||
avatar_url VARCHAR(500) DEFAULT '' COMMENT '头像URL',
|
avatar_url VARCHAR(500) DEFAULT '' COMMENT '头像URL',
|
||||||
points BIGINT DEFAULT 0 COMMENT '积分余额',
|
points DECIMAL(20,2) DEFAULT 0 COMMENT '积分余额',
|
||||||
is_ops TINYINT DEFAULT 0 COMMENT '运营标记:0否 1是',
|
is_ops TINYINT DEFAULT 0 COMMENT '运营标记:0否 1是',
|
||||||
is_bot TINYINT DEFAULT 0 COMMENT '机器人标记:0否 1是',
|
is_bot TINYINT DEFAULT 0 COMMENT '机器人标记:0否 1是',
|
||||||
is_fake TINYINT DEFAULT 0 COMMENT '假人托标记:0否 1是',
|
is_fake TINYINT DEFAULT 0 COMMENT '假人托标记:0否 1是',
|
||||||
@@ -58,9 +58,9 @@ CREATE TABLE IF NOT EXISTS points_flow (
|
|||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
user_id BIGINT NOT NULL COMMENT '用户ID',
|
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||||
type VARCHAR(20) NOT NULL COMMENT '类型:ops_add/ops_sub/bet/award/rp_send/rp_recv',
|
type VARCHAR(20) NOT NULL COMMENT '类型:ops_add/ops_sub/bet/award/rp_send/rp_recv',
|
||||||
amount BIGINT NOT NULL COMMENT '变动数值(正负)',
|
amount DECIMAL(20,2) NOT NULL COMMENT '变动数值(正负)',
|
||||||
balance_before BIGINT NOT NULL COMMENT '变动前余额',
|
balance_before DECIMAL(20,2) NOT NULL COMMENT '变动前余额',
|
||||||
balance_after BIGINT NOT NULL COMMENT '变动后余额',
|
balance_after DECIMAL(20,2) NOT NULL COMMENT '变动后余额',
|
||||||
biz_no VARCHAR(100) COMMENT '业务单号',
|
biz_no VARCHAR(100) COMMENT '业务单号',
|
||||||
operator_id BIGINT COMMENT '操作人ID',
|
operator_id BIGINT COMMENT '操作人ID',
|
||||||
remark VARCHAR(255) DEFAULT '' COMMENT '备注',
|
remark VARCHAR(255) DEFAULT '' COMMENT '备注',
|
||||||
@@ -100,11 +100,11 @@ CREATE TABLE IF NOT EXISTS bet_order (
|
|||||||
round_id BIGINT NOT NULL COMMENT '局ID',
|
round_id BIGINT NOT NULL COMMENT '局ID',
|
||||||
client_msg_id VARCHAR(100) COMMENT '客户端消息ID(防重)',
|
client_msg_id VARCHAR(100) COMMENT '客户端消息ID(防重)',
|
||||||
play_type VARCHAR(10) NOT NULL COMMENT '玩法:单/双/大/小/数字',
|
play_type VARCHAR(10) NOT NULL COMMENT '玩法:单/双/大/小/数字',
|
||||||
bet_amount INT NOT NULL COMMENT '下注金额',
|
bet_amount DECIMAL(20,2) NOT NULL COMMENT '下注金额',
|
||||||
odds DECIMAL(5,2) NOT NULL COMMENT '赔率',
|
odds DECIMAL(5,2) NOT NULL COMMENT '赔率',
|
||||||
is_fake TINYINT DEFAULT 0 COMMENT '是否假人下注',
|
is_fake TINYINT DEFAULT 0 COMMENT '是否假人下注',
|
||||||
status TINYINT DEFAULT 0 COMMENT '状态:0待结算 1已结算 2未中奖',
|
status TINYINT DEFAULT 0 COMMENT '状态:0待结算 1已结算 2未中奖',
|
||||||
win_amount BIGINT DEFAULT 0 COMMENT '中奖金额',
|
win_amount DECIMAL(20,2) DEFAULT 0 COMMENT '中奖金额',
|
||||||
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
|
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
|
||||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
INDEX idx_user_round (user_id, round_id),
|
INDEX idx_user_round (user_id, round_id),
|
||||||
@@ -250,3 +250,14 @@ CREATE TABLE `pending_task` (
|
|||||||
INDEX `idx_execute_time` (`execute_time`),
|
INDEX `idx_execute_time` (`execute_time`),
|
||||||
UNIQUE INDEX `idx_task_id` (`task_id`)
|
UNIQUE INDEX `idx_task_id` (`task_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- ====== 积分字段类型迁移:BIGINT → DECIMAL(20,2),与红包金额统一精度 ======
|
||||||
|
-- 历史BIGINT都是整数,DECIMAL(20,2)可无损容纳,无需特殊转换
|
||||||
|
ALTER TABLE `user` MODIFY COLUMN `points` DECIMAL(20,2) DEFAULT 0 COMMENT '积分余额';
|
||||||
|
ALTER TABLE `points_flow` MODIFY COLUMN `amount` DECIMAL(20,2) NOT NULL COMMENT '变动数值(正负)';
|
||||||
|
ALTER TABLE `points_flow` MODIFY COLUMN `balance_before` DECIMAL(20,2) NOT NULL COMMENT '变动前余额';
|
||||||
|
ALTER TABLE `points_flow` MODIFY COLUMN `balance_after` DECIMAL(20,2) NOT NULL COMMENT '变动后余额';
|
||||||
|
|
||||||
|
-- ====== 注单金额字段类型迁移:INT/BIGINT → DECIMAL(20,2),与积分/红包统一精度 ======
|
||||||
|
ALTER TABLE `bet_order` MODIFY COLUMN `bet_amount` DECIMAL(20,2) NOT NULL COMMENT '下注金额';
|
||||||
|
ALTER TABLE `bet_order` MODIFY COLUMN `win_amount` DECIMAL(20,2) DEFAULT 0 COMMENT '中奖金额';
|
||||||
|
|||||||
Reference in New Issue
Block a user