用djang+redis實現可設置概率的大轉盤抽獎

Hello,大家好,我以及很久很久沒有在csdn上寫文章了,上一篇文章還是在2018年一月份寫的,現在以及過去了大半年,我也從一個做php開發實習生變成了一個目前做python開發的搬磚工,今後我會每週寫一篇技術性博客供大家交流,希望大佬們多多指正。

廢話不多說,我們開始正文。

目前公司的項目是一個做遊戲盒子的微信小程序,裏面涉及到一個大轉盤抽獎的系統,UI給的設計圖如下:

要求是可以設置各個獎品,以及控制每個獎品獲取的概率。

這該怎麼辦呢,隨機數是肯定要用到的,但是該如何做呢

我在網上搜索的大轉盤算法,找到了這篇文章:鏈接

這位大佬給出了兩種方式解決問題:

第一種:根據設置的概率,把獎品的數目相應的翻倍,比方說10%的概率,所有的獎品總量爲長度爲100的數組,則把該獎品×10,存放於數組中

A [0] = iphone 
A [1] = iphone 
A [2] = iphone 
....

A [10] = iphone

A [11] = 100元購物卷 
A [12] = 100元購物卷 

這種方法的缺陷很明顯,如果需要設置一個萬分之一的概率,那就得將數組長度設置爲10000,十萬分之一的概率那就得將數組設置爲10W,這會造成內存極大的消耗,所以不推薦使用。

第二種方法:

將概率映射爲數字段,若數字段一共有10000,10%的概率也就是取0-1000之間的數字就可以了,直接上圖:

很明顯,這種方式很巧妙,不會因爲大概率事件(比方說一萬分之一,十萬分之一)可能會造成的大量佔用內存的情況。

因此我決定採用第二種方式來做抽獎處理,在那位大佬的文章中,他使用了Java的中的散列結構來做,但是Python的中,並沒有散列數據結構,我想到的就是借用的Redis的哈希來處理這個問題,下面我們來看代碼:

我目前的項目是使用django,rest framework框架來提供接口給微信小程序用的,數據庫使用postgresql

首先需要一張存儲獎品的表:

class WechatLuckyDial(models.Model):

    id = models.AutoField(primary_key=True)
    prize_name = models.CharField(max_length=100, default='', verbose_name='獎品文字')
    prize_img_num = models.IntegerField(default=0, verbose_name='大轉盤上鑽石的數目') # 0 代表使用紅包的圖片
    prize_type = models.IntegerField(default=0, verbose_name='獎品類型') # 0 鑽石獎勵 1 紅包獎勵
    prize_amount = models.IntegerField(default=0, verbose_name='獎品數目') # 0 代表隨機獎勵
    prize_probability = models.IntegerField(default=0, verbose_name='獲獎概率') # 單位%  20 就是20%的意思
    create_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'hd_wechat_luckdial'
        ordering = ('id',)

其中prize_img_num不需要管,這是爲了配合前臺展示圖片才設立的字段。

最重要的就是獎品的id和prize_probability(概率性),

然後python manage.py makemigrations {模塊名}和python manage.py migrate {模塊名}一下,在數據庫中創建表。

隨後創建這個模型的視圖集中,實現對這張表的增刪改查:

class WechatLuckyDialViewSet(ModelViewSet):
    queryset = WechatLuckyDial.objects.all()
    serializer_class = WechatLuckyDialSerizlizer

其中serialzier_class也普通,沒有做什麼特別處理:

class WechatLuckyDialSerizlizer(ModelSerializer):
    prize_name = serializers.CharField(required=True, error_messages={'required': '獎勵名稱不能爲空'})
    prize_type = serializers.CharField(required=True, error_messages={'required': '獎勵類型不能爲空'})
    prize_probability = serializers.CharField(required=True, error_messages={'required': '概率不能爲空'})

    class Meta:
        model = WechatLuckyDial
        fields = '__all__'

    def to_representation(self, instance):
        ret = super(WechatLuckyDialSerizlizer, self).to_representation(instance)

        ret.update({
            'create_at': instance.create_at.strftime('%Y-%m-%d %H:%M:%S')
        })
        return ret

隨後將viewset映射爲url,我先用postman往裏面存入相應的獎勵以及設置好概率。

好了,現在基礎的數據以及最重要的概率以及有了,目前我設置的概率爲19%,19%,19%,19%,19%,5%,其中隨機現金紅包概率爲5%,現在還需要將概率映射爲數字段。

數字段由一個最大值和一個最小值構成,獎品在整個序列的位置和概率大小決定了最大值和最小值。當有了這個最大值和最小值之後,每當用戶點抽獎,我們隨機出一個數字,要是這個數字存在與最大值和最小值之間,則中這個獎勵。

所以我這裏選擇使用Redis的哈希的結構去存儲這兩個值,我設立了兩張表:

# 兩張redis的hash表:
# 一張存儲大轉盤單個獎勵概率最大值的表 field luckydial_id
LUCKYDIAL_PRIZE_SCOPE_MAX = 'wechat_luckdial_prize_scope_max'
# 一張存儲大轉盤單個獎勵概率最小值的表
LUCKYDIAL_PRIZE_SCOPE_MIN = 'wechat_luckdial_prize_scope_min'

一張存儲最大值,一張存儲最小值,現場爲獎品的ID,VAL爲相應的值。以上代碼我寫在項目的settings.py文件中,防止表名出錯

所以接下來我們得通過計算,獲得每個獎品的最大值和最小值。

我用了rest framework的APIView去實現:

