記錄一次基于redis抽獎固定獎品數(shù)量獎品分批次投放,超過獎品數(shù)量的客戶直接返回幸運獎

需求

最近公司搞了個問卷調(diào)查的活動,用戶填完問卷就能獲得一次抽獎的就會谍肤,本來抽獎都是按概率來抽獎的啦租,這種按概率的晚上挺多的相關(guān)實現(xiàn)方式,但是我們這個有個特定要求荒揣,就是獎品是固定的篷角,抽獎分為三個階段,第一階段獎品固定個數(shù)系任,二等獎1個恳蹲,三等獎2個,四等獎2個赋除,五等獎15個阱缓,幸運獎180個。保證參與人數(shù)中獎率為100%举农。第二階段,第三階段我就不列出來啦敞嗡,我把產(chǎn)品那邊的需求文檔放上來颁糟。


在這里插入圖片描述

產(chǎn)品需求放在這,之前我也沒做過此類抽獎的項目喉悴,去網(wǎng)上查找資料棱貌,基本全是按照概率來抽獎的,沒有我們的這么操蛋箕肃。

我的實現(xiàn)思路

既然是抽獎婚脱,而且獎品固定,我想到的就是將所有的獎品都放到一個大容器中勺像,用戶抽獎時障贸,抽到哪個就將相應(yīng)的獎品數(shù)量從容器中移除掉一個。于是我開始對各個獎項設(shè)置為了各自抽獎編碼吟宦,一等獎抽獎編碼是 99999 二等獎是88888,三等獎是77777 一次類推篮洁,我做了下面這個表格方便查看。

  • 獎項數(shù)量及中獎代碼
獎項 一等獎 二等獎 三等獎 四等獎 五等獎 幸運獎
物品 華為P30 kindle閱讀器 藍(lán)牙耳機 迷你風(fēng)扇 定制小禮品 代金券或紅包
數(shù)量 1 3 5 8 40 513
代碼 99999 88888 77777 66666 55555 44444

數(shù)量和獎品代碼搞清楚了殃姓,現(xiàn)在就在想那什么容器來裝這些獎品代碼呢袁波?當(dāng)時想過存到數(shù)據(jù)庫中,現(xiàn)在獎品已經(jīng)放到容器了蜗侈,那第二個問題來了篷牌,我該如何保證這些獎品確保指定的獎品在固定的階段被抽走呢?一共分為三階段 0 - 200 踏幻、201 - 400 枷颊、401 - 570 超過570位參與者后,獎品全部都為幸運獎啦,準(zhǔn)確的來分的話偷卧,可以分為前面三個階段加上570 - ∞豺瘤,這里就需要一個計數(shù)器來累計用戶的參與人數(shù),從而來判斷該從第幾階段中抽獎听诸, 于是想到redis中有個計數(shù)器的累加函數(shù)坐求,想到這里同時想到redis中能存list類型的數(shù)據(jù),所以打算放棄將數(shù)據(jù)存到數(shù)據(jù)庫中晌梨,而是存到了reids中桥嗤,既然分為四個階段,我就將前面三個階段的獎品以三個不同的key存儲到redis中仔蝌,以PRIZE_COUNT_200存放第一階段的獎品泛领,PRIZE_COUNT_400存放第二階段的獎品,PRIZE_COUNT_570存放第三階段的獎品敛惊。每次用戶抽獎前渊鞋,執(zhí)行redis的自增函數(shù),該函數(shù)在返回的時候回返回當(dāng)前計數(shù)器的值瞧挤,然后通過該值去判斷該去哪個list中獲取獎品,判斷當(dāng)前l(fā)ist的大小锡宋,并通過隨機數(shù)生成一個該大小之間的一個整數(shù),然后以該整數(shù)為下標(biāo)去當(dāng)前l(fā)ist中獲取中獎碼特恬,拿到中獎碼去數(shù)據(jù)庫匹配當(dāng)前獎品的余額执俩,如果還有余額,將余額減1癌刽,并返回中獎結(jié)果役首。

  • 第一階段獎品(PRIZE_COUNT_200)
獎項 一等獎 二等獎 三等獎 四等獎 五等獎 幸運獎
物品 華為P30 kindle閱讀器 藍(lán)牙耳機 迷你風(fēng)扇 定制小禮品 代金券或紅包
代碼 99999 88888 77777 66666 55555 44444
數(shù)量 0 1 2 2 15 180
  • 第二階段獎品(PRIZE_COUNT_400)
