項(xiàng)目場景
實(shí)現(xiàn)一個(gè)大轉(zhuǎn)盤抽獎(jiǎng)的功能抹沪,能后臺自定義獎(jiǎng)項(xiàng)纵菌,各獎(jiǎng)項(xiàng)中獎(jiǎng)概率洋闽,獎(jiǎng)品數(shù)量玄柠,當(dāng)日抽獎(jiǎng)最大次數(shù)等。
一诫舅、設(shè)計(jì)思路
這里簡單分享下思路:
1.獎(jiǎng)品中獎(jiǎng)概率
所有參與抽獎(jiǎng)的獎(jiǎng)項(xiàng)中獎(jiǎng)概率之和為 1
2.抽獎(jiǎng)規(guī)則
這里首先需要明確如何中獎(jiǎng)羽利?
一般來說是生成隨機(jī)數(shù),然后將隨機(jī)數(shù)與獎(jiǎng)品的中獎(jiǎng)概率相比較刊懈,如果小于中獎(jiǎng)概率則中獎(jiǎng)这弧。
但是,如果每個(gè)獎(jiǎng)項(xiàng)或者幾個(gè)獎(jiǎng)項(xiàng)的概率一樣虚汛,上面的方法就會出現(xiàn)每次抽獎(jiǎng)匾浪,中獎(jiǎng)都是同一個(gè)獎(jiǎng)品的情況
所以我們采用中獎(jiǎng)概率累加的方法,如圖所示:
抽獎(jiǎng)規(guī)則:
- 獲取該游戲的獎(jiǎng)品列表卷哩,按照中獎(jiǎng)概率升序排列 (可以不用排序)
- 獎(jiǎng)品列表中的概率為累加概率户矢,需要按照添加進(jìn)列表的順序進(jìn)行累加
- 生成一個(gè)0-1的隨機(jī)數(shù),與設(shè)置好的獎(jiǎng)品概率循環(huán)比較
- 若隨機(jī)數(shù)小于概率值殉疼,則抽中該獎(jiǎng)項(xiàng)
3.獎(jiǎng)品發(fā)放
在設(shè)置獎(jiǎng)品的時(shí)需要設(shè)置類型,例如:優(yōu)惠券
捌年、積分
瓢娜、商品(實(shí)物)
、虛擬商品
等等礼预。
每個(gè)類型的獎(jiǎng)品發(fā)放規(guī)則不同眠砾,這里可以利用Java多態(tài)的特性建立一個(gè)工廠類,用不同的實(shí)現(xiàn)類來分別實(shí)現(xiàn)不同類型的獎(jiǎng)品的中獎(jiǎng)處理邏輯
這里由于將使用的微服務(wù)托酸,所以直接通過feign調(diào)用對應(yīng)的服務(wù)方法
二褒颈、數(shù)據(jù)庫設(shè)計(jì)
1.轉(zhuǎn)盤游戲表
存儲轉(zhuǎn)盤抽獎(jiǎng)信息
字段 | 描述 |
---|---|
id(varchar) | 主鍵id |
name(varchar) | 活動名稱 |
start_time(datetime) | 開始時(shí)間 |
end_time(datetime) | 結(jié)束時(shí)間 |
description(varchar) | 描述 |
day_limit(int) | 單人當(dāng)天限制次數(shù) 0:代表不限制 |
single_limit(int) | 單人總次數(shù)限制 0:代表不限制 |
state(int) | 活動狀態(tài)柒巫,1 開啟 2 關(guān)閉 |
2.獎(jiǎng)品表
存儲抽獎(jiǎng)獎(jiǎng)品信息
字段 | 描述 |
---|---|
id(varchar) | 主鍵id |
game_id(varchar) | 游戲id |
prize_type(int) | 獎(jiǎng)品類型(1優(yōu)惠券,2商品谷丸,3積分堡掏,4謝謝參與) |
prize_name(varchar) | 獎(jiǎng)品名字 |
prize_id(varchar) | 獎(jiǎng)品id |
prize_value(int) | 獎(jiǎng)品值(數(shù)量) |
ratio(double) | 中獎(jiǎng)幾率 |
current_num(int) | 當(dāng)前命中 |
max_num(int) | 最大中獎(jiǎng)數(shù) 0:代表不限制 |
3.抽獎(jiǎng)記錄表
存儲抽獎(jiǎng)記錄信息
字段 | 描述 |
---|---|
id(varchar) | 主鍵id |
game_id(varchar) | 游戲id |
user_id(datetime) | 用戶id |
user_name(datetime) | 用戶名 |
draw_time(varchar) | 抽獎(jiǎng)時(shí)間 |
is_hit(int) | 是否中獎(jiǎng) 0:未中獎(jiǎng) 1:中獎(jiǎng) |
hit_prize(varchar) | 中獎(jiǎng)獎(jiǎng)品 |
is_send(int) | 是否發(fā)放 1未發(fā)放,2 已發(fā)放 3 發(fā)放失敗 |
send_msg(text) | 發(fā)放結(jié)果 |
三刨疼、業(yè)務(wù)邏輯
1.提供接口
(1)獲取轉(zhuǎn)盤游戲
(2)獲取用戶的抽獎(jiǎng)記剩余次數(shù)
(3)參與轉(zhuǎn)盤抽獎(jiǎng)
(4)抽獎(jiǎng)記錄
2.核心代碼
抽獎(jiǎng)邏輯代碼
// 獲取獎(jiǎng)品信息列表
List<Entity> prizeList = service.list();
// 獎(jiǎng)品列表中的概率為累加概率
// 需要按照添加進(jìn)列表的順序進(jìn)行累加泉唁,為了數(shù)據(jù)處理方便中獎(jiǎng)幾率*100
double sum = 0;
List<Entity> newList = new ArrayList<>();
for (int i = 0; i < prizeList.size(); i++) {
Entity entity = prizeList.get(i);
Entity newEntity = new Entity();
BeanUtils.copyProperties(entity, newEntity);
sum = sum + (entity.getRatio() * 100);
newEntity.setRatio(sum);
newList.add(newEntity);
}
// 生成一個(gè)隨機(jī)數(shù)
Random random = new Random();
Double userSelect = random.nextDouble() * 10000;
for (Entity prize : prizeList) {
// 隨機(jī)數(shù)小于中獎(jiǎng)幾率,則中獎(jiǎng)
if (userSelect < prize.getRatio()) {
// 最大中獎(jiǎng)數(shù)(0:代表不限制次數(shù))
int maxNum = prize.getMaxNum();
// 判斷游戲獎(jiǎng)品當(dāng)前中獎(jiǎng)數(shù)及最大中獎(jiǎng)數(shù)
if (maxNum != 0 && maxNum <= prize.getCurrentNum()) {
// 超過最大中獎(jiǎng)數(shù)則不中
break;
} else {
return prize;
}
}
}
// 謝謝參與
List<Entity> prize = prizeList.stream().filter(item -> item.getPrizeType() == 4).collect(Collectors.toList());
if (prize.size() > 0) {
return prize.get(0);
}
return null;
抽獎(jiǎng)發(fā)放:根據(jù)抽中的獎(jiǎng)品類型調(diào)用對應(yīng)的服務(wù)進(jìn)行獎(jiǎng)品發(fā)放揩慕;(比如抽中優(yōu)惠券亭畜,就調(diào)用優(yōu)惠券的服務(wù)進(jìn)行發(fā)放)
int type = prize.getPrizeType();
try {
switch (type) {
// 發(fā)放優(yōu)惠券
case 1:
ApiResult res = couponFeign.sendCouponToUser(rentId, userId, prize.getPrizeId(), prize.getPrizeValue());
// 其他業(yè)務(wù)處理
break;
// 發(fā)放商品
case 2:
break;
// 發(fā)放積分
case 3:
ApiResult res2 = pointFeign.addPointToUser(rentId, gameId, userId, prize.getPrizeValue());
// 其他業(yè)務(wù)處理
break;
// 謝謝參與
case 4:
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
注意事項(xiàng):抽獎(jiǎng)容易出現(xiàn)并發(fā)問題,所以在抽獎(jiǎng)的地方迎卤,需要加上分布式鎖拴鸵,這里使用Redis官方推薦的Java版的Redis客戶端
Redisson中的分布式鎖功能
RLock lock = redisson.getLock("turntable:" + gameId);
try {
// 指定超時(shí)時(shí)間
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 抽獎(jiǎng)業(yè)務(wù)處理
}
} finally {
lock.unlock();
}
創(chuàng)作不易,關(guān)注蜗搔、點(diǎn)贊就是對作者最大的鼓勵(lì)劲藐,歡迎在下方評論留言
定期分享Java知識,一起學(xué)習(xí)碍扔,共同成長瘩燥,期待您的關(guān)注!