diff --git a/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java b/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java index 47b2045..6465691 100644 --- a/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java +++ b/src/main/java/com/tailbet/service/impl/RedPacketServiceImpl.java @@ -78,6 +78,7 @@ public class RedPacketServiceImpl implements IRedPacketService { rp.setCreateTime(LocalDateTime.now()); // 判断红包类型 + Integer targetDigit = null; if (roundId != null) { rp.setType(TYPE_GAME); } else { @@ -98,25 +99,28 @@ public class RedPacketServiceImpl implements IRedPacketService { round.setFirstRpId(rp.getId()); gameRoundMapper.updateById(round); - // 如果有目标尾数,预计算份额金额 - if (round.getTargetDigit() != null) { - List shareAmounts = preCalculateShareAmounts(rp, round.getTargetDigit()); - try { - rp.setShareAmounts(objectMapper.writeValueAsString(shareAmounts)); - } catch (JsonProcessingException e) { - log.error("序列化shareAmounts失败: {}", shareAmounts, e); - throw new RuntimeException("系统错误"); - } - rp.setShareTakenCount(0); - redPacketMapper.updateById(rp); - log.info("预分配红包份额: rpId={}, targetDigit={}, amounts={}", rp.getId(), round.getTargetDigit(), shareAmounts); - } + // 获取局的目标尾数(可能为null) + targetDigit = round.getTargetDigit(); } else { // 已有红包,不再绑定本局,作为普通游戏红包发放(不计算尾数) log.info("本局已有红包,当前红包不绑定: rpId={}, roundId={}", rp.getId(), roundId); } } + // 无论红包类型(TYPE_GAME/TYPE_RANDOM),都预先生成份额金额 + // 游戏红包有targetDigit时控制手气王尾数,无targetDigit或非游戏红包按纯随机份额 + List shareAmounts = preCalculateShareAmounts(rp, targetDigit); + try { + rp.setShareAmounts(objectMapper.writeValueAsString(shareAmounts)); + } catch (JsonProcessingException e) { + log.error("序列化shareAmounts失败: {}", shareAmounts, e); + throw new RuntimeException("系统错误"); + } + rp.setShareTakenCount(0); + redPacketMapper.updateById(rp); + log.info("预分配红包份额: rpId={}, type={}, targetDigit={}, amounts={}", + rp.getId(), rp.getType(), targetDigit, shareAmounts); + // 审计日志 auditService.log(groupId, roundId, userId, "RP_SEND", String.format("{\"rpId\":%d,\"amount\":%s,\"count\":%d}", @@ -290,10 +294,11 @@ public class RedPacketServiceImpl implements IRedPacketService { } /** - * 预计算红包份额金额,确保手气王尾数正确 - * 手气王金额确保以目标尾数结尾 + * 预计算红包份额金额 + * targetDigit 非空时确保手气王金额尾数=targetDigit + * targetDigit 为空时按纯随机份额处理 */ - private List preCalculateShareAmounts(RedPacket rp, int targetDigit) { + private List preCalculateShareAmounts(RedPacket rp, Integer targetDigit) { List amounts = new ArrayList<>(); int count = rp.getTotalCount(); BigDecimal total = rp.getTotalAmount(); @@ -306,17 +311,8 @@ public class RedPacketServiceImpl implements IRedPacketService { Random random = new Random(); - // 计算手气王金额(较大金额,尾数必须=targetDigit) - // 手气王比例:总额的30%~50%,确保手气王金额足够大 - double ratio = 0.3 + random.nextDouble() * 0.2; - BigDecimal luckyAmountBase = total.multiply(BigDecimal.valueOf(ratio)); - - // 确保尾数 = targetDigit - int lastDigit = luckyAmountBase.remainder(BigDecimal.TEN).intValue(); - int diff = (targetDigit - lastDigit + 10) % 10; - BigDecimal luckyAmount = luckyAmountBase.add(BigDecimal.valueOf(diff)) - .setScale(2, RoundingMode.HALF_UP); - + // 计算手气王金额 + BigDecimal luckyAmount = calculateLuckyAmount(total, targetDigit, random); // 确保手气王金额不超过总额 if (luckyAmount.compareTo(total) > 0) { luckyAmount = total; @@ -346,29 +342,55 @@ public class RedPacketServiceImpl implements IRedPacketService { // 最后一个拿剩余 amounts.add(remainingForOthers.setScale(2, RoundingMode.HALF_UP)); - // 打乱顺序(让领取顺序随机,但手气王金额已确定尾数) + // 打乱顺序(让领取顺序随机) Collections.shuffle(amounts); - // 确保手气王金额尾数正确(如果打乱后手气王不在列表中) - boolean hasCorrectDigit = false; - for (BigDecimal amt : amounts) { - if (amt.remainder(BigDecimal.TEN).intValue() == targetDigit) { - hasCorrectDigit = true; - break; - } - } - if (!hasCorrectDigit) { - // 重新找一个尾数匹配的作为手气王 - for (int i = 0; i < amounts.size(); i++) { - BigDecimal amt = amounts.get(i); - int digit = amt.remainder(BigDecimal.TEN).intValue(); - if (digit == targetDigit && i != 0) { - Collections.swap(amounts, i, 0); - break; - } - } + // 如果指定了目标尾数,确保列表中至少有1个金额的尾数=targetDigit + if (targetDigit != null) { + ensureLuckyDigit(amounts, targetDigit); } return amounts; } + + /** + * 计算手气王金额 + * 比例为总额的30%~50%,有目标尾数时确保尾数=targetDigit + */ + private BigDecimal calculateLuckyAmount(BigDecimal total, Integer targetDigit, Random random) { + // 手气王比例:总额的30%~50% + double ratio = 0.3 + random.nextDouble() * 0.2; + BigDecimal luckyAmountBase = total.multiply(BigDecimal.valueOf(ratio)); + + // 未指定目标尾数时按基础金额返回 + if (targetDigit == null) { + return luckyAmountBase.setScale(2, RoundingMode.HALF_UP); + } + + // 指定了目标尾数,调整金额使尾数=targetDigit + int lastDigit = luckyAmountBase.remainder(BigDecimal.TEN).intValue(); + int diff = (targetDigit - lastDigit + 10) % 10; + return luckyAmountBase.add(BigDecimal.valueOf(diff)) + .setScale(2, RoundingMode.HALF_UP); + } + + /** + * 确保份额列表中至少有1个金额的尾数=targetDigit + * 若没有则从列表中找一个尾数匹配的金额,换到首位作为手气王 + */ + private void ensureLuckyDigit(List amounts, int targetDigit) { + for (BigDecimal amt : amounts) { + if (amt.remainder(BigDecimal.TEN).intValue() == targetDigit) { + return; + } + } + // 没有尾数匹配的,找一个换到首位 + for (int i = 1; i < amounts.size(); i++) { + BigDecimal amt = amounts.get(i); + if (amt.remainder(BigDecimal.TEN).intValue() == targetDigit) { + Collections.swap(amounts, i, 0); + return; + } + } + } }