发红包优化
This commit is contained in:
@@ -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<BigDecimal> 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<BigDecimal> 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<BigDecimal> preCalculateShareAmounts(RedPacket rp, int targetDigit) {
|
||||
private List<BigDecimal> preCalculateShareAmounts(RedPacket rp, Integer targetDigit) {
|
||||
List<BigDecimal> 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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user