Redis+分布式锁实现发红包抢红包Java版

Redis+分布式锁实现发红包和抢红包:

在这里插入图片描述

直接上图:上图为发红包和抢红包的整体业务逻辑(当然,这个只是比较简单的版本,只有发红包和抢红包功能,后期继续完善);

controller层

	/**
     * 发红包请求
     * @param dto 发红包需要的实体
     * @param result 获取校验信息
     * @return
     */
    @PostMapping("/hand/out")
    public BaseResponse handOut(@RequestBody RedPacketDto dto, BindingResult result){
        // 校验用户输入的参数是否正确
        if(result.hasErrors()){
            return new BaseResponse(StatusCode.InvalidParams);
        }
        BaseResponse response = new BaseResponse(StatusCode.Success);
        try{
            // 核心业务逻辑处理服务,最终返回红包全局唯一标识串
            String redId = redDetailService.handOut(dto);
            response.setData(redId);
        }catch (Exception e){
            response = new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }

service实现类:

/**
     * 发送红包核心业务逻辑
     * @param dto
     * @return
     * @throws Exception
     */
    @Override
    public String handOut(RedPacketDto dto) throws Exception {
        // 判断参数的合法性
        if (dto.getTotal() > 0 && dto.getAmount() > 0){
            // 采用二倍均值发生成随机金额列表
            List<Integer> list = RedPacketUtil.divideRedPackage(dto.getAmount(), dto.getTotal());
            // 变成String类型
            List<String> strList = new ArrayList<>();
            for (Integer integer : list) {
                strList.add(integer.toString());
            }
            // 生成红包全局唯一标识串
            String timestamp = String.valueOf(System.nanoTime());
            // 根据缓存Key的前缀与其它信息拼接成一个新的用于存储随机金额列表的KEY
            String redId = new StringBuffer(keyPrefix).append(dto.getUserId()).append(":").append(timestamp).toString();
            // 根据缓存key的前缀与其它信息拼接成一个新的用于存储红包总数的key
            String redTotalKey = redId+":total";
            // 将红包总数存入缓存中
            redisTemplate.opsForValue().set(redTotalKey,dto.getTotal().toString());

            // 将随机生成的金额添加到缓存中
            redisTemplate.opsForList().leftPushAll(redId, strList);

            // 异步记录红包的全局唯一标识串,红包个数与随机金额列表入数据库
            iRedService.recordPacket(dto,redId,list);
            // 将红包的全局唯一标识串返回给前端
            return redId;
        }else{
            throw new Exception("系统异常-分发红包-参数不合法");
        }
    }

上面再关于红包发放的时候,出现了一个算法,工具类如下:

/**
     * 发红包算法,金额参数以分为单位
     * @param totalAmount 红包总金额(单位为分)
     * @param totalPeopleNum 总人数
     * @return
     */
    public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
        // 用户存储每次产生的小红包随机金额列表,单位为分
        List<Integer> amountList = new ArrayList<>();
        // 判断总金额和总人数参数的合法性
        if(totalAmount > 0 && totalPeopleNum > 0){
            // 记录剩余的总金额,初始化时即为红包的总金额
            Integer restAmount = totalAmount;
            // 记录剩余的总人数,初始化即为指定的总人数
            Integer restPeopleNum = totalPeopleNum;
            // 定义产生随机数的实例对象
            Random random = new Random();
            // 不断循环遍历,迭代更新的产生随机金额直到N-1 小于等于0
            for (int i = 0; i < totalPeopleNum - 1; i++) {
                // 随机区间:[1,剩余人均金额的两倍)
                int amount = random.nextInt(restAmount/restPeopleNum*2-1)+1;
                // 更新剩余的总金额M=M-R
                restAmount -= amount;
                // 更新剩余的总人数N=N-1
                restPeopleNum--;
                // 将生产的随机金额添加到列表中
                amountList.add(amount);
            }
            // 循环完毕,剩余的金额即为最后一个随机金额,也需要将其添加到列表中。
            amountList.add(restAmount);
        }
        // 返回最终产生的随机金额列表返回。
        return amountList;
    }

抢红包业务:

controller:

/**
     * 处理抢红包请求
     * @param userId 用户id
     * @param redId 红包全局id
     * @return
     */
    @GetMapping("/rob")
    public BaseResponse rob(@RequestParam Integer userId,@RequestParam String redId){
        BaseResponse response = new BaseResponse(StatusCode.Success);
        try{
            BigDecimal result = redPackService.rob(userId,redId);
            if(result != null){
                response.setData(result);
            }else{
                response = new BaseResponse(StatusCode.Fail.getCode(),"红包被抢完了");
            }
        }catch (Exception e){
            response = new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }

service:

/**
     * 抢洪波实际业务逻辑的处理
     * @param userId 当前用户id
     * @param redId 红包全局唯一标识
     * @return
     * @throws Exception
     */
    @Override
    public BigDecimal rob(Integer userId, String redId) throws Exception {
        // 在处理用户抢红包之前,需要先判断用户是否已经抢过红包了
        String isRob = redisTemplate.opsForValue().get(redId + userId + ":rob");
        if(!StringUtils.isEmpty(isRob)){
            return new BigDecimal(isRob);
        }

        // 点红包业务逻辑,主要用于判断缓存中是否仍然有红包,即剩余红包的个数是否大于0
        Boolean res = click(redId);
        if(res){
            // 解决出现同一时刻多用户同时进入:添加分布式锁
            final String locKey = redId+userId+"-lock";
            // 调用方法,间接实现分布式锁
            Boolean isLock = redisTemplate.opsForValue().setIfAbsent(locKey, redId, 24L, TimeUnit.HOURS);
            try {
                if(isLock){
                    // 需要从红包金额中弹出一个随机金额
                    String leftPop = redisTemplate.opsForList().rightPop(redId);
                    if(!StringUtils.isEmpty(leftPop)){
                        // 进而当前用户抢到了红包,首先需要对缓存中数量-1
                        String redTotalKey = redId+":total";
                        Integer currTotal = redisTemplate.opsForValue().get(redTotalKey) != null ?
                                Integer.parseInt(redisTemplate.opsForValue().get(redTotalKey)) : 0;
                        // 目前只考虑对设定的金额进行弹出,针对红包个数此处没有做特别的处理。只是在24小时之后进行删除
                        redisTemplate.opsForValue().set(redTotalKey,currTotal.toString(),24L,TimeUnit.HOURS);

                        // 将从缓存系统中弹出来的红包金额原本的分变成元
                        BigDecimal result = new BigDecimal(leftPop).divide(new BigDecimal(100));
                        redService.recordRobRedPacket(userId,redId,new BigDecimal(leftPop));

                        // 将数据添加到缓存中,用来给上方判读是否领取过
                        redisTemplate.opsForValue().set(redId+userId+":rob",result.toString(),24L, TimeUnit.HOURS);
                        return result;
                    }
                }

            } catch (Exception e){
                throw  new Exception("系统异常-抢红包-加分布式锁失败");
            }
        }
        return null;
    }

	/**
     * 点红包的业务处理逻辑,如果返回true,则表示缓存系统redis还有红包,否则就抢光了
     * @param redId 红包全局唯一id
     * @return
     * @throws Exception
     */
    private Boolean click(String redId) throws Exception{
        String total = redisTemplate.opsForValue().get(redId + ":total");
        if(Integer.parseInt(total) > 0){
            return true;
        }
        return false;
    }	

想要源码可以私聊

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值