springboot实现抽奖算法

593 阅读2分钟

前言

当我们参加各种活动和抽奖时,抽奖环节总是能引起极大兴趣和关注。抽奖的公正性和随机性是保证活动公平和成功的重要因素。
本文将介绍抽奖算法的实现原理及其在Java编程语言中的应用,以帮助读者了解抽奖程序的实现细节,同时也为读者提供一些有用的参考和借鉴。

欢迎关注个人公众号【好好学技术】交流学习

创建抽奖表

CREATE TABLE `app_draw_prize` (
  `id` bigint NOT NULL,
  `activity_id` int unsigned DEFAULT NULL COMMENT '活动id',
  `name` varchar(50) DEFAULT NULL COMMENT '奖品名称',
  `url` varchar(200) DEFAULT NULL COMMENT '图片地址(未使用)',
  `value` varchar(10) DEFAULT '0' COMMENT '值',
  `probability` double unsigned DEFAULT '0' COMMENT '概率',
  `type` tinyint unsigned DEFAULT NULL COMMENT '类型',
  `status` tinyint unsigned DEFAULT '0' COMMENT '上下架状态:0下架1上架',
  `position` tinyint(1) DEFAULT NULL COMMENT '位置',
  `day_max_times` tinyint unsigned DEFAULT '0' COMMENT '每天最多中奖次数,大于0时生效',
  `month_max_times` tinyint unsigned DEFAULT '0' COMMENT '每月最多中奖次数,大于0时生效',
  `show` tinyint unsigned DEFAULT '0' COMMENT '是否首页展示中奖记录0否1是',
  `default_prize` tinyint(1) DEFAULT '0' COMMENT '抽奖上限时直接返回默认奖品',
  `ref_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '关联id',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='抽奖奖品表';

抽奖代码

算法思路

  • 1.将奖品按集合中顺序概率计算成所占比例区间,放入比例集合。并产生一个随机数加入其中,排序。
  • 2.排序后,随机数落在哪个区间,就表示那个区间的奖品被抽中。
  • 3.返回的随机数在集合中的索引,该索引就是奖品集合中的索引。
  • 4.比例区间的计算通过概率相加获得。
private DrawPrize doDrawPrize(Long activityId, Long userId) {  
    List<DrawPrize> drawPrizes = drawPrizeMapper.selectList(new LambdaQueryWrapper<DrawPrize>()  
            .eq(DrawPrize::getActivityId, activityId)
            .eq(DrawPrize::getStatus, 1)
            .orderByDesc(DrawPrize::getProbability));  
    List<Double> probLists = new ArrayList<>(drawPrizes.size());  
    Double sumProb = 0D;  
    for (DrawPrize drawPrize : drawPrizes) {  
        sumProb += drawPrize.getProbability();  
    }  
    if (sumProb <= 0) {  
        throw new IllegalArgumentException("抽奖概率设置错误");  
    }  
    Double rate = 0D;  
    for (DrawPrize drawPrize : drawPrizes) {  
        rate += drawPrize.getProbability();  
        probLists.add(rate / sumProb);  
    }  
    double random = Math.random();  
    probLists.add(random);  
    Collections.sort(probLists);  
    DrawPrize drawPrize = drawPrizes.get(probLists.indexOf(random));  
    if (drawPrize.getDayMaxTimes() > 0) {  
        //判断当天中奖次数  
        String daily = DateUtil.formatDate(new Date());  
        Long count = drawRecordMapper.selectCount(new LambdaQueryWrapper<DrawRecord>()  
                .eq(DrawRecord::getMemberId, userId)  
                .eq(DrawRecord::getPrizeId, drawPrize.getId())  
                .eq(DrawRecord::getDaily, daily));  
        if (count >= drawPrize.getDayMaxTimes()) {  
        //直接返回谢谢参与  
            return drawPrizes.stream()  
                    .filter(d -> d.getDefaultPrize() == 1)  
                    .findFirst()  
                    .orElseThrow(() -> new IllegalArgumentException("抽奖错误"));  
        }  
    }  
    if (drawPrize.getMonthMaxTimes() > 0) {  
        //判断当月中奖次数  
        Long count = drawRecordMapper.selectCount(new LambdaQueryWrapper<DrawRecord>()  
                .eq(DrawRecord::getMemberId, userId)  
                .eq(DrawRecord::getPrizeId, drawPrize.getId())  
                .eq(DrawRecord::getMonth, DateUtil.month(new Date())));  
        if (count >= drawPrize.getMonthMaxTimes()) {  
        //直接返回谢谢参与  
            return drawPrizes.stream()  
                    .filter(d -> d.getDefaultPrize() == 1)  
                    .findFirst()  
                    .orElseThrow(() -> new IllegalArgumentException("抽奖错误"));  
        }  
    }  
    return drawPrize;  
}