class SaveLuckDialsettingView(APIView):

    def get(self, requset):
        sql = """
            select * from hd_wechat_luckdial order by id asc 
        """
        cursor = connection.cursor()
        cursor.execute(sql)
        result = [i for i in dictfetchall(cursor)] #先將所有獎品從數據庫中取出,放在數組裏,

        conn = get_redis_connection('default') # 鏈接redis
        tempInt = 0 # 設置一個記錄值,用於記錄上一條獎品的最大值和下一個獎品的最小值

        for i in range(len(result)): #循環所有獎品
            if tempInt == 0:   #如果是第一個獎品,就記錄值就爲0  否則就將記錄值+1設置爲當前獎品的最小值
                tempInt = 0
            else:
                tempInt = tempInt + 1

            min = tempInt  
            if i == 0:
                max = tempInt + result[i]['prize_probability'] * 100
            else:
                max = tempInt + result[i]['prize_probability'] * 100 - 1

            conn.hset(settings.LUCKYDIAL_PRIZE_SCOPE_MAX, result[i]['id'], max)
            conn.hset(settings.LUCKYDIAL_PRIZE_SCOPE_MIN, result[i]['id'], min)
            tempInt = max

        return Response({'message': '設置成功'}, status=status.HTTP_200_OK)

通過以上代碼,我們就能將每個商品的最大值和最小值設置數字段,我這裏因爲數據庫中概率爲10即代表10%,這裏我將概率×100用來設置數字段,比方說第一個獎品概率爲10%,那麼他對應的代碼段就是0-1000。總數字段長度爲10000,玩家抽獎的隨機數的範圍也就是0-10000。

這樣我們就成功設置了數字段,接下來進行抽獎就變得很容易,隨機0-10000的數字即可,判斷隨機出來的數字在哪個數字區間內,直接上代碼:

# 開始抽獎!
class ZhuanQiLaiView(APIView):
    permission_classes = (PlayerPermissions,)

    def get(self, request):
        try:
            with transaction.atomic():
                open_id = request.user.openid
                sql = """
                      select diamond_amount, money_amount from hd_wechatuser where openid='{openid}'
                  """.format(openid=open_id)
                cursor = connection.cursor()
                cursor.execute(sql)
                result = dictfetchall(cursor)
                diamond_amount = result[0]['diamond_amount']

                if diamond_amount < 200:
                    return Response({'message': '您的鑽石不夠抽獎'}, status=status.HTTP_400_BAD_REQUEST)

                player_lucky_num = random.randint(0, 10000)
                conn = get_redis_connection('default')
                queryset = WechatLuckyDial.objects.all()
                serizalize = WechatLuckyDialSerizlizer(queryset, many=True)
                luckydial_data = serizalize.data

                for i in luckydial_data:
                    max = conn.hget(settings.LUCKYDIAL_PRIZE_SCOPE_MAX, i['id'])
                    min = conn.hget(settings.LUCKYDIAL_PRIZE_SCOPE_MIN, i['id'])
                    if player_lucky_num > int(min) and player_lucky_num < int(max):
                        prize = i
                        break

                print(prize)

                if prize['prize_type'] == 1:
                # 紅包獎勵
                    if prize['prize_amount'] == 0:
                #隨機獎勵
                        award_money = random.randint(10, 100)
                        money_amonut = result[0]['money_amount']
                        now_money_amount = award_money + money_amonut
                        WechatUser.objects.filter(openid=open_id).update(money_amount=now_money_amount)
                        message = '恭喜你獲取紅包{task_award}元'.format(task_award=award_money / 100)
                elif prize['prize_type'] == 0:
                # 鑽石獎勵
                    task_award = prize['prize_amount']
                    now_diamond_amount = task_award + diamond_amount
                    message = '恭喜你獲得{task_award}個鑽石'.format(task_award=task_award)

                try:
                    now_diamond_amount
                except NameError:
                    end_diamond_amount = diamond_amount - 200
                else:
                    end_diamond_amount = now_diamond_amount - 200

                WechatUser.objects.filter(openid=open_id).update(diamond_amount=end_diamond_amount)
        except Exception as e:
            return Response({'message': '抽獎失敗'}, status=status.HTTP_400_BAD_REQUEST)

        data = {
            'message': message,
            'prize_id': prize['id']
        }

        return Response({'data': data}, status=status.HTTP_200_OK)

這一段代碼略多,裏面包含了檢測玩家鑽石夠不夠抽獎,以及抽獎之後獎品的發放等等,核心的抽獎代碼我抽取出來是這樣的:

                player_lucky_num = random.randint(0, 10000)
                conn = get_redis_connection('default')
                queryset = WechatLuckyDial.objects.all()
                serizalize = WechatLuckyDialSerizlizer(queryset, many=True)
                luckydial_data = serizalize.data  #將獎品信息從數據庫取出來

                for i in luckydial_data:
                    max = conn.hget(settings.LUCKYDIAL_PRIZE_SCOPE_MAX, i['id'])
                    min = conn.hget(settings.LUCKYDIAL_PRIZE_SCOPE_MIN, i['id'])
                    if player_lucky_num > int(min) and player_lucky_num < int(max):
                        prize = i #若在區間內,就中這個獎勵
                        break

                # prize包含了中的獎勵的所有信息
                 print(prize)

至此我們就實現了大轉盤抽獎的功能,道理並不複雜,也可以用很多方式實現,我這裏只是用我想到的一種方式去處理,若有什麼好的想法,可聯繫我,我的郵箱是[email protected]。歡迎大家來交流,互相學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章