二分法红包

采用二分法划分红包

 public static List<Integer> divideRedPackage(Integer totalAmount,Integer totalPeopleNum){
        int restAmount=totalAmount;     //剩余金额
        int restPeopleNum=totalPeopleNum;     //剩余人数
        ArrayList<Integer>amountList= new ArrayList<>();  //存储红包金额
        Random rand = new Random();
        for(;restPeopleNum>1;restPeopleNum--){
            // restAmount/restPeopleNum>=1
            int amount=rand.nextInt(restAmount/restPeopleNum*2-1)+1;
            amountList.add(amount);
            restAmount-=amount;
        }
        amountList.add(restAmount);
        return amountList;
    }
数据库表对应的model层

RedRecord:

    private Integer id;    //主键
    private Integer userId;  //用户id
    private String redPacket;  //红包标识串
    private Integer total;   //红包数量
    private BigDecimal amount;  //红包总金额(单位 分)
    private Byte isActive;      //是否有效
    private Date createTime;   //创建时间

RedDetail: 记录单个小红包信息

    private Integer id;     //主键
    private Integer recordId;    //对应的红包主键id
    private BigDecimal amount;  //单个小红包金额
    private Byte isActive;
    private Date createTime

RedRobRecord

    private Integer id;
    private Integer userId;   //抢红包的user的id
    private String redPacket;  //红包标识串
    private BigDecimal amount;  //小红包的金额
    private Date robTime;
    private Byte isActive;

控制层

@RestController
public class RedPacketController {
    private static final Logger log = getLogger(RedPacketController.class);
    private static final String prefix="red/packet";
    @Autowired
    private IRedPacketService redPacketService;

    @PostMapping(prefix+"/hand/out")
    public BaseResponse handout(@Validated @RequestBody RedPacketDto dto, BindingResult result){
        if(result.hasErrors()){
            return new BaseResponse(StatusCode.InvalidParams);
        }
        BaseResponse response = new BaseResponse(StatusCode.Succuss);
        try {
            String redId = redPacketService.handOut(dto);
            response.setData(redId);  //将红包唯一标识返回前端
        } catch (Exception e) {
            log.error("发红包异常:dto={}",dto,e.fillInStackTrace());
             response = new BaseResponse(StatusCode.Fail.getCode(), e.getMessage());
        }
        return response;
    }

    @GetMapping(prefix+"/rob")
    public BaseResponse rob(@RequestParam Integer userId,@RequestParam String redId){
        BaseResponse response = new BaseResponse(StatusCode.Succuss);
        try {
            BigDecimal result = redPacketService.rob(userId, redId);
            if(result !=null){
                response.setData(result);
            }else{
                response=new BaseResponse(StatusCode.Fail.getCode(),"红包已被抢完");
            }
        } catch (Exception e) {
            log.error("抢红包发生异常:userId={} redId={}",userId,redId,e.fillInStackTrace());
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
}

服务层

@Service
public class RedPacketService implements IRedPacketService {
    private static final Logger log = getLogger(RedPacketService.class);
    private static final String keyPrefix = "redis:red:packet:";
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private IRedService redService;

    @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 timestamp = String.valueOf(System.nanoTime()); //随机数字串
            String redId = new StringBuffer(keyPrefix).append(dto.getUserId()).append(":")
                    .append(timestamp).toString();  //红包唯一标识
            redisTemplate.opsForList().leftPushAll(redId, list);
            String redTotalKey = redId + ":total";
            redisTemplate.opsForValue().set(redTotalKey, dto.getTotal());
            redService.recordRedPacket(dto, redId, list);
            return redId;   //通过redId->list
        } else {
            throw new Exception("参数不合法");
        }
    }