獎項 一等獎 二等獎 三等獎 四等獎 五等獎 幸運獎
物品 華為P30 kindle閱讀器 藍(lán)牙耳機 迷你風(fēng)扇 定制小禮品 代金券或紅包
代碼 99999 88888 77777 66666 55555 44444
數(shù)量 0 1 2 2 15 180
  • 第三階段獎品(PRIZE_COUNT_570)
獎項 一等獎 二等獎 三等獎 四等獎 五等獎 幸運獎
物品 華為P30 kindle閱讀器 藍(lán)牙耳機 迷你風(fēng)扇 定制小禮品 代金券或紅包
代碼 99999 88888 77777 66666 55555 44444
數(shù)量 1 1 1 4 10 153

初始化獎品

這里我們先將抽獎代碼放到各自對應(yīng)的list當(dāng)中,代碼如下:

        //計數(shù)器初始化為0显拜,并將三個list鐘的數(shù)據(jù)全部清空
        listTools.initIncreCount(RedisKeyConstant.PRIZE_COUNT);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_200);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_400);
        listTools.delete(RedisKeyConstant.PRIZE_COUNT_570);



        //獲取一個200以內(nèi)隨機數(shù)衡奥,將重要的獎品放到幸運獎中間位置
        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());
            //放二等獎進(jìn)去
            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);
        /**********************************************/

獎品已經(jīng)放到了三個容器當(dāng)前,現(xiàn)在就是將獎品數(shù)量持久化讼油,放到數(shù)據(jù)庫當(dāng)中杰赛。表結(jié)構(gòu)以及數(shù)量如下,這里幸運獎設(shè)置為9999是因為來抽獎的用戶是未知的矮台,第570位以后的用戶乏屯,全部為幸運獎。


在這里插入圖片描述

開始抽獎

假如當(dāng)前用戶為第208位來瘦赫,當(dāng)前計數(shù)器為 208 我們就該去PRIZE_COUNT_400該集合中獲取獎品辰晕,但是獎品該怎么拿,并且確保每個獎品都被抽光呢确虱?我個人的想法是含友,首先拿到PRIZE_COUNT_400該值得集合的size,然后通過隨機數(shù)生成一個size以內(nèi)的int類型的數(shù),然后通過集合下標(biāo)獲取到中獎的號碼。

//通過redis拿到當(dāng)前的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);
 // 隨機算法窘问,獲取隨機下標(biāo)并取出對應(yīng)的獎品編碼
 int index = new Random().nextInt(size.intValue());
 //獲取到的中獎號碼
 final String code = listTools.getIndexValue(prizeCodeKey,index);

通過獲取到的中獎code辆童,我們假如這里是88888去數(shù)據(jù)庫查詢當(dāng)前獎品的余額數(shù)量, 如果余額大于0惠赫,則讓該獎品數(shù)量減少1份把鉴,然后把redis中PRIZE_COUNT_400list中通過listOperations.remove(key,1,value);移除掉第一個遇到的88888,這樣當(dāng)前l(fā)ist的size就會減少1,按照該方法一直抽下去儿咱,list的size會越來越小庭砍,抽獎人數(shù)計數(shù)器等于400的時候,該list中的獎品全部被抽完混埠。

抽獎核心代碼

       //判斷該用戶是否已經(jīng)中獎
        boolean flag = userService.isCanLuckDraw(name);
        if (!flag) {
            log.warn("{},已經(jīng)抽過獎怠缸,無需重復(fù)抽獎",name);
            throw new LuckDrawException(ResultCode.LUCK_DRAW_AGAIN);
        }
        boolean isOk = false;
        //通過redis拿到當(dāng)前的prize_count的大小
        Long prizeCount = listTools.increCount(RedisKeyConstant.PRIZE_COUNT);

        //默認(rèn)幸運獎
        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;
            }

            //這里寫循環(huán)是為了補償那些拿到數(shù)據(jù)庫沒有的庫存的獎品
            synchronized (PrizeController.class) {
                for (int i = 0; i < 3; i++) {
                    Long size = listTools.getListSize(prizeCodeKey);
                    // 隨機算法,獲取隨機下標(biāo)并取出對應(yīng)的獎品編碼
                    int index = new Random().nextInt(size.intValue());
                    //獲取到的中獎號碼
                    final String code = listTools.getIndexValue(prizeCodeKey,index);
                    //將中獎號碼賦予給外層的中獎號碼
                    luckCode = code;
                    //執(zhí)行的結(jié)果钳宪,返回true表示數(shù)據(jù)庫還有余額
                    //取數(shù)據(jù)庫核對該獎品是否還有余額揭北,有自減 沒有重新進(jìn)行隨機算法 最后將中獎信息插入數(shù)據(jù)庫
                    isOk = prizeService.resultTrue(name,code);
                    if (isOk) {
                        //移除redis中對應(yīng)list中的一個獎項
                        listTools.removeFirstValue(prizeCodeKey,code);
                        break;
                    }
                    log.error("{},獲取{}獎品,庫存余額不足 這是第幾次獲取:{}",name,luckCode,i+1);
                }
            }
        }
        //如果該代碼執(zhí)行了說明拿出的獎品數(shù)據(jù)庫余額不足使套,直接返回幸運獎
        if (!isOk) {
            if (prizeCount > 570)
                log.warn("{}罐呼,獎品已經(jīng)全部發(fā)放完畢,直接返回幸運獎",name);
            else
                log.error("{}侦高,拿到的獎品數(shù)據(jù)庫余額不足,直接返回幸運獎",name);
            prizeService.resultTrue(name, luckCode);
        }
        return ResultUtils.success("您中的獎為 ["+luckCode+"]");

