用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]。欢迎大家来交流,互相学习。

猜你喜欢

转载自blog.csdn.net/wry_developer/article/details/82315836