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;
}
想要源码可以私聊