168 lines
5.8 KiB
Java
168 lines
5.8 KiB
Java
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);
|
||
}
|
||
}
|