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]。歡迎大家來交流,互相學習。