java实现砸金蛋抽奖功能
作者:门卫向大爷 发布时间:2022-02-22 01:26:50
标签:java,抽奖
本文实例为大家分享了java实现砸金蛋抽奖的具体代码,供大家参考,具体内容如下
代码如下
需求:用户每一次砸金蛋,抽中一等奖的概率为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;
}
来源:https://blog.csdn.net/qq_22638399/article/details/81352819


猜你喜欢
- 方法1:import java.net.HttpURLConnection;import java.net.URL;import org.j
- 一. 下载环境Ubuntu 2.x.x 版本二. 创建Hadoop用户在虚拟机创建安装完成后。1.进入用户,打开终端输入如下命令:sudo
- 在Java编程过程中,我们常常会遇到比较基本类型或者对象之间的大小关系,下面我们来看看怎么去比较。源码如下:package object;c
- 1、AOP基本总结连接点(JoinPoint):连接点是程序运行的某个阶段点,如方法调用、异常抛出等切入点(Pointcut):切入点是Jo
- 工具类-java精确到小数点后6位验证要求,必须精确到小数点后6位,但是后面都是0的话,double会省略0,正则验证不通过,所以有了下面解
- 本文实例讲述了C#操作PowerPoint的方法。分享给大家供大家参考。具体如下:这里C#操作PowerPoint的基本代码,包括打开ppt
- 本文实例讲述了Java编程使用箱式布局管理器。分享给大家供大家参考,具体如下:先来看看运行效果:完整代码如下:package awtDemo
- 手把手教你用C#开发Android应用程序的方法和流程摘要:用C#能开发RFID-android吗?C#真的能开发android程序吗?C#
- 当app中打开了多个activity的时候,由于进入的比较深,所以,很多app不得不让用户一步一步退回到第一个界面(MainActivity
- C#2.0引入了泛型这个特性,由于泛型的引入,在一定程度上极大的增强了C#的生命力,可以完成C#1.0时需要编写复杂代码才可以完成的一些功能
- 使用POI读写Word doc文件 Apache poi的hwpf模
- 1. 文件上传a. 看看@FIEL注解的属性/** * 上传文件时使用该注解 设置文件相关参数 */@Retention(Retention
- Optional在JAVA中被定义为一个容器类,更确切的说只存一个元素的容器。container object which may or m
- 一、前言想要自定义starter组件,首先要了解springboot是如何加载starter的,也就是springboot的自动装配机制原理
- 新建SL4 应用程序,在MainPage下添加代码:<Button x:Name="btnThread1" Cli
- Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式
- C语言是一种高级编程语言,其最重要的特点之一是它允许程序员使用函数来组织代码。函数是一组相关的指令的集合,可以在程序中多次调用。在 C语言中
- 我有以下课程public class ModInfo : IEquatable<ModInfo>{ public int ID
- Java内存区域与内存溢出异常概述对于 C 和 C++程序开发的开发人员来说,在内存管理领域,程序员对内存拥有绝对的使用权,但是也要主要到正
- 概述动态SQL:SQL语句会随着用户输入或外部条件的变化而变化 。例如:我们在做多条件查询的时候,编写SQL语句的查询操作,我们并不知道用户