zoukankan      html  css  js  c++  java
  • 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);

  • 相关阅读:
    如何限制Dedecms文章或产品描述的字数
    Python 进阶 之 yield
    Python 进阶 之 contextlib模块
    JavaScript 之 定时器 延迟器
    Python 进阶 之 函数对象
    CSS入门之定义和应用格式
    Python 进阶 之 socket模块
    Python 进阶 之 闭包变量
    Python 进阶 之 else块 巧(慎)用
    Python 进阶 之 zip() izip() zip_longest函数
  • 原文地址:https://www.cnblogs.com/fuhui-study-footprint/p/12573165.html
Copyright © 2011-2022 走看看