需求簡介
新項目有一個類似王者榮耀抽獎的功能:抽取花費積分,積累幸運值,每階段幸運值可以抽取到不同的獎品,幸運值集滿時,必得稀有道具
功能實現預期:建立一個抽獎池(抽獎池級別根據type區分),獎品在不同的抽獎池中,獲取用戶幸運值,創建一個List,達到要求就將該抽獎池中的獎品放入該抽獎集合中,進行抽獎,如果幸運值為滿,則只將特殊道具放入抽獎池中,進行抽獎
第一步:創建數據庫相關數據表
抽獎池表:此處原本要建立兩張表(抽獎池(如果是兩張表lucky_restrict 是可以直接限制獎池條件的,一張表時,該字段廢棄),和獎池道具),但是因為項目沒啥特殊要求,所以就先湊合用了
CREATE TABLE `t_draw_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `prop_id` int(11) DEFAULT NULL COMMENT '道具ID', `type` int(1) DEFAULT '1' COMMENT '獎池級別,0號獎池,1號獎池,2號獎池,3稀有獎池', `lucky_restrict` varchar(255) DEFAULT NULL COMMENT '(廢棄)限制條件:獎池抽獎限制幸運值限制', `probability` int(11) DEFAULT NULL COMMENT '中獎概率,總概率的多少分之一,如果所有道具的概率總和為100 當前獎品的概率是1,那么中獎概率就是百分之一', `top` int(11) DEFAULT '0' COMMENT '道具排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4
道具表:此表為獎品池中的道具
CREATE TABLE `t_prop` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL COMMENT '物品名稱', `description` varchar(512) DEFAULT NULL COMMENT '描述', `type` int(11) DEFAULT NULL COMMENT '0實體,1虛擬,2價值點', `status` int(11) DEFAULT NULL COMMENT '物品狀態,0正常,1刪除', `num` int(11) DEFAULT NULL COMMENT '物品數量', `price` double DEFAULT NULL COMMENT '價值點', `price_type` int(11) DEFAULT NULL COMMENT '2積分', `url` varchar(1024) DEFAULT NULL COMMENT '道具展示圖', `create_time` datetime DEFAULT NULL COMMENT '物品創建時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4
抽獎套餐表:這張表,原先是用來存放購買積分套餐的,但是因為字段相同,沒必要新增一張表,就加個type進行了區分
CREATE TABLE `t_starlight_set_meal` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ios_id` varchar(255) DEFAULT NULL COMMENT 'IOS內購id', `price` double DEFAULT NULL COMMENT '價格 金額 | 積分', `amount` int(11) DEFAULT NULL COMMENT '套餐內積分數量 | 套餐內抽獎次數', `name` varchar(255) DEFAULT NULL COMMENT '套餐名稱', `description` varchar(255) DEFAULT NULL COMMENT '套餐描述', `url` varchar(512) DEFAULT NULL COMMENT '積分展示圖片', `top` int(11) DEFAULT NULL COMMENT '套餐排序', `type` int(1) DEFAULT '0' COMMENT '2抽獎套餐', `create_time` datetime DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='星雲幣充值套餐'
用戶錢包表:存放積分(即抽獎積分)、幸運值等信息,當前項目中,積分不計入賬單,所以就沒有相對應的賬單列表,有需要的可以在每次修改錢包時去記錄賬單,此處不做贅述
CREATE TABLE `t_wallet` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '用戶id', `starlight` int(11) NOT NULL DEFAULT '0' COMMENT '積分', `lucky` int(11) NOT NULL DEFAULT '0' COMMENT '幸運值', PRIMARY KEY (`id`), UNIQUE KEY `idx_userId` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='錢包:積分、幸運值'
用戶背包表:背包也是應該建立兩張表(背包表:應包含禮物數量限制 和 背包容量,背包物品表:關聯背包和物品信息),同是因為沒有必須要,就使用一張表代替了
CREATE TABLE `t_knapsack` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '用戶id', `gift_id` int(11) NOT NULL COMMENT '道具id | 禮物id', `numble` int(11) DEFAULT '1' COMMENT '禮物數量,無限制', `capacity` int(11) DEFAULT '100' COMMENT '(廢棄)背包容量,無限制', `occupying_dosage` int(11) DEFAULT '0' COMMENT '(廢棄)背包占用量,無限制', `type` int(1) DEFAULT '0' COMMENT '0道具背包,1禮物背包', PRIMARY KEY (`id`), UNIQUE KEY `IDX_userId` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用戶背包'
以上相關數據表建立完成,生成相對應的對象即可(DrawPool、Prop、Wallet、Knapsack、StarlightSetMeal)
第二步:代碼邏輯
public class DrawPoolService {
@Resource
private DrawPoolMapper drawPoolMapper;
@Resource
private PropMapper propMapper;
@Resource
private WalletMapper walletMapper;
@Resource
private KnapsackMapper knapsackMapper;
@Resource
private StarlightSetMealMapper starlightSetMealMapper;
/**
* 獎池列表
* @return
*/
public Map<String, Object> list(Integer currentUserId) {
Map<String, Object> result = new HashMap<>();
List<DrawPool> drawPoolList = drawPoolMapper.selectByType(null);
for (DrawPool drawPool : drawPoolList){
drawPool.setPropInfo(propMapper.selectByPrimaryKey(drawPool.getPropId()));
}
// 封裝獎品信息
result.put("prop", drawPoolList);
// 封裝幸運值上限,暫定500
result.put("upperLucky", 500);
// 封裝用戶星雲幣、積分、幸運值信息
if (currentUserId != null){
result.put("walletInfo", walletMapper.selectByUserId(currentUserId));
}else{
result.put("walletInfo", null);
}
List<StarlightSetMeal> starlightSetMeals = starlightSetMealMapper.selectByType(2);
result.put("starlightSetMeals", starlightSetMeals);
return result;
}
/**
* 抽獎
* @param currentUserId 抽獎用戶
* @param starlightSetMealId 抽獎套餐ID
* @return
*/
public ResponseVO luckDraw(int currentUserId, int starlightSetMealId){
Map<String, Object> result = new HashMap<>();
List<Prop> propList = new ArrayList<>();
Wallet wallet = walletMapper.selectByUserId(currentUserId);
StarlightSetMeal starlightSetMeal = starlightSetMealMapper.selectByPrimaryKey(starlightSetMealId);
if (wallet == null || starlightSetMeal == null){
return ResponseVO.error("不滿足抽獎條件");
}
// 此次抽獎應該消耗的積分
int usrStarlight = starlightSetMeal.getPrice().intValue();
int num = starlightSetMeal.getAmount();
if (wallet.getStarlight() < usrStarlight){
return ResponseVO.error("積分不足");
}
// 更新積分數量, 積分不用記錄賬單
wallet.setStarlight(wallet.getStarlight() - usrStarlight);
walletMapper.updateByPrimaryKeySelective(wallet);
// 一號獎池上限
int oneUpperLucky = 200;
// 二號獎池上限
int towUpperLucky = 300;
boolean isResetLucky = false;
for (int i = 0; i < num; i++){
// 更新幸運值
wallet = walletMapper.selectByUserId(currentUserId);
// 返回抽獎池列表結果
List<DrawPool> drawPoolList = new ArrayList<>();
// 根據幸運值獲取不同的抽獎池
if (wallet.getLucky() >= 0 && wallet.getLucky() < towUpperLucky){
drawPoolList.addAll(drawPoolMapper.selectByType(1));
}
if (wallet.getLucky() >= towUpperLucky && wallet.getLucky() < oneUpperLucky){
drawPoolList.addAll(drawPoolMapper.selectByType(2));
}
if (wallet.getLucky() >= oneUpperLucky){
// 如果當前用戶的幸運值大於550,必得特殊道具
drawPoolList.addAll(drawPoolMapper.selectByType(3));
isResetLucky = true;
}
int prizeId = getPrizeIndex(drawPoolList);
// 如果獎品是價值點之類的,直接增加
Prop prop = propMapper.selectByPrimaryKey(prizeId);
if (prop.getType() == 2){
wallet.setStarlight(wallet.getStarlight() + prop.getNum());
walletMapper.updateByPrimaryKeySelective(wallet);
}else{
// 如果是道具,就存放到用戶背包
Knapsack knapsack = knapsackMapper.getKnapsack(currentUserId, Constants.Knapsack.Type.PROP, prizeId);
if (ValidateUtils.isNull(knapsack)){
knapsack = new Knapsack();
knapsack.setUserId(currentUserId);
knapsack.setGiftId(prizeId);
knapsack.setType(Constants.Knapsack.Type.PROP);
knapsack.setNumble(prop.getNum());
knapsackMapper.insertSelective(knapsack);
}else{
knapsack.setNumble(knapsack.getNumble() + prop.getNum());
knapsackMapper.updateByPrimaryKeySelective(knapsack);
}
}
// 沒進行一輪如果抽到了特殊獎池,那么都要清空幸運值,不然如果是多次抽獎,那么后面的每次都會是特殊道具
if (isResetLucky){
// 清空幸運值
wallet.setLucky(0);
walletMapper.updateByPrimaryKeySelective(wallet);
}else{
// 增加 幸運值 , 每抽獎一次, 幸運值+1
wallet.setLucky(wallet.getLucky() + 1);
walletMapper.updateByPrimaryKeySelective(wallet);
}
propList.add(prop);
}
// 清空幸運值
if (isResetLucky){
wallet.setLucky(0);
walletMapper.updateByPrimaryKeySelective(wallet);
}
result.put("propList", propList);
// 封裝用戶積分、幸運值信息
result.put("walletInfo", walletMapper.selectByUserId(currentUserId));
return ResponseVO.succeess(result);
}
/**
* 中獎概率,總概率的多少分之一,如果所有道具的概率總和為100 當前獎品的概率是1,那么中獎概率就是百分之一
* 根據Math.random()產生一個double型的隨機數,判斷每個獎品出現的概率
* @param drawPools
* @return random:獎品列表drawPools中的序列(drawPools中的第random個就是抽中的獎品),返回中獎的道具ID
*/
public static int getPrizeIndex(List<DrawPool> drawPools) {
// DecimalFormat df = new DecimalFormat("######0.00");
int prizeId = 0;
try{
//計算總權重
double sumWeight = 0;
for(DrawPool drawPool : drawPools){
sumWeight += drawPool.getProbability();
}
//產生隨機數
double randomNumber;
randomNumber = Math.random();
//根據隨機數在所有獎品分布的區域並確定所抽獎品
double d1 = 0;
double d2 = 0;
for(int i=0;i<drawPools.size();i++){
// 依次獲取獎品所在的范圍
// 獲取當前獎品所在的中獎概率范圍 最大值
d2 += Double.parseDouble(String.valueOf(drawPools.get(i).getProbability()))/sumWeight;
if(i==0){
d1 = 0;
}else{
// 獲取上一個獎品所在的中獎概率范圍 最大值
d1 +=Double.parseDouble(String.valueOf(drawPools.get(i-1).getProbability()))/sumWeight;
}
// 如果中獎隨機數 大於上一個獎品的最大值 並且小於當前獎品的最大值,那么表示中獎隨機數在當前獎品的中獎范圍內,當前獎品中獎
if(randomNumber >= d1 && randomNumber <= d2){
prizeId = drawPools.get(i).getPropId();
break;
}
}
}catch(Exception e){
System.out.println("生成抽獎隨機數出錯,出錯原因:" +e.getMessage());
}
return prizeId;
}
}
以上代碼,list函數返回抽獎頁面的相關信息,luckDraw函數進行抽獎,返回中獎信息,以及積分修改后的信息,getPrizeIndex函數則為具體的抽獎邏輯(該部分邏輯代碼源於博文:https://blog.csdn.net/huyuyang6688/article/details/50480687);