代码如下

需求:用户每一次砸金蛋,抽中一等奖的概率为2% 二等奖10% 三等奖18% 四等奖70%。
累计砸第n次时必抽中x等奖以上的奖品。比如,累计砸第5次,则此次必中二等奖及以上的奖品。且配置的此次必中中奖概率不一样。有点绕。。。

/**
  * 金蛋抽奖
  * userId : 抽奖用户ID
  * consumeType : 抽奖消耗的物品  1:金币 2:次数
  */
 @Override
 public Map<String, Object> eggsLottery(Integer userId, Integer consumeType) {
     /*******first : check user ************/
     checkUserIsLock(userId);
     logger.info("userId {} start lottery  -eggs.", userId);
     Jedis jedis = RedisPool.getJedis();
     try {
         //查询活动开关
         String hget = jedis.hget(Rkey.SMASH_GOLD_EGGS_GLOBAL_CONFIG, "status");
         if (null == hget || "0".equals(hget)) {
             throw new BusiException(E.INVALID_PARAMETER, "活动暂未开启,敬请期待");
         }
         //check lottery type
         Long consumeScore = 0L;

         /**score lottery**/
         consumeScore = jedis.hincrBy(Rkey.SMASH_GOLD_EGGS_GLOBAL_CONFIG, "consumeGoldScore", 0);
         if (consumeScore < 1) {
             throw new BusiException(E.EGGS_ACTIVITY_CONFIG_EXCEPTION, "活动配置有误!");
         }
         long surScore = goldWalletMapper.selectAmountGoldByUserId(userId);
         surScore = surScore - consumeScore;
         if (surScore < 0) {
             throw new BusiException(E.SCORE_NOT_ENOUGH, "您的金币不足");
         }
         // 砸金蛋之前扣除金币
         Date now = new Date();
         reduceGold(consumeScore, now, userId);

         /*******second : lottery ************/
         Map<String, Object> map;
         try {
             map = lottery(jedis, now, userId, consumeScore);
             // 抽奖结束后 记录今日总共完成的抽奖次数 +1
             // 必中 不加
             jedis.hincrBy(Rkey.SMASH_GOLD_EGGS_USER_WIN_TOTAL_COUNT, userId.toString(), 1);
         } catch (Exception e) {
             throw e;
         }
         return map;
     } finally {
         RedisPool.returnJedis(jedis);
     }
 }

 /**
  * 抽奖 begin----
  */
 private Map<String, Object> lottery(Jedis jedis, Date now, Integer userId, Long consumeScore) {
     Map<String, Object> map = new HashMap<String, Object>();
     // 判断本次是否是必中?  jackpotType=1: 不是 2:是必中
     Integer jackpotType = 1;
     // 剩余次数
     Integer freeCount = 0;
     String countStr = jedis.hget(Rkey.SMASH_GOLD_EGGS_USER_WIN_COUNT, userId.toString());
     if (StringUtils.isNotEmpty(countStr)) {
         Integer count = Integer.valueOf(countStr);
         String whenAwardCount = jedis.hget(Rkey.SMASH_GOLD_EGGS_GLOBAL_CONFIG, "whenAwardCount");

         if (StringUtils.isEmpty(whenAwardCount)) {
             throw new BusiException(E.INVALID_REQUEST, "请先完善砸金蛋全局配置");
         }
         freeCount = Integer.valueOf(whenAwardCount) - count - 1;

         if (count >= Integer.valueOf(whenAwardCount) - 1) {
             logger.info("此次是必中....");
             // 此次是必中
             jackpotType = 2;
             // 抽奖结束后 先把记录总共完成的抽奖次数 置为0次
             jedis.hdel(Rkey.SMASH_GOLD_EGGS_USER_WIN_COUNT, userId.toString());
             jedis.hincrBy(Rkey.SMASH_GOLD_EGGS_USER_WIN_COUNT, userId.toString(), 0);
         } else {
             logger.info("此次不是必中....");
         }

         if (freeCount == 0) {
             freeCount = Integer.valueOf(whenAwardCount);
         }
     }

     // 根据配置得到总的奖品数量
     Integer totalCount = 0;
     if (jackpotType == 1) {
         totalCount = getAwardTotalCount(1);
     } else {
         totalCount = getAwardTotalCount(2);
     }
     Integer award = getRandomNumber(totalCount);
     // 看落在哪个区间
     Integer awardId = getWinAwardId(award, jackpotType);
     BsGoldEggsConfig goldEggsConfig = goldEggsConfigMapper.selectById(awardId);
     if (goldEggsConfig == null) {
         throw new BusiException(E.INVALID_REQUEST, "奖品信息未找到");
     }

     map.put("freeCount", freeCount);
     map.put("userId", userId);
     map.put("awardId", awardId);
     map.put("awardName", goldEggsConfig.getDescribe());
     map.put("goldCount", goldEggsConfig.getGoldCount());
     map.put("awardImg", goldEggsConfig.getAwardImg());
     logger.info("userId {} win award {}, 奖励金币:{}, 随机生成抽奖数:{}", userId, goldEggsConfig.getDescribe(), goldEggsConfig.getGoldCount(), award);
     // 抽奖结束 action:
     if (jackpotType == 1) {
         // 抽奖结束后 记录累计抽奖次数 +1
         // 必中 不加
         jedis.hincrBy(Rkey.SMASH_GOLD_EGGS_USER_WIN_COUNT, userId.toString(), 1);
     }
     // 奖励金币结算
     Date now1 = new Date();
     lotteryAddGold(goldEggsConfig.getGoldCount().longValue(), now1, userId);
     return map;
 }

 /**
  * 得到中奖id
  *
  * @param award : 随机生成的抽奖数字
  * @return
  */
 private Integer getWinAwardId(Integer award, Integer jackpotType) {
     List<BsGoldEggsConfig> goldEggsConfigList = goldEggsConfigMapper.queryAllList();
     if (goldEggsConfigList == null || goldEggsConfigList.size() < 4) {
         throw new BusiException(E.INVALID_REQUEST, "请先完成砸金蛋奖品配置!");
     }

     Integer[] weight = new Integer[4];
     if (jackpotType == 1) {
         // 基础抽奖
         for (int i = 0; i < goldEggsConfigList.size(); i++) {
             weight[i] = goldEggsConfigList.get(i).getBaseWeight();
         }
     } else {
         // 必中抽奖
         for (int i = 0; i < goldEggsConfigList.size(); i++) {
             weight[i] = goldEggsConfigList.get(i).getWinWeight();
         }
     }
     // 判断随机数落在了哪个区间  左开右闭      ---------- 这里如果用redis的set来做区间?如何实现?
     Integer awardId = 1;
     if (0 < award && award <= weight[0]) {
         // 一等奖
         awardId = 1;
     } else if (weight[0] < award && award <= (weight[0] + weight[1])) {
         // 二等奖
         awardId = 2;

     } else if ((weight[0] + weight[1]) < award && award <= (weight[1] + weight[2])) {
         // 三等奖
         awardId = 3;
     } else {
         // 四等奖
         awardId = 4;
     }
     return awardId;
 }
 /**
  * 获取1-max范围内 一个随机数
  *
  * @param max
  * @return
  */
  private Integer getRandomNumber(Integer max) {
      int i = (int) (Math.random() * max + 1);

      return i;
  }

