java 抽奖算法心得

最近自己想要实现抽奖类需求,我查阅了一下网上的各种资料,结合自身的需求,考虑的有如下几点问题:

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;
    }

  

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值