发红包优化

This commit is contained in:
wells
2026-08-08 22:33:47 +08:00
parent b227f33d9f
commit 7784e01e5e
@@ -78,6 +78,7 @@ public class RedPacketServiceImpl implements IRedPacketService {
rp.setCreateTime(LocalDateTime.now()); rp.setCreateTime(LocalDateTime.now());
// 判断红包类型 // 判断红包类型
Integer targetDigit = null;
if (roundId != null) { if (roundId != null) {
rp.setType(TYPE_GAME); rp.setType(TYPE_GAME);
} else { } else {
@@ -98,9 +99,17 @@ public class RedPacketServiceImpl implements IRedPacketService {
round.setFirstRpId(rp.getId()); round.setFirstRpId(rp.getId());
gameRoundMapper.updateById(round); gameRoundMapper.updateById(round);
// 如果有目标尾数,预计算份额金额 // 获取局的目标尾数(可能为null
if (round.getTargetDigit() != null) { targetDigit = round.getTargetDigit();
List<BigDecimal> shareAmounts = preCalculateShareAmounts(rp, round.getTargetDigit()); } else {
// 已有红包,不再绑定本局,作为普通游戏红包发放(不计算尾数)
log.info("本局已有红包,当前红包不绑定: rpId={}, roundId={}", rp.getId(), roundId);
}
}
// 无论红包类型(TYPE_GAME/TYPE_RANDOM),都预先生成份额金额
// 游戏红包有targetDigit时控制手气王尾数,无targetDigit或非游戏红包按纯随机份额
List<BigDecimal> shareAmounts = preCalculateShareAmounts(rp, targetDigit);
try { try {
rp.setShareAmounts(objectMapper.writeValueAsString(shareAmounts)); rp.setShareAmounts(objectMapper.writeValueAsString(shareAmounts));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@@ -109,13 +118,8 @@ public class RedPacketServiceImpl implements IRedPacketService {
} }
rp.setShareTakenCount(0); rp.setShareTakenCount(0);
redPacketMapper.updateById(rp); redPacketMapper.updateById(rp);
log.info("预分配红包份额: rpId={}, targetDigit={}, amounts={}", rp.getId(), round.getTargetDigit(), shareAmounts); log.info("预分配红包份额: rpId={}, type={}, targetDigit={}, amounts={}",
} rp.getId(), rp.getType(), targetDigit, shareAmounts);
} else {
// 已有红包,不再绑定本局,作为普通游戏红包发放(不计算尾数)
log.info("本局已有红包,当前红包不绑定: rpId={}, roundId={}", rp.getId(), roundId);
}
}
// 审计日志 // 审计日志
auditService.log(groupId, roundId, userId, "RP_SEND", auditService.log(groupId, roundId, userId, "RP_SEND",
@@ -290,10 +294,11 @@ public class RedPacketServiceImpl implements IRedPacketService {
} }
/** /**
* 预计算红包份额金额,确保手气王尾数正确 * 预计算红包份额金额
* 手气王金额确保以目标尾数结尾 * targetDigit 非空时确保手气王金额尾数=targetDigit
* targetDigit 为空时按纯随机份额处理
*/ */
private List<BigDecimal> preCalculateShareAmounts(RedPacket rp, int targetDigit) { private List<BigDecimal> preCalculateShareAmounts(RedPacket rp, Integer targetDigit) {
List<BigDecimal> amounts = new ArrayList<>(); List<BigDecimal> amounts = new ArrayList<>();
int count = rp.getTotalCount(); int count = rp.getTotalCount();
BigDecimal total = rp.getTotalAmount(); BigDecimal total = rp.getTotalAmount();
@@ -306,17 +311,8 @@ public class RedPacketServiceImpl implements IRedPacketService {
Random random = new Random(); Random random = new Random();
// 计算手气王金额(较大金额,尾数必须=targetDigit // 计算手气王金额
// 手气王比例:总额的30%~50%,确保手气王金额足够大 BigDecimal luckyAmount = calculateLuckyAmount(total, targetDigit, random);
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);
// 确保手气王金额不超过总额 // 确保手气王金额不超过总额
if (luckyAmount.compareTo(total) > 0) { if (luckyAmount.compareTo(total) > 0) {
luckyAmount = total; luckyAmount = total;
@@ -346,29 +342,55 @@ public class RedPacketServiceImpl implements IRedPacketService {
// 最后一个拿剩余 // 最后一个拿剩余
amounts.add(remainingForOthers.setScale(2, RoundingMode.HALF_UP)); amounts.add(remainingForOthers.setScale(2, RoundingMode.HALF_UP));
// 打乱顺序(让领取顺序随机,但手气王金额已确定尾数 // 打乱顺序(让领取顺序随机)
Collections.shuffle(amounts); Collections.shuffle(amounts);
// 确保手气王金额尾数正确(如果打乱后手气王不在列表中) // 如果指定了目标尾数,确保列表中至少有1个金额尾数=targetDigit
boolean hasCorrectDigit = false; if (targetDigit != null) {
for (BigDecimal amt : amounts) { ensureLuckyDigit(amounts, targetDigit);
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;
}
}
} }
return amounts; 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<BigDecimal> 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;
}
}
}
} }