下面是用奖池写的一个算法 读者可忽略,博主只是记录一下。

思路:
根据需求,
1.生成两类奖池(普通奖池,和必中奖池),中奖概率不一样!为了保证概率正确,我们生成100组(1-100)的数字,随机打乱放入redis中,作为一个奖池。

2.生成中奖区间,放入redis

3.每次用户砸金蛋,从奖池里面取一个数,

4.判断该数在哪个中奖区间

/**
  * 抽奖 begin----  该方法废除
  */
 private Map<String, Object> lottery2(Jedis jedis, Date now, Integer userId, Long consumeScore) {
     // 从奖池中拿出第一个奖品
     String jackpotKey = Rkey.SMASH_GOLD_EGGS_JACKPOT_ + "one";
     String baseWinLimitKey = Rkey.SMASH_GOLD_EGGS_AWARD_WIN_LIMIT_BASE;
     String nextWinLimitKey = Rkey.SMASH_GOLD_EGGS_AWARD_WIN_LIMIT_NEXT;
     Map<String, Object> map = new HashMap<String, Object>();

     // 奖池存在 且奖池不为空
     String awardFlag = jedis.lpop(jackpotKey);   // 如:20
     Integer award = Integer.valueOf(awardFlag);

     //

     if (!StringUtils.isEmpty(awardFlag)) {
         // 判断该奖品的等级
         // 这里是有问题的  博主后面纠正
         Set<String> winLimitSet = jedis.zrange(baseWinLimitKey, 0, award - 1);
         if (winLimitSet != null && !winLimitSet.isEmpty()) {
             Integer size = winLimitSet.size();
             List list = new ArrayList(winLimitSet);
             // 取区间最后一个
             String lastAwardLevel = String.valueOf(list.get(size - 1));

             // 获取奖品info
             Integer id = null;
             if ("one".equals(lastAwardLevel)) {
                 id = 1;
             } else if ("two".equals(lastAwardLevel)) {
                 id = 2;
             } else if ("three".equals(lastAwardLevel)) {
                 id = 3;
             } else if ("four".equals(lastAwardLevel)) {
                 id = 4;
             }
             BsGoldEggsConfig goldEggsConfig = goldEggsConfigMapper.selectById(id);
             if (goldEggsConfig == null) {
                 throw new BusiException(E.INVALID_REQUEST, "奖品信息未找到");
             }
             map.put("userId", userId);
             map.put("awardId", id);
             map.put("awardName", goldEggsConfig.getDescribe());
             map.put("goldCount", goldEggsConfig.getGoldCount());
             logger.info("userId {} win award {}, 奖励金币:{}, 随机生成抽奖数:{}", userId, goldEggsConfig.getDescribe(), goldEggsConfig.getGoldCount(), award);
         }
     }
     return map;
 }
