思路
将奖品按集合中顺序概率计算成所占比例区间,放入比例集合。并产生一个随机数加入其中,排序。排序后,随机数落在哪个区间,就表示那个区间的奖品被抽中。返回的随机数在集合中的索引,该索引就是奖品集合中的索引。比例区间的计算通过概率相加获得。如上图:假设抽中苹果的概率为0.2,香蕉的概率为0.3,西瓜的概率为0.5。我们把它们做成一个数组按概率从小到大排列。然后生成一个0-1的随机数,如果落到哪里,对应的就是奖品。
实现
活动表
奖品表
抽奖记录表
代码语言:javascript复制抽奖接口
@ApiOperation("开始抽奖")
@RequestMapping(value = "/lottery",method = RequestMethod.POST)
public Prize lottery(@RequestBody @Validated LotteryDto dto){
return activityService.lottery(dto);
}
@Data
public class LotteryDto {
@ApiModelProperty(value = "抽奖活动id")
@NotNull(message ="抽奖活动id不能为空")
private Integer id;
}
代码语言:javascript复制/**
* 抽奖
*
* @param dto
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public prize lottery(LotteryDto dto) {
Activity activity;
synchronized (this){
//活动信息
activity = this.getById(dto.getId());
}
if (activity.getStartTime().after(new Date())) {
throw new Exception("活动未开始");
}
if (activity.getEndTime().before(new Date())) {
throw new Exception("活动已结束");
}
QueryWrapper<DrawRecord> qws = new QueryWrapper<>();
qws.eq("activity_id", dto.getId());
qws.eq("user_id", dto.getUserId());
//用户抽奖次数
int integer = drawRecordMapper.selectCount(qws);
//限制抽奖次数
Integer userMax = activity.getUserMax();
if (userMax != null && integer >= userMax) {
throw new Exception("您已参数活动最大次数限制");
}
//开始抽奖
Prize prize = gift(activity, loginUser.getUserId());
if (Objects.nonNull(prize)){
//减库存
synchronized (this){
prize.setInventory(prize.getInventory()-1);
prizeMapper.updateById(prize);
}
//记录入库
DrawRecord drawRecord = new DrawRecord();
drawRecord.setUserId(loginUser.getUserId())
.setState(prize.getType()==0 ? 0 : 1)
.setActivityId(dto.getId())
.setPrizeId(prize.getType()==0 ? null : prize.getId());
int insert = drawRecordMapper.insert(drawRecord);
if (insert > 0){
return prize;
}
}
}
代码语言:javascript复制/**
* 抽奖方法
* @param activity 活动
* @param userId 用户id
* @return
*/
private Prize gift(Activity activity, int userId) {
List<String> ids = Arrays.asList(activity.getPrizeId().split(","));
//奖品池
List<Prize> prizes = prizeMapper.selectBatchIds(ids);
//过滤没有库存的奖品
List<Prize> prizeList = prizes.stream().filter(prize -> prize.getInventory() > 0).collect(Collectors.toList());
//用户在本次活动的抽中记录
List<DrawRecord> drawRecords = drawRecordMapper.selectList(new QueryWrapper<DrawRecord>().eq("user_id", userId)
.eq("state", 1).eq("activity_id", activity.getId()).isNotNull("prize_id"));
if (CollectionUtil.isNotEmpty(drawRecords)){
//以奖品id分组
Map<Integer, List<DrawRecord>> drawRecordList = drawRecords.stream().collect(Collectors.groupingBy(DrawRecord::getPrizeId));
//去除符合抽中次数条件的奖品
Iterator it = prizeList.iterator();
while (it.hasNext()) {
Prize prize = (Prize) it.next();
//该奖品用户已抽中次数
List<DrawRecord> dr = drawRecordList.get(prize.getId());
if (CollectionUtil.isNotEmpty(dr)){
int size = drawRecordList.get(prize.getId()).size();
//用户已达到最大抽中次数
if (size >= prize.getUserMax()) {
prizeList.remove(prize);
}
}
}
}
//开始抽奖
int index = LotteryUtil.drawGift(prizeList);
Prize prize = prizeList.get(index);
return prize;
}
代码语言:javascript复制import com.java.bf.ldkj.entity.Prize;
import io.netty.util.internal.ThreadLocalRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 抽奖工具类
* 将奖品按集合中顺序概率计算成所占比例区间,放入比例集合。
* 并产生一个随机数加入其中,排序。
* 排序后,随机数落在哪个区间,就表示那个区间的奖品被抽中。
* @author adu
* @date 2022/9/30.
*/
public class LotteryUtil {
public static int drawGift(List<Prize> prizes) {
if (null != prizes && prizes.size() > 0) {
List<Double> orgProbList = new ArrayList<Double>(prizes.size());
for (Prize prize : prizes) {
// 按顺序将概率添加到集合中
orgProbList.add(prize.getProbability());
}
return draw(orgProbList);
}
return -1;
}
public static int draw(List<Double> giftProbList) {
List<Double> sortRateList = new ArrayList<>();
// 计算概率总和
Double sumRate = 0D;
for (Double prob : giftProbList) {
sumRate = prob;
}
if (sumRate != 0) {
double rate = 0D;
// 概率所占比例
for (Double prob : giftProbList) {
rate = prob;
// 构建一个比例区段组成的集合(避免概率和不为1)
sortRateList.add(rate / sumRate);
}
// 随机生成一个随机数,并排序
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
double random = threadLocalRandom.nextDouble(0, 1);
sortRateList.add(random);
Collections.sort(sortRateList);
// 返回该随机数在比例集合中的索引
return sortRateList.indexOf(random);
}
return -1;
}
}