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