package com.tailbet.service.impl; import com.tailbet.model.entity.RedPacket; import com.tailbet.model.entity.RedPacketRecv; import com.tailbet.model.entity.User; 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.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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.*; /** * 红包Service实现 */ @Slf4j @RequiredArgsConstructor @Service public class RedPacketServiceImpl implements IRedPacketService { private final RedPacketMapper redPacketMapper; private final RedPacketRecvMapper redPacketRecvMapper; private final UserMapper userMapper; private final GameRoundMapper gameRoundMapper; private final IPointsService pointsService; private final IAuditService auditService; /** * 发红包 */ @Override @Transactional public RedPacket sendRp(Long userId, Long groupId, BigDecimal totalAmount, Integer totalCount, Long roundId) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } // 扣积分 boolean success = pointsService.subPoints(userId, totalAmount.longValue(), IPointsService.TYPE_RP_SEND, null, null, "发送红包"); if (!success) { throw new RuntimeException("余额不足"); } // 创建红包 RedPacket rp = new RedPacket(); rp.setUserId(userId); rp.setGroupId(groupId); rp.setRoundId(roundId); rp.setTotalAmount(totalAmount); rp.setTotalCount(totalCount); rp.setRemainAmount(totalAmount); rp.setRemainCount(totalCount); rp.setStatus(STATUS_ONGOING); rp.setCreateTime(LocalDateTime.now()); // 判断红包类型 if (roundId != null) { rp.setType(TYPE_GAME); } else { rp.setType(TYPE_RANDOM); } redPacketMapper.insert(rp); // 审计日志 auditService.log(groupId, roundId, userId, "RP_SEND", String.format("{\"rpId\":%d,\"amount\":%s,\"count\":%d}", rp.getId(), totalAmount, totalCount)); return rp; } /** * 领红包 */ @Override @Transactional public RedPacketRecv receiveRp(Long rpId, Long userId) { RedPacket rp = redPacketMapper.selectById(rpId); if (rp == null) { throw new RuntimeException("红包不存在"); } if (rp.getStatus() != STATUS_ONGOING) { throw new RuntimeException("红包已领完"); } // 检查是否已领取 RedPacketRecv existing = redPacketRecvMapper.selectOne(new LambdaQueryWrapper() .eq(RedPacketRecv::getRpId, rpId) .eq(RedPacketRecv::getUserId, userId)); if (existing != null) { throw new RuntimeException("已领取过该红包"); } // 计算领取金额 BigDecimal amount = calculateReceiveAmount(rp); // 更新红包 rp.setRemainAmount(rp.getRemainAmount().subtract(amount)); rp.setRemainCount(rp.getRemainCount() - 1); if (rp.getRemainCount() <= 0) { rp.setStatus(STATUS_FINISHED); } redPacketMapper.updateById(rp); // 加积分 pointsService.addPoints(userId, amount.longValue(), IPointsService.TYPE_RP_RECV, String.valueOf(rpId), null, "领取红包"); // 记录领取 RedPacketRecv recv = new RedPacketRecv(); recv.setRpId(rpId); recv.setUserId(userId); recv.setAmount(amount); recv.setCreateTime(LocalDateTime.now()); redPacketRecvMapper.insert(recv); // 审计日志 auditService.log(rp.getGroupId(), rp.getRoundId(), userId, "RP_RECV", String.format("{\"rpId\":%d,\"amount\":%s}", rpId, amount)); return recv; } /** * 计算领取金额 */ @Override public BigDecimal calculateReceiveAmount(RedPacket rp) { if (rp.getRemainCount() <= 0) { return BigDecimal.ZERO; } // 如果是游戏红包且有目标尾数,则需要控制手气王 // 简化实现:随机分配 Random random = new Random(); if (rp.getRemainCount() == 1) { // 最后一个,拿剩余全部 return rp.getRemainAmount(); } // 随机金额,最小0.01 double min = 0.01; double max = rp.getRemainAmount().divide(BigDecimal.valueOf(rp.getRemainCount()), 2, RoundingMode.HALF_UP).doubleValue() * 2; double amount = min + (max - min) * random.nextDouble(); return BigDecimal.valueOf(amount).setScale(2, RoundingMode.HALF_UP); } /** * 获取红包详情 */ @Override public RedPacket getById(Long rpId) { return redPacketMapper.selectById(rpId); } /** * 获取红包领取列表 */ @Override public List getReceives(Long rpId) { return redPacketRecvMapper.selectList(new LambdaQueryWrapper() .eq(RedPacketRecv::getRpId, rpId) .orderByAsc(RedPacketRecv::getCreateTime)); } /** * 查询红包记录 */ @Override public Page getRecords(Long userId, Integer pageNum, Integer pageSize) { Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(RedPacket::getUserId, userId) .orderByDesc(RedPacket::getCreateTime); return redPacketMapper.selectPage(page, wrapper); } /** * 查找进行中的游戏红包 */ @Override public RedPacket getOngoingGameRp(Long groupId) { return redPacketMapper.selectOne(new LambdaQueryWrapper() .eq(RedPacket::getGroupId, groupId) .eq(RedPacket::getType, TYPE_GAME) .eq(RedPacket::getStatus, STATUS_ONGOING) .isNotNull(RedPacket::getRoundId)); } }