Java實現抽獎功能


需求簡介

新項目有一個類似王者榮耀抽獎的功能:抽取花費積分,積累幸運值,每階段幸運值可以抽取到不同的獎品,幸運值集滿時,必得稀有道具

功能實現預期:建立一個抽獎池(抽獎池級別根據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);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM