1、需求
一个抽奖活动中,每个用户可以多次抽奖机会
2、相应类
1、奖品信息类Prize
/**
* 奖品信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Prize {
// 奖品id
private Long id;
// 奖品名称
private String name;
// 奖品数量
private Integer number;
// 中奖概率
private double winRate;
}
2、用户类
/**
* 用户
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
// 用户id
private Long id;
// 用户名称
private String name;
}
3、抽奖活动类
/**
* 抽奖活动
*/
@Data
public class Lottery {
// 活动id
private Long id;
// 抽奖次数
private Integer count;
// 奖品id集合
private List<Long> prizeIdList;
}
4、抽奖结果类
/**
* 抽奖结果
*/
@Data
public class LotteryResult {
// 抽奖人id
private Long userId;
// 是否中奖
private Boolean isWin;
// 中奖的奖品
private Prize prize;
}
3、初始化相应信息
1、controller层
// 初始化抽奖活动信息、抽奖人信息、奖品信息
@PostMapping("init")
public Lottery init() {
return lotteryService.init();
}
2、service接口层
// 初始化抽奖活动信息、抽奖人信息、奖品信息
Lottery init();
3、service接口实现类
@Autowired
private RedisService redisService;
private final static String INFO_USER_KEY = "info:user:";
private final static String INFO_PRIZE_KEY = "info:prize:";
private final static String INFO_LOTTERY_KEY = "info:lottery:";
private final static String USER_COUNT_KEY = "userCount:";
// 初始化抽奖活动信息、抽奖人信息、奖品信息
@Override
public Lottery init() {
// 初始化抽奖人员
User zhangsan = new User(100L, "张三");
User lisi = new User(200L, "李四");
// 人员信息存入缓存
String zhangsanInfoKey = INFO_USER_KEY + zhangsan.getId();
String zhangsanJson = JSON.toJSONString(zhangsan);
redisService.addInitToCache(zhangsanInfoKey, zhangsanJson);
String lisiInfoKey = INFO_USER_KEY + lisi.getId();
String lisiJson = JSON.toJSONString(lisi);
redisService.addInitToCache(lisiInfoKey, lisiJson);
// 初始化奖品信息
Prize iPhone15 = new Prize(1000L, "iPhone15", 10, 0.5d);
Prize rolex = new Prize(2000L, "劳力士-手表", 10, 0.1d);
// 转为map
Map<String, Object> iPhone15Map = entityToMap(iPhone15);
Map<String, Object> rolexMap = entityToMap(rolex);
// 奖品信息以map存入缓存
String iPhone15InfoKey = INFO_PRIZE_KEY + iPhone15.getId();
String rolexInfoKey = INFO_PRIZE_KEY + rolex.getId();
redisService.addPrizeMapToCache(iPhone15InfoKey, iPhone15Map);
redisService.addPrizeMapToCache(rolexInfoKey, rolexMap);
// 初始化抽奖活动信息
Lottery lottery = new Lottery();
lottery.setId(10086L);
lottery.setCount(10); // 每人3次抽奖机会
lottery.setPrizeIdList(Arrays.asList(1000L, 2000L));
// 抽奖活动信息存入缓存
String lotteryInfoKey = INFO_LOTTERY_KEY + lottery.getId();
String lotteryJson = JSON.toJSONString(lottery);
redisService.addInitToCache(lotteryInfoKey, lotteryJson);
return lottery;
}
// 实体类转为map
public Map<String, Object> entityToMap(Object obj) {
if (obj == null) {
return null;
}
Map<String, Object> map = new HashMap<>();
// 获取类的所有属性
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
try {
// 私有属性可操作
field.setAccessible(true);
// 属性的值
Object o = field.get(obj);
// 属性名
String name = field.getName();
// 存入map
map.put(name, o);
} catch (Exception e) {
e.printStackTrace();
}
}
return map;
}
4、开始抽奖
1、controller层
// 开始抽奖
@GetMapping("start/{userId}/{lotteryId}")
public LotteryResult start(@PathVariable("userId") Long userId, @PathVariable("lotteryId") Long lotteryId) {
return lotteryService.start(userId, lotteryId);
}
2、service接口层
// 开始抽奖
LotteryResult start(Long userId, Long lotteryId);
3、service接口实现类
// 开始抽奖
@Override
public LotteryResult start(Long userId, Long lotteryId) {
// 获取用户信息
String userInfoKey = INFO_USER_KEY + userId;
Object userObj = redisService.getInitInfoFromCache(userInfoKey);
if (userObj == null) {
throw new RuntimeException("用户不存在");
}
// 获取抽奖活动信息
String lotteryInfoKey = INFO_LOTTERY_KEY + lotteryId;
Object lotteryObj = redisService.getInitInfoFromCache(lotteryInfoKey);
if (lotteryObj == null) {
throw new RuntimeException("抽奖活动不存在");
}
// 转化信息
User user = JSON.parseObject(userObj.toString(), User.class);
Lottery lottery = JSON.parseObject(lotteryObj.toString(), Lottery.class);
// 抽奖活动每人的抽奖次数
Integer count = lottery.getCount();
// 获取当前用户剩余的抽奖次数
String userCountKey = USER_COUNT_KEY + userId;
Integer userCount = redisService.getUserCountFromCache(userCountKey);
if (userCount == null) {
userCount = count;
}
if (userCount == 0) {
throw new RuntimeException("已没抽奖次数");
}
// 活动奖品
List<Long> prizeIdList = lottery.getPrizeIdList();
// 抽奖
Long prizeId = draw(prizeIdList);
// 用户抽奖次数-1
redisService.addUserCountToCache(userCountKey, userCount -1);
// 创建返回对象
LotteryResult lotteryResult = new LotteryResult();
lotteryResult.setUserId(userId);
if (prizeId == null) { // 没中奖
lotteryResult.setIsWin(false);
System.out.println("您没中奖");
return lotteryResult;
}else { // 中奖了
// 封装返回对象
Prize prize = redisService.getPrizeInfoToCache(INFO_PRIZE_KEY + prizeId);
lotteryResult.setIsWin(true);
lotteryResult.setPrize(prize);
// 更新奖品数量
redisService.editPrizeNumberToCache(INFO_PRIZE_KEY + prizeId, "number", prize.getNumber() - 1);
System.out.println("您中奖了,奖品是:" + prize.getName());
}
return lotteryResult;
}
/**
* 抽奖计算
* 1、抽奖总概率不大于1
*
*
* @param prizeIdList 奖品id集合
* @return 中奖id
*/
private Long draw(List<Long> prizeIdList) {
// 用来累计概率
double index = 0.0d;
// 所有奖品概率池
List<Double> probabilityPool = new ArrayList<>();
// 奖品list
List<Prize> prizeList = new ArrayList<>();
// 遍历奖品id集合
for (int i = 0; i < prizeIdList.size(); i++) {
// 奖品id
Long prizeId = prizeIdList.get(i);
// 奖品信息
Prize prize = redisService.getPrizeInfoToCache(INFO_PRIZE_KEY + prizeId);
if (prize == null) {
continue;
}
// 如果奖品数量为0,直接跳过,即不会参与抽奖
if (prize.getNumber() <= 0) {
continue;
}
prizeList.add(prize);
// 将概率加入概率池
probabilityPool.add(index + prize.getWinRate());
// 累加
index += prize.getWinRate();
}
// 随机数,看落到哪个范围,即中奖哪个
Random random = new Random();
double d = random.nextDouble();
// 计算是否中奖
for (int i = 0; i < prizeList.size(); i++) {
if (i == 0) {
// 当前中奖范围为0 - 此奖品的概率值
if (probabilityPool.get(i) > d) {
// 中奖
return prizeList.get(i).getId();
}
}else {
// 当前中奖范围为前一个奖品概率 - 当前奖品概率
if (probabilityPool.get(i - 1) < d && d <= probabilityPool.get(i)) {
// 中奖
return prizeList.get(i).getId();
}
}
}
return null;
}
5、redis
1、redis接口层
/**
* 缓存接口
*/
public interface RedisService {
// 将初始化信息存入缓存
void addInitToCache(String key, String value);
// 奖品信息以map存入缓存
void addPrizeMapToCache(String key, Map<String, Object> map);
// 获取初始化信息
Object getInitInfoFromCache(String key);
// 获取当前用户剩余的抽奖次数
Integer getUserCountFromCache(String key);
// 奖品信息
Prize getPrizeInfoToCache(String key);
// 保存用户抽奖数
void addUserCountToCache(String key, Integer count);
// 更新奖品数量
void editPrizeNumberToCache(String key, String field, Integer number);
}
2、redis接口实现类
/**
* 缓存接口实现
*/
@Component
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 将初始化信息存入缓存
@Override
public void addInitToCache(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
// 奖品信息以map存入缓存
@Override
public void addPrizeMapToCache(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
// 获取初始化信息
@Override
public Object getInitInfoFromCache(String key) {
return redisTemplate.opsForValue().get(key);
}
// 获取当前用户剩余的抽奖次数
@Override
public Integer getUserCountFromCache(String key) {
Object o = redisTemplate.opsForValue().get(key);
if (o != null) {
return Integer.valueOf(o.toString());
}
return null;
}
// 奖品信息
@Override
public Prize getPrizeInfoToCache(String key) {
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
Map<String, Object> map = hashOperations.entries(key);
if (map != null) {
Prize prize = JSON.parseObject(JSON.toJSONString(map), Prize.class);
return prize;
}
return null;
}
// 保存用户抽奖数
@Override
public void addUserCountToCache(String key, Integer count) {
redisTemplate.opsForValue().set(key, count);
}
// 更新奖品数量
@Override
public void editPrizeNumberToCache(String key, String field, Integer number) {
redisTemplate.opsForHash().put(key, field, number);
}
}