From c5250e23ef650b498b532f2c79ecd06fa3636de2 Mon Sep 17 00:00:00 2001 From: wells <123456@qq.com> Date: Sun, 9 Aug 2026 12:30:23 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=91=E9=A2=9D=20BigDecimal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/tailbet/mapper/UserMapper.java | 6 ++- .../java/com/tailbet/model/dto/BetDTO.java | 4 +- .../java/com/tailbet/model/dto/PointsDTO.java | 4 +- .../com/tailbet/model/entity/BetOrder.java | 4 +- .../com/tailbet/model/entity/PointsFlow.java | 8 ++-- .../java/com/tailbet/model/entity/User.java | 6 ++- .../com/tailbet/model/vo/DigitTrialVo.java | 13 ++++-- .../com/tailbet/model/vo/LoginUserVo.java | 4 +- .../openim/OpenIMCallbackController.java | 8 ++-- .../java/com/tailbet/service/IBetService.java | 4 +- .../com/tailbet/service/IPointsService.java | 8 ++-- .../tailbet/service/impl/BetServiceImpl.java | 42 ++++++++++-------- .../service/impl/FakeUserServiceImpl.java | 8 ++-- .../tailbet/service/impl/GameServiceImpl.java | 44 ++++++++++++------- .../service/impl/PointsServiceImpl.java | 29 +++++++----- .../service/impl/RedPacketServiceImpl.java | 4 +- .../tailbet/service/impl/UserServiceImpl.java | 4 +- src/main/resources/sql/init.sql | 23 +++++++--- 18 files changed, 142 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/tailbet/mapper/UserMapper.java b/src/main/java/com/tailbet/mapper/UserMapper.java index 8af97e4..e6fbc67 100644 --- a/src/main/java/com/tailbet/mapper/UserMapper.java +++ b/src/main/java/com/tailbet/mapper/UserMapper.java @@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; +import java.math.BigDecimal; + /** * 用户Mapper */ @@ -16,11 +18,11 @@ public interface UserMapper extends BaseMapper { * 原子增加积分 */ @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) */ @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); } diff --git a/src/main/java/com/tailbet/model/dto/BetDTO.java b/src/main/java/com/tailbet/model/dto/BetDTO.java index 598f6f8..25a76a6 100644 --- a/src/main/java/com/tailbet/model/dto/BetDTO.java +++ b/src/main/java/com/tailbet/model/dto/BetDTO.java @@ -2,6 +2,8 @@ package com.tailbet.model.dto; import lombok.Data; +import java.math.BigDecimal; + /** * 下注请求 */ @@ -25,5 +27,5 @@ public class BetDTO { /** * 下注金额 */ - private Integer betAmount; + private BigDecimal betAmount; } diff --git a/src/main/java/com/tailbet/model/dto/PointsDTO.java b/src/main/java/com/tailbet/model/dto/PointsDTO.java index 075b5ef..275342f 100644 --- a/src/main/java/com/tailbet/model/dto/PointsDTO.java +++ b/src/main/java/com/tailbet/model/dto/PointsDTO.java @@ -2,6 +2,8 @@ package com.tailbet.model.dto; import lombok.Data; +import java.math.BigDecimal; + /** * 积分操作请求 */ @@ -15,7 +17,7 @@ public class PointsDTO { /** * 积分数量 */ - private Long amount; + private BigDecimal amount; /** * 备注 diff --git a/src/main/java/com/tailbet/model/entity/BetOrder.java b/src/main/java/com/tailbet/model/entity/BetOrder.java index 5c9fbc6..769fa88 100644 --- a/src/main/java/com/tailbet/model/entity/BetOrder.java +++ b/src/main/java/com/tailbet/model/entity/BetOrder.java @@ -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; /** * 创建时间 diff --git a/src/main/java/com/tailbet/model/entity/PointsFlow.java b/src/main/java/com/tailbet/model/entity/PointsFlow.java index e4b0a55..22f1aa4 100644 --- a/src/main/java/com/tailbet/model/entity/PointsFlow.java +++ b/src/main/java/com/tailbet/model/entity/PointsFlow.java @@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; + +import java.math.BigDecimal; 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; /** * 业务单号 diff --git a/src/main/java/com/tailbet/model/entity/User.java b/src/main/java/com/tailbet/model/entity/User.java index 327a766..8ee15d9 100644 --- a/src/main/java/com/tailbet/model/entity/User.java +++ b/src/main/java/com/tailbet/model/entity/User.java @@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import java.math.BigDecimal; + /** * 用户表 */ @@ -36,9 +38,9 @@ public class User { private String avatarUrl; /** - * 积分余额 + * 积分余额(与红包金额一致使用DECIMAL,避免浮点误差) */ - private Long points; + private BigDecimal points; /** * 运营标记:0否 1是 diff --git a/src/main/java/com/tailbet/model/vo/DigitTrialVo.java b/src/main/java/com/tailbet/model/vo/DigitTrialVo.java index 658d86f..9fa09ce 100644 --- a/src/main/java/com/tailbet/model/vo/DigitTrialVo.java +++ b/src/main/java/com/tailbet/model/vo/DigitTrialVo.java @@ -2,6 +2,8 @@ package com.tailbet.model.vo; 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) */ - private long profit; + private BigDecimal profit; } diff --git a/src/main/java/com/tailbet/model/vo/LoginUserVo.java b/src/main/java/com/tailbet/model/vo/LoginUserVo.java index 7831fe6..fa09cb8 100644 --- a/src/main/java/com/tailbet/model/vo/LoginUserVo.java +++ b/src/main/java/com/tailbet/model/vo/LoginUserVo.java @@ -3,6 +3,8 @@ package com.tailbet.model.vo; import com.tailbet.model.entity.User; import lombok.Data; +import java.math.BigDecimal; + /** * 登录用户信息 */ @@ -31,7 +33,7 @@ public class LoginUserVo { /** * 积分 */ - private Long points; + private BigDecimal points; /** * 是否运营 diff --git a/src/main/java/com/tailbet/openim/OpenIMCallbackController.java b/src/main/java/com/tailbet/openim/OpenIMCallbackController.java index d8236fb..d89afa4 100644 --- a/src/main/java/com/tailbet/openim/OpenIMCallbackController.java +++ b/src/main/java/com/tailbet/openim/OpenIMCallbackController.java @@ -11,6 +11,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.math.BigDecimal; + /** * OpenIM回调接口 */ @@ -101,9 +103,9 @@ public class OpenIMCallbackController { } String playType = parts[0]; - Integer betAmount; + BigDecimal betAmount; try { - betAmount = Integer.parseInt(parts[1]); + betAmount = new BigDecimal(parts[1]); } catch (NumberFormatException e) { sendGroupMessage(groupId, "金额格式错误"); return; @@ -127,7 +129,7 @@ public class OpenIMCallbackController { var bet = betService.placeBet(userId, bizGroupId, msgId, playType, betAmount); if (bet != null) { - sendGroupMessage(groupId, String.format("已记录: %s %d", playType, betAmount)); + sendGroupMessage(groupId, String.format("已记录: %s %s", playType, betAmount.toPlainString())); } } catch (RuntimeException e) { diff --git a/src/main/java/com/tailbet/service/IBetService.java b/src/main/java/com/tailbet/service/IBetService.java index 9293c65..3d978d1 100644 --- a/src/main/java/com/tailbet/service/IBetService.java +++ b/src/main/java/com/tailbet/service/IBetService.java @@ -13,7 +13,7 @@ public interface IBetService { * 下注 */ 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); } diff --git a/src/main/java/com/tailbet/service/IPointsService.java b/src/main/java/com/tailbet/service/IPointsService.java index 7e517c2..1981711 100644 --- a/src/main/java/com/tailbet/service/IPointsService.java +++ b/src/main/java/com/tailbet/service/IPointsService.java @@ -3,6 +3,8 @@ package com.tailbet.service; import com.tailbet.model.entity.PointsFlow; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.math.BigDecimal; + /** * 积分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); /** * 查询积分流水记录 diff --git a/src/main/java/com/tailbet/service/impl/BetServiceImpl.java b/src/main/java/com/tailbet/service/impl/BetServiceImpl.java index 5859a99..4c0863d 100644 --- a/src/main/java/com/tailbet/service/impl/BetServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/BetServiceImpl.java @@ -39,7 +39,7 @@ public class BetServiceImpl implements IBetService { @Override @Transactional public BetOrder placeBet(Long userId, Long groupId, String clientMsgId, - String playType, Integer betAmount) { + String playType, BigDecimal betAmount) { // 防重检查 if (clientMsgId != null) { BetOrder existing = betOrderMapper.selectOne(new LambdaQueryWrapper() @@ -57,7 +57,7 @@ public class BetServiceImpl implements IBetService { if (user.getIsFake() != null && user.getIsFake() == 1) { // 假人下注只记录审计日志,不产生订单 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; } @@ -69,25 +69,28 @@ public class BetServiceImpl implements IBetService { throw new RuntimeException("当前不在下注阶段"); } - // 检查余额 - if (user.getPoints() < betAmount) { - throw new RuntimeException("余额不足"); - } - - // 检查限额 - if (betAmount < 1 || betAmount > 2000) { + // 检查限额(必须为正整数,避免小数下注带来的精度问题) + if (betAmount == null || betAmount.signum() <= 0 + || betAmount.compareTo(BigDecimal.valueOf(2000)) > 0) { 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() + BigDecimal roundTotal = betOrderMapper.selectList(new LambdaQueryWrapper() .eq(BetOrder::getUserId, userId) .eq(BetOrder::getRoundId, round.getId()) .eq(BetOrder::getIsFake, 0)) .stream() - .mapToInt(BetOrder::getBetAmount) - .sum(); - if (roundTotal + betAmount > 20000) { + .map(BetOrder::getBetAmount) + .filter(java.util.Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + if (roundTotal.add(betAmount).compareTo(BigDecimal.valueOf(20000)) > 0) { throw new RuntimeException("单局累计下注不能超过20000"); } @@ -95,7 +98,7 @@ public class BetServiceImpl implements IBetService { BigDecimal odds = getOdds(playType); // 扣减积分 - pointsService.subPoints(userId, betAmount.longValue(), + pointsService.subPoints(userId, betAmount, IPointsService.TYPE_BET, null, null, "游戏下注"); @@ -115,8 +118,8 @@ public class BetServiceImpl implements IBetService { // 审计日志 auditService.log(groupId, round.getId(), userId, "BET_PLACE", - String.format("{\"betId\":%d,\"playType\":\"%s\",\"amount\":%d}", - bet.getId(), playType, betAmount)); + String.format("{\"betId\":%d,\"playType\":\"%s\",\"amount\":%s}", + bet.getId(), playType, betAmount.toPlainString())); return bet; } @@ -139,13 +142,14 @@ public class BetServiceImpl implements IBetService { * 获取用户本局下注总额 */ @Override - public Integer getUserRoundBetAmount(Long userId, Long roundId) { + public BigDecimal getUserRoundBetAmount(Long userId, Long roundId) { return betOrderMapper.selectList(new LambdaQueryWrapper() .eq(BetOrder::getUserId, userId) .eq(BetOrder::getRoundId, roundId) .eq(BetOrder::getIsFake, 0)) .stream() - .mapToInt(BetOrder::getBetAmount) - .sum(); + .map(BetOrder::getBetAmount) + .filter(java.util.Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); } } diff --git a/src/main/java/com/tailbet/service/impl/FakeUserServiceImpl.java b/src/main/java/com/tailbet/service/impl/FakeUserServiceImpl.java index 6fdf471..e26c3dd 100644 --- a/src/main/java/com/tailbet/service/impl/FakeUserServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/FakeUserServiceImpl.java @@ -13,6 +13,8 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.math.BigDecimal; + import java.util.*; /** @@ -56,7 +58,7 @@ public class FakeUserServiceImpl implements IFakeUserService { User fake = new User(); fake.setUsername("fake_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12)); fake.setNickname(generateNickname()); - fake.setPoints((long) (1000 + RANDOM.nextInt(49000))); + fake.setPoints(BigDecimal.valueOf(1000 + RANDOM.nextInt(49000))); fake.setIsFake(1); fake.setIsBot(0); fake.setIsOps(0); @@ -155,11 +157,11 @@ public class FakeUserServiceImpl implements IFakeUserService { // 随机玩法和金额 String[] playTypes = {"单", "双", "大", "小"}; 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 { betService.placeBet(fakeId, groupId, "fake_" + UUID.randomUUID(), playType, amount); - log.debug("假人{}下注: {} {}", fakeId, playType, amount); + log.debug("假人{}下注: {} {}", fakeId, playType, amount.toPlainString()); } catch (Exception e) { log.debug("假人下注失败: {}", e.getMessage()); } diff --git a/src/main/java/com/tailbet/service/impl/GameServiceImpl.java b/src/main/java/com/tailbet/service/impl/GameServiceImpl.java index d306fb9..2638b5e 100644 --- a/src/main/java/com/tailbet/service/impl/GameServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/GameServiceImpl.java @@ -251,19 +251,20 @@ public class GameServiceImpl implements IGameService { trial.setDigit(d); trial.setPlayWin(getPlayWinDesc(d)); - int totalBet = 0; - long totalAward = 0; + BigDecimal totalBet = BigDecimal.ZERO; + BigDecimal totalAward = BigDecimal.ZERO; for (BetOrder bet : bets) { - totalBet += bet.getBetAmount(); + totalBet = totalBet.add(nullToZero(bet.getBetAmount())); 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.setTotalAward(totalAward); - trial.setProfit(totalBet - totalAward); + trial.setProfit(totalBet.subtract(totalAward)); result.add(trial); } @@ -320,15 +321,16 @@ public class GameServiceImpl implements IGameService { .eq(BetOrder::getRoundId, roundId) .eq(BetOrder::getStatus, 0)); // 待结算 - long totalBet = 0; - long totalAward = 0; + BigDecimal totalBet = BigDecimal.ZERO; + BigDecimal totalAward = BigDecimal.ZERO; for (BetOrder bet : bets) { - totalBet += bet.getBetAmount(); + totalBet = totalBet.add(nullToZero(bet.getBetAmount())); if (isWin(bet, digit)) { - // 中奖,派奖 - long winAmount = (long) (bet.getBetAmount() * (1 + bet.getOdds().doubleValue())); + // 中奖,派奖(用BigDecimal精确计算,避免double转换误差) + BigDecimal winAmount = nullToZero(bet.getBetAmount()) + .multiply(BigDecimal.ONE.add(nullToZero(bet.getOdds()))); bet.setStatus(1); // 已结算 bet.setWinAmount(winAmount); betOrderMapper.updateById(bet); @@ -337,7 +339,7 @@ public class GameServiceImpl implements IGameService { pointsService.addPoints(bet.getUserId(), winAmount, IPointsService.TYPE_AWARD, String.valueOf(bet.getId()), null, "游戏中奖"); - totalAward += winAmount; + totalAward = totalAward.add(winAmount); } else { // 未中奖 bet.setStatus(2); // 未中奖 @@ -352,9 +354,9 @@ public class GameServiceImpl implements IGameService { record.setRoundId(roundId); record.setDigit(digit); record.setPlayWins(getPlayWinDesc(digit)); - record.setTotalBet((int) totalBet); - record.setTotalAward(totalAward); - record.setProfit(totalBet - totalAward); + record.setTotalBet(totalBet.intValue()); + record.setTotalAward(totalAward.longValue()); + record.setProfit(totalBet.subtract(totalAward).longValue()); record.setCreateTime(LocalDateTime.now()); drawRecordMapper.insert(record); @@ -365,8 +367,9 @@ public class GameServiceImpl implements IGameService { // 审计日志 auditService.log(round.getGroupId(), roundId, null, "SETTLE", - String.format("{\"digit\":%d,\"totalBet\":%d,\"totalAward\":%d,\"profit\":%d}", - digit, totalBet, totalAward, totalBet - totalAward)); + String.format("{\"digit\":%d,\"totalBet\":%s,\"totalAward\":%s,\"profit\":%s}", + digit, totalBet.toPlainString(), totalAward.toPlainString(), + totalBet.subtract(totalAward).toPlainString())); } /** @@ -400,7 +403,7 @@ public class GameServiceImpl implements IGameService { .eq(BetOrder::getGroupId, groupId) .eq(BetOrder::getIsFake, 0)); 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 records = drawRecordMapper.selectList(new LambdaQueryWrapper() @@ -410,4 +413,11 @@ public class GameServiceImpl implements IGameService { return stat; } + + /** + * BigDecimal空值防御:null视为0 + */ + private static BigDecimal nullToZero(BigDecimal v) { + return v == null ? BigDecimal.ZERO : v; + } } diff --git a/src/main/java/com/tailbet/service/impl/PointsServiceImpl.java b/src/main/java/com/tailbet/service/impl/PointsServiceImpl.java index 72ff5cf..98d7798 100644 --- a/src/main/java/com/tailbet/service/impl/PointsServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/PointsServiceImpl.java @@ -12,6 +12,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; + /** * 积分Service实现 */ @@ -28,13 +30,16 @@ public class PointsServiceImpl implements IPointsService { */ @Override @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); if (user == null) { throw new RuntimeException("用户不存在"); } - Long before = user.getPoints(); + BigDecimal before = user.getPoints() == null ? BigDecimal.ZERO : user.getPoints(); // 原子增加积分 int rows = userMapper.addPoints(userId, amount); @@ -42,7 +47,7 @@ public class PointsServiceImpl implements IPointsService { throw new RuntimeException("积分增加失败"); } - Long after = before + amount; + BigDecimal after = before.add(amount); // 记录流水 PointsFlow flow = new PointsFlow(); @@ -62,16 +67,20 @@ public class PointsServiceImpl implements IPointsService { */ @Override @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); if (user == null) { throw new RuntimeException("用户不存在"); } - if (user.getPoints() < amount) { + BigDecimal current = user.getPoints() == null ? BigDecimal.ZERO : user.getPoints(); + if (current.compareTo(amount) < 0) { throw new RuntimeException("余额不足"); } - Long before = user.getPoints(); + BigDecimal before = current; // 原子扣除积分(SQL层保证余额充足才扣) int rows = userMapper.subPoints(userId, amount); @@ -79,13 +88,13 @@ public class PointsServiceImpl implements IPointsService { throw new RuntimeException("积分扣除失败"); } - Long after = before - amount; + BigDecimal after = before.subtract(amount); // 记录流水 PointsFlow flow = new PointsFlow(); flow.setUserId(userId); flow.setType(type); - flow.setAmount(-amount); + flow.setAmount(amount.negate()); flow.setBalanceBefore(before); flow.setBalanceAfter(after); flow.setBizNo(bizNo); @@ -99,9 +108,9 @@ public class PointsServiceImpl implements IPointsService { * 获取用户积分 */ @Override - public Long getPoints(Long userId) { + public BigDecimal getPoints(Long userId) { User user = userMapper.selectById(userId); - return user != null ? user.getPoints() : 0L; + return user != null && user.getPoints() != null ? user.getPoints() : BigDecimal.ZERO; } /** diff --git a/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java b/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java index 7200c26..c1ffc11 100644 --- a/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java @@ -62,7 +62,7 @@ public class RedPacketServiceImpl implements IRedPacketService { } // 扣积分 - pointsService.subPoints(userId, totalAmount.longValue(), + pointsService.subPoints(userId, totalAmount, 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, "领取红包"); // 记录领取 diff --git a/src/main/java/com/tailbet/service/impl/UserServiceImpl.java b/src/main/java/com/tailbet/service/impl/UserServiceImpl.java index f3fb110..0a3bf17 100644 --- a/src/main/java/com/tailbet/service/impl/UserServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/UserServiceImpl.java @@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; + import java.util.List; /** @@ -53,7 +55,7 @@ public class UserServiceImpl extends ServiceImpl implements IU user.setUsername(username); user.setPassword(passwordUtil.encode(password)); user.setNickname(username); - user.setPoints(0L); + user.setPoints(BigDecimal.ZERO); user.setStatus(1); userMapper.insert(user); diff --git a/src/main/resources/sql/init.sql b/src/main/resources/sql/init.sql index 1bea48d..59b4618 100644 --- a/src/main/resources/sql/init.sql +++ b/src/main/resources/sql/init.sql @@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS user ( password VARCHAR(255) NOT NULL COMMENT '密码(BCrypt)', nickname VARCHAR(50) DEFAULT '' COMMENT '昵称', 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_bot 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, user_id BIGINT NOT NULL COMMENT '用户ID', type VARCHAR(20) NOT NULL COMMENT '类型:ops_add/ops_sub/bet/award/rp_send/rp_recv', - amount BIGINT NOT NULL COMMENT '变动数值(正负)', - balance_before BIGINT NOT NULL COMMENT '变动前余额', - balance_after BIGINT NOT NULL COMMENT '变动后余额', + amount DECIMAL(20,2) NOT NULL COMMENT '变动数值(正负)', + balance_before DECIMAL(20,2) NOT NULL COMMENT '变动前余额', + balance_after DECIMAL(20,2) NOT NULL COMMENT '变动后余额', biz_no VARCHAR(100) COMMENT '业务单号', operator_id BIGINT COMMENT '操作人ID', remark VARCHAR(255) DEFAULT '' COMMENT '备注', @@ -100,11 +100,11 @@ CREATE TABLE IF NOT EXISTS bet_order ( round_id BIGINT NOT NULL COMMENT '局ID', client_msg_id VARCHAR(100) COMMENT '客户端消息ID(防重)', 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 '赔率', is_fake TINYINT DEFAULT 0 COMMENT '是否假人下注', 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 '逻辑删除', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_round (user_id, round_id), @@ -250,3 +250,14 @@ CREATE TABLE `pending_task` ( INDEX `idx_execute_time` (`execute_time`), UNIQUE INDEX `idx_task_id` (`task_id`) ) 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 '中奖金额';