Files
cai-server/ruoyi-cai/src/main/java/com/ruoyi/cai/lottery/LotteryService.java
2025-12-23 17:10:31 +08:00

168 lines
5.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String, Object> redisTemplate;
/**
* 用户抽奖
*/
public PrizeInfo draw(Long userId) {
// 加锁防止并发抽奖
lock.lock();
try {
// 1. 获取用户累计抽数
int continuousDraws = getContinuousDraws(userId);
int newContinuousDraws = continuousDraws + 1;
// 2. 获取启用的奖品(排除谢谢惠顾)
List<Prize> 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<Prize> 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);
}
}