fix
This commit is contained in:
@@ -1,26 +1,35 @@
|
||||
package com.tailbet.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tailbet.event.RedPacketFinishedEvent;
|
||||
import com.tailbet.model.entity.GameRound;
|
||||
import com.tailbet.model.entity.RedPacket;
|
||||
import com.tailbet.model.entity.RedPacketRecv;
|
||||
import com.tailbet.model.entity.User;
|
||||
import com.tailbet.mapper.GameRoundMapper;
|
||||
import com.tailbet.mapper.RedPacketMapper;
|
||||
import com.tailbet.mapper.RedPacketRecvMapper;
|
||||
import com.tailbet.mapper.UserMapper;
|
||||
import com.tailbet.mapper.GameRoundMapper;
|
||||
import com.tailbet.service.IRedPacketService;
|
||||
import com.tailbet.service.IPointsService;
|
||||
import com.tailbet.service.IAuditService;
|
||||
import com.tailbet.service.IPointsService;
|
||||
import com.tailbet.service.IRedPacketService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 红包Service实现
|
||||
@@ -36,6 +45,8 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
||||
private final GameRoundMapper gameRoundMapper;
|
||||
private final IPointsService pointsService;
|
||||
private final IAuditService auditService;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 发红包
|
||||
@@ -77,6 +88,37 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
||||
|
||||
redPacketMapper.insert(rp);
|
||||
|
||||
// 如果是游戏红包,检查并绑定第一个红包
|
||||
if (roundId != null) {
|
||||
GameRound round = gameRoundMapper.selectById(roundId);
|
||||
if (round == null) {
|
||||
throw new RuntimeException("局不存在");
|
||||
}
|
||||
|
||||
if (round.getFirstRpId() == null) {
|
||||
// 第一个红包,绑定到局
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// 已有红包,不再绑定本局,作为普通游戏红包发放(不计算尾数)
|
||||
log.info("本局已有红包,当前红包不绑定: rpId={}, roundId={}", rp.getId(), roundId);
|
||||
}
|
||||
}
|
||||
|
||||
// 审计日志
|
||||
auditService.log(groupId, roundId, userId, "RP_SEND",
|
||||
String.format("{\"rpId\":%d,\"amount\":%s,\"count\":%d}",
|
||||
@@ -107,17 +149,35 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
||||
throw new RuntimeException("已领取过该红包");
|
||||
}
|
||||
|
||||
// 计算领取金额
|
||||
BigDecimal amount = calculateReceiveAmount(rp);
|
||||
// 计算领取金额(从预分配中取或随机)
|
||||
BigDecimal amount = drawFromPreAllocatedAmounts(rp);
|
||||
|
||||
// 更新红包
|
||||
rp.setRemainAmount(rp.getRemainAmount().subtract(amount));
|
||||
rp.setRemainCount(rp.getRemainCount() - 1);
|
||||
if (rp.getRemainCount() <= 0) {
|
||||
boolean isFinished = rp.getRemainCount() <= 0;
|
||||
if (isFinished) {
|
||||
rp.setStatus(STATUS_FINISHED);
|
||||
}
|
||||
|
||||
// 更新手气王信息(第一个领取者为手气王)
|
||||
Integer takenCount = rp.getShareTakenCount();
|
||||
if (takenCount != null && takenCount == 0) {
|
||||
rp.setLuckyAmount(amount);
|
||||
rp.setLuckyUserId(userId);
|
||||
rp.setLuckyDigit(amount.remainder(BigDecimal.TEN).intValue());
|
||||
}
|
||||
rp.setShareTakenCount(takenCount != null ? takenCount + 1 : 1);
|
||||
|
||||
redPacketMapper.updateById(rp);
|
||||
|
||||
// 如果是游戏红包且已领完,发布开奖事件
|
||||
if (isFinished && TYPE_GAME.equals(rp.getType()) && rp.getRoundId() != null) {
|
||||
eventPublisher.publishEvent(new RedPacketFinishedEvent(
|
||||
this, rp.getRoundId(), rp.getGroupId(), rp.getId(), rp.getLuckyDigit()));
|
||||
log.info("游戏红包领完,发布开奖事件: roundId={}, luckyDigit={}", rp.getRoundId(), rp.getLuckyDigit());
|
||||
}
|
||||
|
||||
// 加积分
|
||||
pointsService.addPoints(userId, amount.longValue(),
|
||||
IPointsService.TYPE_RP_RECV, String.valueOf(rpId), null, "领取红包");
|
||||
@@ -202,4 +262,114 @@ public class RedPacketServiceImpl implements IRedPacketService {
|
||||
.eq(RedPacket::getStatus, STATUS_ONGOING)
|
||||
.isNotNull(RedPacket::getRoundId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从预分配金额中顺序取值
|
||||
* 兼容旧数据:如果没有预分配金额,则走随机算法
|
||||
*/
|
||||
private BigDecimal drawFromPreAllocatedAmounts(RedPacket rp) {
|
||||
if (rp.getShareAmounts() == null || rp.getShareAmounts().isEmpty()) {
|
||||
// 兼容旧数据fallback到随机
|
||||
return calculateReceiveAmount(rp);
|
||||
}
|
||||
|
||||
try {
|
||||
List<BigDecimal> amounts = objectMapper.readValue(rp.getShareAmounts(),
|
||||
new TypeReference<List<BigDecimal>>() {});
|
||||
if (amounts.isEmpty()) {
|
||||
return calculateReceiveAmount(rp);
|
||||
}
|
||||
// 取第一个
|
||||
BigDecimal amount = amounts.remove(0);
|
||||
// 更新JSON
|
||||
rp.setShareAmounts(objectMapper.writeValueAsString(amounts));
|
||||
return amount;
|
||||
} catch (Exception e) {
|
||||
log.error("解析shareAmounts失败: {}, error={}", rp.getShareAmounts(), e.getMessage());
|
||||
return calculateReceiveAmount(rp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预计算红包份额金额,确保手气王尾数正确
|
||||
* 手气王金额确保以目标尾数结尾
|
||||
*/
|
||||
private List<BigDecimal> preCalculateShareAmounts(RedPacket rp, int targetDigit) {
|
||||
List<BigDecimal> amounts = new ArrayList<>();
|
||||
int count = rp.getTotalCount();
|
||||
BigDecimal total = rp.getTotalAmount();
|
||||
|
||||
if (count <= 1) {
|
||||
// 只有1个,全部给手气王
|
||||
amounts.add(total);
|
||||
return amounts;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 确保手气王金额不超过总额
|
||||
if (luckyAmount.compareTo(total) > 0) {
|
||||
luckyAmount = total;
|
||||
}
|
||||
|
||||
// 剩余金额平均分配给其他人
|
||||
BigDecimal remaining = total.subtract(luckyAmount);
|
||||
BigDecimal avgAmount = remaining.divide(BigDecimal.valueOf(count - 1), 2, RoundingMode.HALF_UP);
|
||||
|
||||
// 添加手气王金额
|
||||
amounts.add(luckyAmount);
|
||||
|
||||
// 添加其他金额(略有随机波动)
|
||||
BigDecimal remainingForOthers = remaining;
|
||||
for (int i = 1; i < count - 1; i++) {
|
||||
// 允许小范围波动 0.8~1.2
|
||||
double factor = 0.8 + random.nextDouble() * 0.4;
|
||||
BigDecimal amount = avgAmount.multiply(BigDecimal.valueOf(factor))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
// 确保不超过剩余
|
||||
if (amount.compareTo(remainingForOthers) > 0) {
|
||||
amount = remainingForOthers;
|
||||
}
|
||||
amounts.add(amount);
|
||||
remainingForOthers = remainingForOthers.subtract(amount);
|
||||
}
|
||||
// 最后一个拿剩余
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return amounts;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user