拆红包的核心功能:
拆红包功能是整个功能的核心,红包计算逻辑:剩余金额/剩余红包个数 * 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<String>(StatusCode.IS_EXISTS.getCode(),StatusCode.IS_EXISTS.getMsg(),null);
}
if(code < 100){
return new RetDTO<String>(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<String>(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<String>(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,都是一些常用的功能。