Redis应用项目---抢红包功能(四)

拆红包的核心功能:

拆红包功能是整个功能的核心,红包计算逻辑:剩余金额/剩余红包个数 * 2,这里有个一个点,如果保证剩余金额和剩余红包个数同时等于0,特别是在并发的情况下。

核心代码:

public RetDTO getRedPacketMoney(int uid, long redPacketId){
        //这里判断用户是否已经抢过红包了,以及是否还有红包可以抢
        Map map = this.getRedPacket(redPacketId, uid);
        int code = CommonUtil.getRedPacketStatus(map);
        if(code == 100){
            return new RetDTO(StatusCode.IS_EXISTS.getCode(),StatusCode.IS_EXISTS.getMsg(),null);
        }
        if(code < 100){
            return new RetDTO(StatusCode.IS_QIANG.getCode(),StatusCode.IS_QIANG.getMsg(),null);
        }
        if(code > 100){
            String redPacketName = redPacketId + TOTAL_NUM;
            String totalAmountName = redPacketId + TOTAL_AMOUNT;
            //剩下的次数
            String num = (String)redisService.get(redPacketName);
            Integer totalNumInt = Integer.parseInt(num);
            if (StringUtils.isBlank(num) || Integer.parseInt(num) == 0) {
                return new RetDTO(StatusCode.ISOVER.getCode(),StatusCode.ISOVER.getMsg(),null);
            }
            //剩下的金额
            String totalAmount = (String) redisService.get(totalAmountName);

            if (StringUtils.isNotBlank(totalAmount)){
                //获取随机金额
                Integer totalAmountInt = Integer.valueOf(totalAmount);
                Integer randomAmount = CommonUtil.getMaxMoney(totalAmountInt,totalNumInt);

                if(randomAmount != null && randomAmount > 0){
                    //用来记录谁已经领过红包了
                    String userReceive = redPacketName + "_" + uid;
                    //从安全性上考虑===============这块的代码可以考虑分布式事务,保证数据一致性===============================
                    //lua脚本里面完成了多个命令校验  lua脚本的传参:总金额key,红包总数key,本次红包金额,用户key
                    //excretepacket.lua 1.需要进行剩余金额和红包个数的校验,如果没有剩余金额,则不可再抢红包,如果没有红包个数也不能再抢红包
                    //2.另外特别需要说明的,当最后一个红包的时候,本次红包金额,不能再用,直接把剩下的金额给最后一个用户(这里坑了我大半天,在并发的情况下一直没办法保证总金额和红包个数的数据匹配)
                    String result = redisService.getRedPacketMoney("excretepacket.lua", totalAmountName,redPacketName,String.valueOf(randomAmount),userReceive);
                    //这里lua脚本会放用户抢到的金额,如果返回非金额,则请不要执行落库操作
                    if(CommonUtil.isNumeric(result)){
                        //目前压测发现这里是性能的瓶颈 ,可以考虑利用MQ的形式进行落库
                        updateRacketInDB(uid, redPacketId,Integer.valueOf(result));
                        return new RetDTO<>(StatusCode.ISSUCCESS.getCode(),StatusCode.ISSUCCESS.getMsg(),"恭喜您抢到了"+ randomAmount);
                    }
                    //==================================================================================================

                }else {
                    return new RetDTO<>(StatusCode.IS_ERROR.getCode(),StatusCode.IS_ERROR.getMsg(),"见鬼了");
                }
            }
        }

        return new RetDTO(StatusCode.IS_ERROR.getCode(),StatusCode.IS_ERROR.getMsg(),null);
    }

lua脚本

local totalAmountName = KEYS[1]
local redPacketName = KEYS[2]
local randomAmount = KEYS[3]
local userReceive = KEYS[4]

local result_1 = redis.call("GET",totalAmountName)
local result_2 = redis.call("GET",redPacketName)

local result = "ABC"

if tonumber(result_1) >= tonumber(randomAmount) then
    if tonumber(result_2) > 0 then
        if tonumber(result_2) == 1 then
            redis.call("DECRBY",totalAmountName,result_1)
            redis.call("DECR", redPacketName)
            redis.call("SET", userReceive,userReceive)

            return result_1
        else
            redis.call("DECRBY", totalAmountName, randomAmount)
            redis.call("DECR", redPacketName)
            redis.call("SET", userReceive,userReceive)

            return randomAmount
        end
    end
else
    return result
end

说明:本功能是基于springBoot2.0开发,设计到技术及插件,springBoot2.0,swagger,mybaits,redis,lua脚本,mysql,都是一些常用的功能。

你可能感兴趣的:(redis)