Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Java基于Redis编写的抽奖程序,奖品分批投放,同时满足给出的奖品抽中的概率和数量

Notifications You must be signed in to change notification settings

wavestone/luck-draw-java

Open more actions menu
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

本项目正确的测试步骤

  • 每次测试之前,请将数据库删除,导入我给的sql文件,不然会出现奖品不足的情况
  • 将项目跑起来
  • 然后在测试目录下面,执行奖品初始化的方法,最后执行多线程抽奖的测试代码。

需求

最近公司搞了个问卷调查的活动,用户填完问卷就能获得一次抽奖的就会,本来抽奖都是按概率来抽奖的,这种按概率的晚上挺多的相关实现方式,但是我们这个有个特定要求,就是奖品是固定的,抽奖分为三个阶段,第一阶段奖品固定个数,二等奖1个,三等奖2个,四等奖2个,五等奖15个,幸运奖180个。保证参与人数中奖率为100%。第二阶段,第三阶段我就不列出来啦,我把产品那边的需求文档放上来。 在这里插入图片描述 产品需求放在这,之前我也没做过此类抽奖的项目,去网上查找资料,基本全是按照概率来抽奖的,没有我们的这么操蛋。

我的实现思路

既然是抽奖,而且奖品固定,我想到的就是将所有的奖品都放到一个大容器中,用户抽奖时,抽到哪个就将相应的奖品数量从容器中移除掉一个。于是我开始对各个奖项设置为了各自抽奖编码,一等奖抽奖编码是 99999 二等奖是88888,三等奖是77777 一次类推,我做了下面这个表格方便查看。

  • 奖项数量及中奖代码
奖项 一等奖 二等奖 三等奖 四等奖 五等奖 幸运奖
物品 华为P30 kindle阅读器 蓝牙耳机 迷你风扇 定制小礼品 代金券或红包
数量 1 3 5 8 40 513
代码 99999 88888 77777 66666 55555 44444

数量和奖品代码搞清楚了,现在就在想那什么容器来装这些奖品代码呢?当时想过存到数据库中,现在奖品已经放到容器了,那第二个问题来了,我该如何保证这些奖品确保指定的奖品在固定的阶段被抽走呢?一共分为三阶段 0 - 200201 - 400401 - 570 超过570位参与者后,奖品全部都为幸运奖啦,准确的来分的话,可以分为前面三个阶段加上570 - ∞ ,这里就需要一个计数器来累计用户的参与人数,从而来判断该从第几阶段中抽奖, 于是想到redis中有个计数器的累加函数,想到这里同时想到redis中能存list类型的数据,所以打算放弃将数据存到数据库中,而是存到了reids中,既然分为四个阶段,我就将前面三个阶段的奖品以三个不同的key存储到redis中,以PRIZE_COUNT_200存放第一阶段的奖品,PRIZE_COUNT_400存放第二阶段的奖品,PRIZE_COUNT_570存放第三阶段的奖品。每次用户抽奖前,执行redis的自增函数,该函数在返回的时候回返回当前计数器的值,然后通过该值去判断该去哪个list中获取奖品,判断当前list的大小,并通过随机数生成一个该大小之间的一个整数,然后以该整数为下标去当前list中获取中奖码,拿到中奖码去数据库匹配当前奖品的余额,如果还有余额,将余额减1,并返回中奖结果。

  • 第一阶段奖品(PRIZE_COUNT_200)
奖项 一等奖 二等奖 三等奖 四等奖 五等奖 幸运奖
物品 华为P30 kindle阅读器 蓝牙耳机 迷你风扇 定制小礼品 代金券或红包
代码 99999 88888 77777 66666 55555 44444
数量 0 1 2 2 15 180
  • 第二阶段奖品(PRIZE_COUNT_400)
奖项 一等奖 二等奖 三等奖 四等奖 五等奖 幸运奖
物品 华为P30 kindle阅读器 蓝牙耳机 迷你风扇 定制小礼品 代金券或红包
代码 99999 88888 77777 66666 55555 44444
数量 0 1 2 2 15 180
  • 第三阶段奖品(PRIZE_COUNT_570)
