nnnn
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user