优惠券领取方案分享
2025-02-08 07:24 阅读(68)

前言

项目背景,不是春节快到了么,一年一度的返乡潮也随之而来,业主方要求开发一个权益发放的项目,让农民工在线上领取优惠券,线下进核销。因为开发周期等原因,优惠券领取和维护都是对接公司现有项目的接口。


虽然项目流量不大,也没有用太多的中间件,我还是相信本篇文章也能给一些初中级开发一点参考。当然如果读者有丰富的秒杀等高并发经验也欢迎提出您宝贵的意见。


落地方案

上面也说了,优惠券的领取、核销和数量的维护都是对接公司现有项目(B)的接口。本次开发的新项目(A)也会去同步库存数据,以及保存领取记录。

虽然领取接口也是调用(后端调用) 其他系统的服务,但是库存扣减、限流、并发、缓存 我们也还是自己做了一遍。

主要的设计方案如下:

https://www.zuocode.com

优惠券总数也会从B系统拉取保存到本地,库存数量也是本系统自己维护。库存数量不一致,就会走数据对账流程

库存拉取同步

管理员在创建活动,配置优惠券的时候,会去查询B项目的优惠券,保存之后,A 系统就会把基础信息进行保存,以及优惠券总数和库存也都会进行保存。 后续B项目可能会追加库存,我们A系统就去页面主动点击库存同步,同时定时任务也会定时去同步库存。

领取接口

看看领取的伪代码吧,代码中的Object实际代码中肯定是具体的类。

Controller 层代码

// 1s 最多200个请求
@RateLimiter(value = 200)
public Response<Object> receiveCoupon(@RequestBody @Validated ReqParam receiveReq) {
    User user = getUser();
    String lockKey = LOCK_KEY+user.getId();
    // 活动用户锁,获取不到直接返回失败,锁过期时间 20s
    Boolean lock = redissonLockClient.tryLock(lockKey,20);
    if(!lock){
        return Result.ok("稍后再试");
    }
    //领取权益
    try {
        Object vo = pageCouponService.receiveCoupon(receiveReq);
        return Result.OK(vo);
    }finally {
        redissonLockClient.unlock(lockName);
    }

}

Service 层 核心逻辑

校验用户是否能领取

insert 日志

调用三方领取接口 (通过HTTP调用)

更新日志

更新本系统库存

insert 用户领取记录

public Object getCoupon(ReqParam req) {
    // 1. 校验是否能够领取
    FaileVO vo = checkUserNotReceiveReason(req);
    if(Objects.nonNull(vo)){
        return vo;
    }
    //2.先做日志记录
    Long logId = recordLog(pageCoupon,bizId);
    //3.调用三方领取接口 
    ReceiveRet receiveRet = requestPhReceive(req);
    //4.更新日志
    updateRecordLog(logId,receiveRet);
    //领取失败
    if(Objects.nonNull(receiveRet.getFailedCode())){
         return vo;
    }
    //库存扣减
    Integer row = inventoryMapper.inventoryReduceOne(inventoryId);
    //领取成功,保存领取记录.....
    if(row == 1){
        saveReceiveRecord(receiveRet...);
    }
    //返回领取成功信息
    return vo;
}

看完service的主体代码,简要的分析一下,为什么调用第三方接口之前 insert 了日志,以及为什么不加事务呢

加日志的理由:

主要是防止HTTP接口没有正常响应带来的影响,方便问题数据的恢复;

比如:第三系统的HTTP接口实际上执行成功了,但是由于网络问题或者其他原因,没有正常的响应。后续我们发现我们的库存数据,对不上的时候,就可以通过我们日志去做数据恢复

不加事务的理由:


加事务影响库存扣减的效率,毕竟库存扣减的行锁需要等事务执行完成之后才释放

如果对整个代码加事务,因为有HTTP请求,可能引起长事务,占用数据库资源。

如果只对本系统的数据库加事务的话,好像意义也不大,因为第三方接口已经领取成功,就算本系统更新数据的操作出现了异常回滚,对实际领取的结果也不会出现任何影响。无非影响的是两个系统的数据一致性。加了之后作用不大,就不加吧!


领取核销对账

因为我们的领取和核销都是调用的 其他系统的接口,所以难免接口调用过程中出现超时等未知问题,所以在领取业务调用B系统接口前先插入一条日志,拿到结果后再更新日志。如果B系统执行完成,但是由于网络等问题,没有做出响应,这条领取日志就会被成为异常日志,后续的对账会再次核对这批数据。

我们的对账任务,就会去扫描库存是否有不一致的情况,不一致就会调用B系统的查询接口,进行数据核对,比如B系统库存扣减成功,但是没有响应结果。 对账任务就会二次查询,修正我们的结果,将B系统领取成功的记录但是 A系统没记录的数据。

总结

本篇文章分享了,对接三方系统领券业务实现方案,为了解决HTTP 接口可能存在丢失响应的情况,我们用了数据对账这一补偿机制,来处理异常数据。


作者:提前退休的java猿

链接:https://juejin.cn