Java实现抽奖功能

需求简介

新项目有一个类似王者荣耀抽奖的功能:抽取花费积分,积累幸运值,每阶段幸运值可以抽取到不同的奖品,幸运值集满时,必得稀有道具

功能实现预期:建立一个抽奖池(抽奖池级别根据type区分),奖品在不同的抽奖池中,获取用户幸运值,创建一个List,达到要求就将该抽奖池中的奖品放入该抽奖集合中,进行抽奖,如果幸运值为满,则只将特殊道具放入抽奖池中,进行抽奖

第一步:创建数据库相关数据表

  抽奖池表:此处原本要建立两张表(抽奖池(如果是两张表lucky_restrict 是可以直接限制奖池条件的,一张表时,该字段废弃),和奖池道具),但是因为项目没啥特殊要求,所以就先凑合用了

1

2

3

4

5

6

7

8

9

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=15DEFAULT CHARSET=utf8mb4

 道具表:此表为奖品池中的道具

1

2

3

4

5

6

7

8

9

10

11

12

13

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` datetimeDEFAULT NULL COMMENT'物品创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=15DEFAULT CHARSET=utf8mb4

 抽奖套餐表:这张表,原先是用来存放购买积分套餐的,但是因为字段相同,没必要新增一张表,就加个type进行了区分

1

2

3

4

5

6

7

8

9

10

11

12

13

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` datetimeDEFAULT NULL COMMENT'创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=3DEFAULT CHARSET=utf8mb4 COMMENT='星云币充值套餐'

 用户钱包表:存放积分(即抽奖积分)、幸运值等信息,当前项目中,积分不计入账单,所以就没有相对应的账单列表,有需要的可以在每次修改钱包时去记录账单,此处不做赘述

1

2

3

4

5

6

7

8

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=6DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='钱包:积分、幸运值'

 用户背包表:背包也是应该建立两张表(背包表:应包含礼物数量限制 和 背包容量,背包物品表:关联背包和物品信息),同是因为没有必须要,就使用一张表代替了

1

2

3

4

5

6

7

8

9

10

11

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=InnoDBDEFAULT 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);

不要让未来的你,来埋怨如今的自己。

好文要顶 关注我 收藏该文  

浅灰色的记忆
关注 - 2
粉丝 - 4

+加关注

0

0

« 上一篇: 从零开始使用spring boot搭建前后端分离框架(二、集成shiro)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值