奖项 一等奖 二等奖 三等奖 四等奖 五等奖 幸运奖
物品 华为P30 kindle阅读器 蓝牙耳机 迷你风扇 定制小礼品 代金券或红包
代码 99999 88888 77777 66666 55555 44444
数量 1 1 1 4 10 153

初始化奖品

这里我们先将抽奖代码放到各自对应的list当中,代码如下:

        //计数器初始化为0,并将三个list钟的数据全部清空
        listTools.initIncreCount(RedisKeyConstant.PRIZE_COUNT);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_200);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_400);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_570);



		//获取一个200以内随机数,将重要的奖品放到幸运奖中间位置
        int temp = new Random().nextInt(170);
        /****************第一阶段*************************/
        List<String> list200 = new ArrayList<>();

        //三等奖 2个
        list200.add(LuckCodeEnum.GRADE_THREE.getCode());
        //四等奖 2个
        list200.add(LuckCodeEnum.GRADE_FOUR.getCode());

        //五等奖 15个
        for (int i = 0; i< 15; i++) {
            list200.add(LuckCodeEnum.GRADE_FIVE.getCode());
        }

        //幸运奖
        for (int j = 0; j < 180; j++) {
            list200.add(LuckCodeEnum.GRADE_LUCK.getCode());
            //放二等奖进去
            if (j == temp) {
                //二等奖 1个
                list200.add(LuckCodeEnum.GRADE_TWO.getCode());
                list200.add(LuckCodeEnum.GRADE_THREE.getCode());
                list200.add(LuckCodeEnum.GRADE_FOUR.getCode());
            }
        }
        /***********************************************/

        /*****************第二阶段***********************/
        List<String> list400 = new ArrayList<>();
        //二等奖 1个
        list400.add(LuckCodeEnum.GRADE_TWO.getCode());
        //三等奖 2个
        list400.add(LuckCodeEnum.GRADE_THREE.getCode());
        list400.add(LuckCodeEnum.GRADE_THREE.getCode());
        //四等奖 2个
        list400.add(LuckCodeEnum.GRADE_FOUR.getCode());
        list400.add(LuckCodeEnum.GRADE_FOUR.getCode());

        //五等奖 15个
        for (int i = 0; i< 15; i++) {
            list400.add(LuckCodeEnum.GRADE_FIVE.getCode());
        }

        //幸运奖
        for (int j = 0; j < 180; j++) {
            list400.add(LuckCodeEnum.GRADE_LUCK.getCode());
        }
        /**********************************************/

        /*****************第三阶段***********************/
        List<String> list570 = new ArrayList<>();

        //三等奖 2个
        list570.add(LuckCodeEnum.GRADE_THREE.getCode());
        //四等奖 4个
        list570.add(LuckCodeEnum.GRADE_FOUR.getCode());


        //五等奖 15个
        for (int i = 0; i< 10; i++) {
            list570.add(LuckCodeEnum.GRADE_FIVE.getCode());
        }

        int index = new Random().nextInt(130);
        //幸运奖
        for (int j = 0; j < 153; j++) {
            list570.add(LuckCodeEnum.GRADE_LUCK.getCode());
            if (index == j) {
                list570.add(LuckCodeEnum.GRADE_ONE.getCode());
                list570.add(LuckCodeEnum.GRADE_FOUR.getCode());

            }
            if (index +20 == j) {
                //二等奖 1个
                list570.add(LuckCodeEnum.GRADE_TWO.getCode());
                list570.add(LuckCodeEnum.GRADE_FOUR.getCode());
                list570.add(LuckCodeEnum.GRADE_FOUR.getCode());
            }
        }

        listTools.leftPushList(RedisKeyConstant.PRIZE_COUNT_200,list200);
        listTools.leftPushList(RedisKeyConstant.PRIZE_COUNT_400,list400);
        listTools.leftPushList(RedisKeyConstant.PRIZE_COUNT_570,list570);
        /**********************************************/

奖品已经放到了三个容器当前,现在就是将奖品数量持久化,放到数据库当中。表结构以及数量如下,这里幸运奖设置为9999是因为来抽奖的用户是未知的,第570位以后的用户,全部为幸运奖。 在这里插入图片描述

开始抽奖

