Java实现抽奖功能

2022-10-30 15:05:13 浏览数 (1)

思路

将奖品按集合中顺序概率计算成所占比例区间,放入比例集合。并产生一个随机数加入其中,排序。排序后,随机数落在哪个区间,就表示那个区间的奖品被抽中。返回的随机数在集合中的索引,该索引就是奖品集合中的索引。比例区间的计算通过概率相加获得。如上图:假设抽中苹果的概率为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;
    }
}

0 人点赞