/**
  * 生成奖池
  *
  * @param jackpotType : 奖池类型  1:普通奖池 2:必中奖池
  * @param jackpotSort :奖池序号 1,2,3...... 如普通奖池1,普通奖池2,
  */
 @Override
 public void addAwardToJackpot(Integer jackpotType, Integer jackpotSort) {

     // 存放奖池数据
     List<String> awadList = new ArrayList<>();
     // 奖池key
     String jackpotKey = "";
     String jackpotTypeToStr = "";
     if (jackpotType == 1) {
         jackpotTypeToStr = "普通";
         jackpotKey = Rkey.SMASH_GOLD_EGGS_JACKPOT_ + jackpotSort;
     } else {
         jackpotTypeToStr = "必中";
         jackpotKey = Rkey.SMASH_GOLD_EGGS_NEXT_WIN_JACKPOT_ + jackpotSort;
     }

     logger.info("开始生成{}奖池{}。。。。。", jackpotTypeToStr, jackpotSort);
     Jedis jedis = RedisPool.getJedis();
     try {

         if (jedis.exists(jackpotKey)) {
             // 判断奖池中是否还有奖品
             Long length = jedis.llen(jackpotKey);
             if (length <= 0) {
                 // 奖池空了,重新放入奖品
                 logger.info("{}奖池{}空了,重新放入奖品。。。。。", jackpotTypeToStr, jackpotSort);
                 // 根据配置得到总的奖品数量
                 Integer totalCount = getAwardTotalCount(1);

                 setSingleAwardToJackpot(awadList, jedis, jackpotKey, totalCount);
             }

         } else {
             // 直接生成奖池
             logger.info("{}奖池{}不存在,直接放入奖品。。。。。", jackpotTypeToStr, jackpotSort);
             Integer totalCount = getAwardTotalCount(1);
             setSingleAwardToJackpot(awadList, jedis, jackpotKey, totalCount);
         }
     } finally {
         RedisPool.returnJedis(jedis);
     }
 }


 /**
  * 获取奖池奖品数量
  *
  * @param type 奖池类型:1:普通奖池  2:必中奖池
  * @return
  */
 Integer getAwardTotalCount(Integer type) {
     List<BsGoldEggsConfig> goldEggsConfigList = goldEggsConfigMapper.queryAllList();
     if (goldEggsConfigList == null || goldEggsConfigList.size() < 4) {
         throw new BusiException(E.INVALID_REQUEST, "请先完成砸金蛋奖品配置!");
     }
     Integer totalCount = 0;
     if (type == 1) {
         // 普通奖池奖品数量
         for (BsGoldEggsConfig goldEggsConfig : goldEggsConfigList) {
             totalCount += goldEggsConfig.getBaseWeight();
         }


     } else {
         // 必中奖池数量
         Jedis jedis = RedisPool.getJedis();
         try {
             String mustAwardLevel = jedis.hget(Rkey.SMASH_GOLD_EGGS_GLOBAL_CONFIG, "mustAwardLevel");
             if (StringUtils.isEmpty(mustAwardLevel)) {
                 throw new BusiException(E.INVALID_REQUEST, "请先完成砸金蛋奖品配置!");
             }
             for (int i = 0; i < Integer.valueOf(mustAwardLevel); i++) {
                 totalCount += goldEggsConfigList.get(i).getWinWeight();
             }
         } finally {
             RedisPool.returnJedis(jedis);
         }


     }

     return totalCount;
 }

 /**
  * @param awadList
  * @param jedis
  * @param jackpotKey
  * @param totalCount 总的奖品个数 比如一等奖10个,二等奖20个,三等奖30,四等奖40  则totalCount = 10+20+30+40=100
  */
 private void setSingleAwardToJackpot(List<String> awadList, Jedis jedis, String jackpotKey, Integer totalCount) {
     // 1.生成 100组 [1-100] 随机数 awadList
     for (int i = 0; i < 2; i++) {
         List<Integer> list = getOneToHundredNumber(totalCount);
         for (Integer j : list) {
             awadList.add(j.toString());
         }
     }

     // 2.awadList打乱放入redis(list)  这里打乱2次
     Collections.shuffle(awadList);
     Collections.shuffle(awadList);

     // 3.放入redis
     awadList.forEach(s -> jedis.lpush(jackpotKey, s));
     logger.info("奖品info:预设值:{} 实际设置:{}", 10000, awadList.size());
 }

 /**
  * jdk8 得到包含1-end数字的list
  * end : 生成数字的个数
  *
  * @return
  */
 private List<Integer> getOneToHundredNumber(Integer end) {
     // 起始数字
     int start = 1;
     // 生成数字的个数
     // int end = 100;
     // 生成1,2,3,4,5...100
     List<Integer> list = Stream.iterate(start, item -> item + 1).limit(end).collect(Collectors.toList());
     return list;
 }