這就是我自己的自創(chuàng)該次抽獎的想法厌杜,有什么說的不對的地方請各位能夠提出來一起討論一下奉呛。

項目源碼:https://github.com/niezhiliang/luck-draw

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市夯尽,隨后出現(xiàn)的幾起案子瞧壮,更是在濱河造成了極大的恐慌,老刑警劉巖匙握,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咆槽,死亡現(xiàn)場離奇詭異,居然都是意外死亡圈纺,警方通過查閱死者的電腦和手機秦忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛾娶,“玉大人灯谣,你說我怎么就攤上這事』桌牛” “怎么了胎许?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我辜窑,道長钩述,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任穆碎,我火速辦了婚禮牙勘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惨远。我一直安慰自己谜悟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布北秽。 她就那樣靜靜地躺著葡幸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贺氓。 梳的紋絲不亂的頭發(fā)上蔚叨,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音辙培,去河邊找鬼蔑水。 笑死,一個胖子當(dāng)著我的面吹牛扬蕊,可吹牛的內(nèi)容都是我干的搀别。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尾抑,長吁一口氣:“原來是場噩夢啊……” “哼歇父!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起再愈,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤榜苫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翎冲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垂睬,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年抗悍,在試婚紗的時候發(fā)現(xiàn)自己被綠了驹饺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡檐春,死狀恐怖逻淌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疟暖,我是刑警寧澤卡儒,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布田柔,位于F島的核電站,受9級特大地震影響骨望,放射性物質(zhì)發(fā)生泄漏硬爆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一擎鸠、第九天 我趴在偏房一處隱蔽的房頂上張望缀磕。 院中可真熱鬧,春花似錦劣光、人聲如沸袜蚕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牲剃。三九已至,卻和暖如春雄可,著一層夾襖步出監(jiān)牢的瞬間凿傅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工数苫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聪舒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓虐急,卻偏偏與公主長得像箱残,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子止吁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 抽獎活動API 獲取參與抽獎名單 分頁獲取參與抽獎名單 導(dǎo)出參與名單的報表模板 導(dǎo)入?yún)⑴c名單 內(nèi)定中獎名單列表 獲...
    kingsonCai閱讀 2,517評論 1 1
  • 一般的抽獎管理功能疚宇,基本是在一個獎池中放一堆獎品,分別給它們設(shè)置不同的數(shù)量和概率赏殃,在獎品沒有發(fā)完的情況下,...
    wwking閱讀 10,204評論 3 16
  • 一般的抽獎管理功能间涵,基本是在一個獎池中放一堆獎品仁热,分別給它們設(shè)置不同的數(shù)量和概率,在獎品沒有發(fā)完的情況下勾哩,概...
    wwking02閱讀 3,882評論 1 4
  • /* *經(jīng)典的概率算法秽褒, *$proArr是一個預(yù)先設(shè)置的數(shù)組壶硅, *假設(shè)數(shù)組為:array(100,200,300...
    抬頭紋小鑫閱讀 843評論 0 2
  • 簡單的對打,魔性的鏈錘销斟,爽快的爆頭庐椒,Epic Flail是款第一次玩就會讓人喜歡上的游戲。 簡單爽快蚂踊,創(chuàng)意十足约谈。對...
    XYiP閱讀 476評論 0 0