随笔(二十四)『简单抽奖活动』

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);
    }
}
posted @   小昕昕  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示