    //高并发下多个线程同时访问资源,数据不一致
    @Override
    public BigDecimal rob(Integer userId, String redId) throws Exception {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Object obj = valueOperations.get(redId + userId + ":rob");
        //如果已抢过红包,直接返回金额
        if (obj != null) {
            return new BigDecimal(obj.toString());
        }
        Boolean res = click(redId);
        if (res) {
            final String lockKey = redId + userId + "-lock";
            //间接实现分布式锁
            Boolean lock = valueOperations.setIfAbsent(lockKey, redId);
            redisTemplate.expire(lockKey, 24L, TimeUnit.HOURS);

            //从红包列表中弹出一个值
            Object value = redisTemplate.opsForList().rightPop(redId);
            if (value != null) {
                try {
                    if (lock) {   //加锁,防止高并发下同一个用户多次抢红包
                        String redTotalKey = redId + ":total";
                        Integer currTotal = valueOperations.get(redTotalKey) != null ? (Integer) valueOperations.get(redTotalKey) : 0;
                        valueOperations.set(redTotalKey, currTotal - 1);   //更新红包个数
                        //红包单位:分换算为元  除以100
                        BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal(100));
                        redService.recordRobRedPacket(userId, redId, new BigDecimal(value.toString()));
                        //标记用户已抢过
                        valueOperations.set(redId + userId + ":rob", result, 24L, TimeUnit.HOURS);
                        log.info("当前用户抢到红包了:userId={} key={} 金额={}元", userId, redId, result);
                        return result;
                }
                } catch (Exception e) {
                    throw new Exception("抢红包-分布式加锁失败");
                }
            }
        }
        return null;
    }

    private Boolean click(String redId) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String redTotalKey = redId + ":total";
        //获取剩余红包个数
        Object total = valueOperations.get(redTotalKey);
        if (total != null && Integer.valueOf(total.toString()) > 0) {
            return true;
        }
        return false;
    }
}
@Service
@EnableAsync
public class RedService implements IRedService {
    private static final Logger log = getLogger(RedService.class);
    @Autowired
    RedRecordMapper redRecordMapper;
    @Autowired
    RedDetailMapper redDetailMapper;
    @Autowired
    RedRobRecordMapper redRobRecordMapper;
    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void recordRedPacket(RedPacketDto dto, String redId, List<Integer> list) throws Exception {
        RedRecord redRecord = new RedRecord();
        redRecord.setUserId(dto.getUserId());
        redRecord.setAmount(BigDecimal.valueOf(dto.getAmount()));
        redRecord.setTotal(dto.getTotal());
        redRecord.setRedPacket(redId);
        redRecord.setCreateTime(new Date());
        redRecordMapper.insertSelective(redRecord);
        RedDetail redDetail;
        for (Integer i : list) {
            redDetail =new RedDetail();
            redDetail.setAmount(BigDecimal.valueOf(i));
            redDetail.setRecordId(redRecord.getId());
            redDetail.setCreateTime(new Date());
            redDetailMapper.insertSelective(redDetail);
        }
    }

    @Override
    @Async
    public void recordRobRedPacket(Integer userId, String redId, BigDecimal amount) throws Exception {
        RedRobRecord redRobRecord = new RedRobRecord();
        redRobRecord.setAmount(amount);
        redRobRecord.setRedPacket(redId);
        redRobRecord.setUserId(userId);
        redRobRecord.setRobTime(new Date());
        redRobRecordMapper.insertSelective(redRobRecord);
    }
}

Dto层

public class RedPacketDto {
    private Integer userId;
    @NotNull
    private Integer total;  //红包个数
    @NotNull
    private Integer amount;  //红包总金额
    }

entity

public class BaseResponse<T> {
 private Integer code;
 private String msg;
 private T data;

 public BaseResponse(Integer code, String msg) {
     this.code = code;
     this.msg = msg;
 }

 public BaseResponse(StatusCode statusCode) {   //不同的重载方式
     this.code=statusCode.getCode();
     this.msg=statusCode.getMsg();
 }

 public BaseResponse(Integer code, String msg, T data) {
     this.code = code;
     this.msg = msg;
     this.data = data;
 }
}
public enum StatusCode {
 Succuss(0,"成功"),
 Fail(-1,"失败"),
 InvalidParams(-2,"参数非法");
 private Integer code;
 private String msg;

 StatusCode(Integer code, String msg) {
     this.code = code;
     this.msg = msg;
 }
}

流程图
红包流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值