假如当前用户为第208位来,当前计数器为 208 我们就该去PRIZE_COUNT_400该集合中获取奖品,但是奖品该怎么拿,并且确保每个奖品都被抽光呢?我个人的想法是,首先拿到PRIZE_COUNT_400该值得集合的size,然后通过随机数生成一个size以内的int类型的数,然后通过集合下标获取到中奖的号码。

//通过redis拿到当前的prize_count的大小
Long prizeCount = listTools.increCount(RedisKeyConstant.PRIZE_COUNT);
//判断该去哪个容器中获取奖品
if (prizeCount <= 570) {
    String prizeCodeKey = null;
    if (prizeCount <= 200) {
        prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_200;
    } else if (prizeCount <= 400) {
        prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_400;
    } else if (prizeCount <= 570) {
        prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_570;
    }
 //获取到集合的大小
Long size = listTools.getListSize(prizeCodeKey);
 // 随机算法,获取随机下标并取出对应的奖品编码
 int index = new Random().nextInt(size.intValue());
 //获取到的中奖号码
 final String code = listTools.getIndexValue(prizeCodeKey,index);

通过获取到的中奖code,我们假如这里是88888去数据库查询当前奖品的余额数量, 如果余额大于0,则让该奖品数量减少1份,然后把redis中PRIZE_COUNT_400list中通过 listOperations.remove(key,1,value);移除掉第一个遇到的88888,这样当前list的size就会减少1,按照该方法一直抽下去,list的size会越来越小,抽奖人数计数器等于400的时候,该list中的奖品全部被抽完。

抽奖核心代码

       //判断该用户是否已经中奖
        boolean flag = userService.isCanLuckDraw(name);
        if (!flag) {
            log.warn("{},已经抽过奖,无需重复抽奖",name);
            throw new LuckDrawException(ResultCode.LUCK_DRAW_AGAIN);
        }
        boolean isOk = false;
        //通过redis拿到当前的prize_count的大小
        Long prizeCount = listTools.increCount(RedisKeyConstant.PRIZE_COUNT);

        //默认幸运奖
        String luckCode = LuckCodeEnum.GRADE_LUCK.getCode();
        if (prizeCount <= 570) {
            String prizeCodeKey = null;
            if (prizeCount <= 200) {
                prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_200;
            } else if (prizeCount <= 400) {
                prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_400;
            } else if (prizeCount <= 570) {
                prizeCodeKey = RedisKeyConstant.PRIZE_COUNT_570;
            }

            //这里写循环是为了补偿那些拿到数据库没有的库存的奖品
            synchronized (PrizeController.class) {
                for (int i = 0; i < 3; i++) {
                    Long size = listTools.getListSize(prizeCodeKey);
                    // 随机算法,获取随机下标并取出对应的奖品编码
                    int index = new Random().nextInt(size.intValue());
                    //获取到的中奖号码
                    final String code = listTools.getIndexValue(prizeCodeKey,index);
                    //将中奖号码赋予给外层的中奖号码
                    luckCode = code;
                    //执行的结果,返回true表示数据库还有余额
                    //取数据库核对该奖品是否还有余额,有自减 没有重新进行随机算法 最后将中奖信息插入数据库
                    isOk = prizeService.resultTrue(name,code);
                    if (isOk) {
                        //移除redis中对应list中的一个奖项
                        listTools.removeFirstValue(prizeCodeKey,code);
                        break;
                    }
                    log.error("{},获取{}奖品,库存余额不足 这是第几次获取:{}",name,luckCode,i+1);
                }
            }
        }
        //如果该代码执行了说明拿出的奖品数据库余额不足,直接返回幸运奖
        if (!isOk) {
            if (prizeCount > 570)
                log.warn("{},奖品已经全部发放完毕,直接返回幸运奖",name);
            else
                log.error("{},拿到的奖品数据库余额不足,直接返回幸运奖",name);
            prizeService.resultTrue(name, luckCode);
        }
        return ResultUtils.success("您中的奖为 ["+luckCode+"]");

这就是我自己的自创该次抽奖的想法,有什么说的不对的地方请各位能够提出来一起讨论一下。

项目源码:https://github.com/niezhiliang/luck-draw

About

Java基于Redis编写的抽奖程序,奖品分批投放,同时满足给出的奖品抽中的概率和数量

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 94.9%
  • PLpgSQL 5.1%
Morty Proxy This is a proxified and sanitized view of the page, visit original site.