最近自己想要实现抽奖类需求,我查阅了一下网上的各种资料,结合自身的需求,考虑的有如下几点问题:
1.抽奖概率如何设计及计算?
2.库存如何设计能保证平均分布?
3.最终我如何测试验证是否满足我的预期?
最终设计思路如下:
数据表层面:
每日库存表:存储每日可以抽的奖品
每日抽奖次数表:每日每个用户剩余能抽多少次。
抽奖记录表:记录所有抽奖的记录,包含支付状态结果等。
中奖记录表:记录中奖的记录,用户后续发送奖品等环节。与抽奖记录表拆分是为了减少抽奖的压力。
抽奖基本信息表:活动起止时间,其他相关限制等
奖品信息表:奖品的基本信息。
具体到代码设计:
概率设计:
创建抽奖是会在后台管理界面设定每一个的抽奖概率。假设设计的是最小千分之一。当时页面输入的设计是填写__%的值,也就是最小在页面上可以填入0.1%。
奖品1:1%
奖品2:0.1%
奖品3:10%
/**
* 抽奖核心算法(删减过一些不相干的代码)
* 抽奖思路是把奖品概率放到一个数组里,比如设计单位是千分之一,就是一个0-1000的数组。其中,(0,1]是奖品2,(1,11]就是
* 奖品1, (11,121]就是奖品3.(121,1000]就是未中奖。这样random一个0-1000的整数,看结果位于哪个区间,如果落在对应的奖品
* 区间且查询的当日库存里有对应的奖品则中奖,不然就算落在对应区间没有对应的奖品也视为未中奖。
* @return 中奖的奖品对象
*/
public UserJoinDTO startLottery(String lotteryId, String lotteryChanceType, String memberId, int costMileage,String joinId,Integer limitTimes,int lastPaidTimes) {
String rewardId = "";
//获取今日剩余商品库存(包含商品id,有效期等信息)
List<InstantRewardsStockDTO> larList = lotteryInstantRewardsStockDao.getAllCurrentStockList(lotteryId);
//redis分布式锁,避免高并发把库存搞错
Lock stockLock = null;
LotteryActicityRewardsDTO award = null;
try {
if (lockOpen()) {
stockLock = redissonClient.getLock("LOTTERY:LOCK:STOCK:" + lotteryId);
stockLock.lock();
}
int num = 0;
Map<InstantRewardsStockDTO, int[]> stockMap = new HashMap<>();
for (InstantRewardsStockDTO stock : larList1) {
// 生效且库存大于0的奖品
if (stock.getCurrentStock() > 0) {
stockMap.put(stock, new int[]{num, num + (int) (stock.getRewardProbability() * 10)});
logger.info("奖项{}的抽奖域为{},{}", stock.getRewardId(), num, num + (int) (stock.getRewardProbability() * 10));
num += (int) (stock.getRewardProbability() * 10);
}
}
// 开始抽奖
int hitNum = new Random().nextInt(1000);
logger.info("会员在活动{}随机到的抽奖值为{}", lotteryId, hitNum);
boolean isWin = false;
for (Map.Entry<InstantRewardsStockDTO, int[]> entry : stockMap.entrySet()) {
if (hitNum >= entry.getValue()[0] && hitNum < entry.getValue()[1]) {
// 扣减库存
InstantRewardsStockDTO stock = entry.getKey();
stock.setCurrentStock(stock.getCurrentStock() - 1);
rewardId = stock.getRewardId();
lotteryInstantRewardsStockDao.deleteOneReward(rewardId,lotteryId,currentDate);
isWin = true;
logger.info("会员抽到奖品{}", rewardId);
break;
}
}
if (!isWin) {
logger.info("会员未抽到奖品");
}
ujDto.setMemberId(memberId);
ujDto.setJionTime(new Date().toString());
ujDto.setRewardId(rewardId);
ujDto.setLotteryId(lotteryId);
if(rewardId!=""){
Map<String, Object> reward = lotteryActivityRewardsService.findRewardByRewardId(rewardId,lotteryId);
LotteryActivityRewardsDTO lar1 =(LotteryActivityRewardsDTO)reward.get("reward");
//更新抽奖记录,返回成功
}else{
//更新记录,返回失败
}
//抽完之后,如果扣减成功则减掉当日抽奖次数库存
LotteryTodayJoinTimesDTO ltjt = new LotteryTodayJoinTimesDTO();
ltjt.setMemberId(memberId);
ltjt.setLotteryId(lotteryId);
lotteryTodayJoinTimesDao.updateLotteryTodayJoinTimes(ltjt);
} finally {
if (stockLock != null) {
stockLock.unlock();
}
}
return ujDto;
}