package com.ruoyi.cai.lottery; import com.lottery.entity.Prize; import com.lottery.entity.UserDrawRecord; import com.lottery.mapper.UserDrawRecordMapper; import com.ruoyi.cai.domain.PrizeInfo; import com.ruoyi.cai.service.PrizeOnlineService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Random; import java.util.concurrent.locks.ReentrantLock; @Service public class LotteryService { private static final Long THANKS_PRIZE_ID = 1L; private static final String USER_DRAW_COUNT_KEY = "user:draw:count:%s"; // 每个用户的锁,防止并发抽奖(单机版用ReentrantLock) private final ReentrantLock lock = new ReentrantLock(); @Autowired private PrizeOnlineService prizeOnlineService; @Autowired private RedisTemplate redisTemplate; /** * 用户抽奖 */ public PrizeInfo draw(Long userId) { // 加锁防止并发抽奖 lock.lock(); try { // 1. 获取用户累计抽数 int continuousDraws = getContinuousDraws(userId); int newContinuousDraws = continuousDraws + 1; // 2. 获取启用的奖品(排除谢谢惠顾) List prizes = prizeService.getEnablePrizesExcludeThanks(); if (prizes.isEmpty()) { // 只有谢谢惠顾 saveDrawRecord(userId, THANKS_PRIZE_ID, newContinuousDraws); return prizeService.getPrizeFromCache(THANKS_PRIZE_ID); } // 3. 处理抽奖规则(保底、最低中奖抽数、中奖率) Prize winPrize = null; boolean isGuarantee = false; // 3.1 检查保底规则 for (Prize prize : prizes) { if (prize.getGuaranteeDraws() > 0 && newContinuousDraws >= prize.getGuaranteeDraws()) { // 触发保底,直接中该奖品 if (prizeService.deductStock(prize.getId())) { winPrize = prize; isGuarantee = true; break; } } } // 3.2 未触发保底,按中奖率和最低中奖抽数规则抽奖 if (winPrize == null) { winPrize = randomDraw(prizes, newContinuousDraws); } // 4. 处理中奖结果 Long prizeId = winPrize != null ? winPrize.getId() : THANKS_PRIZE_ID; int finalContinuousDraws = winPrize != null ? 0 : newContinuousDraws; // 中奖后重置累计抽数 // 5. 保存抽奖记录 saveDrawRecord(userId, prizeId, finalContinuousDraws); // 6. 更新用户累计抽数缓存 updateContinuousDrawsCache(userId, finalContinuousDraws); return prizeService.getPrizeFromCache(prizeId); } finally { lock.unlock(); } } /** * 随机抽奖(处理中奖率和最低中奖抽数) */ private Prize randomDraw(List prizes, int continuousDraws) { // 计算总中奖率 double totalProbability = 0.0; for (Prize prize : prizes) { // 检查最低中奖抽数规则:未达到则该奖品中奖率为0 if (continuousDraws < prize.getMinWinDraws()) { continue; } totalProbability += prize.getWinProbability(); } if (totalProbability <= 0) { // 所有奖品都未达到最低中奖抽数,返回null(谢谢惠顾) return null; } // 随机数(0-总中奖率) double random = new Random().nextDouble() * totalProbability; double current = 0.0; for (Prize prize : prizes) { if (continuousDraws < prize.getMinWinDraws()) { continue; } current += prize.getWinProbability(); if (random <= current) { // 抽中该奖品,检查库存 if (prizeService.deductStock(prize.getId())) { return prize; } else { // 库存不足,重新抽奖(递归) return randomDraw(prizes, continuousDraws); } } } return null; } /** * 获取用户累计抽数(缓存+数据库) */ private int getContinuousDraws(Long userId) { String key = String.format(USER_DRAW_COUNT_KEY, userId); // 先从缓存获取 Integer count = (Integer) redisTemplate.opsForValue().get(key); if (count != null) { return count; } // 从数据库查询 count = userDrawRecordMapper.selectLastContinuousDraws(userId); count = count == null ? 0 : count; // 存入缓存 redisTemplate.opsForValue().set(key, count); return count; } /** * 更新用户累计抽数缓存 */ private void updateContinuousDrawsCache(Long userId, int count) { String key = String.format(USER_DRAW_COUNT_KEY, userId); redisTemplate.opsForValue().set(key, count); } /** * 保存抽奖记录 */ @Transactional(rollbackFor = Exception.class) private void saveDrawRecord(Long userId, Long prizeId, int continuousDraws) { UserDrawRecord record = new UserDrawRecord(); record.setUserId(userId); record.setPrizeId(prizeId); record.setDrawTime(LocalDateTime.now()); record.setContinuousDraws(continuousDraws); userDrawRecordMapper.insert(record); } }