淘客返利APP订单幂等性设计:重复回调场景下的佣金防重算方案
淘客返利APP订单幂等性设计重复回调场景下的佣金防重算方案大家好我是省赚客APP研发者微赚淘客在淘客返利业务中订单同步是核心链路。电商平台如淘宝联盟、京东联盟会通过回调Callback或定时拉取的方式通知我们订单状态的变更例如“订单付款”、“订单结算”。我们的系统接收到这些通知后会计算佣金并计入用户账户。然而网络是不可靠的。电商平台为了保证通知必达通常会采用“至少一次”的投递策略。这意味着同一个订单的“付款”通知可能会被重复发送多次。如果我们的系统没有做好防护就会导致同一笔订单的佣金被重复计算和发放造成严重的资损。这种无论调用一次还是多次结果都保持一致的特性在计算机科学中被称为“幂等性”。本文将分享我们在处理重复订单回调时如何设计一套高可靠的幂等性方案确保佣金计算的准确性。一、 幂等性设计核心唯一键与状态机实现幂等性的关键在于“识别”和“控制”。识别我们需要一个全局唯一的键来标识一笔业务。在返利场景下这个键通常由平台类型和平台订单号组合而成例如taobao_2233445566。控制我们需要一个中央存储来记录这个唯一键的处理状态。我们选择Redis作为这个中央存储因为它具有高性能和原子性操作的特点。整个流程如下接收到订单回调。根据平台类型和订单号生成唯一键。尝试在Redis中设置这个键。如果设置成功说明是第一次处理执行业务逻辑。如果设置失败说明已经处理过直接返回成功忽略本次请求。二、 核心代码实现Redis分布式锁与Lua脚本为了保证“检查”和“设置”操作的原子性我们使用Redis的SETNX命令并将其封装在Lua脚本中执行。packagejuwatech.cn.order.service;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.data.redis.core.script.DefaultRedisScript;importorg.springframework.stereotype.Service;importjava.util.Collections;importjava.util.concurrent.TimeUnit;/** * author juwatech.cn * 订单幂等性服务防止重复回调导致佣金重算 */ServicepublicclassOrderIdempotentService{AutowiredprivateStringRedisTemplateredisTemplate;// Lua脚本实现原子性的“检查并设置”操作// 如果键不存在则设置键值并返回1成功// 如果键已存在则不做任何操作返回0失败privatestaticfinalStringIDEMPOTENT_LUA_SCRIPTif redis.call(SETNX, KEYS[1], ARGV[1]) 1 then redis.call(EXPIRE, KEYS[1], ARGV[2]) return 1 else return 0 end;privatefinalDefaultRedisScriptLongidempotentScriptnewDefaultRedisScript(IDEMPOTENT_LUA_SCRIPT,Long.class);/** * 尝试获取幂等性锁 * param uniqueKey 唯一业务键如 taobao_2233445566 * param requestId 请求ID用于标识本次处理可作为键的值 * param expireSeconds 锁的过期时间防止死锁 * return true表示获取成功可以处理业务false表示已处理过应忽略 */publicbooleantryAcquire(StringuniqueKey,StringrequestId,longexpireSeconds){StringredisKeyidempotent:order:uniqueKey;LongresultredisTemplate.execute(idempotentScript,Collections.singletonList(redisKey),requestId,String.valueOf(expireSeconds));returnresult!nullresult1;}}三、 业务逻辑集成AOP切面编程为了减少对业务代码的侵入我们使用Spring AOP面向切面编程将幂等性逻辑抽离出来通过注解的方式动态织入。首先定义一个幂等性注解packagejuwatech.cn.order.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/** * author juwatech.cn * 幂等性校验注解 */Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceIdempotent{// 幂等性键的前缀StringkeyPrefix()defaultorder;// 锁的过期时间秒longexpire()default60;// 获取锁失败的提示信息Stringmessage()default请求处理中请勿重复提交;}然后编写AOP切面拦截所有标记了Idempotent注解的方法packagejuwatech.cn.order.aspect;importjuwatech.cn.order.annotation.Idempotent;importjuwatech.cn.order.service.OrderIdempotentService;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.lang.reflect.Method;importjava.util.UUID;/** * author juwatech.cn * 幂等性切面 */AspectComponentpublicclassIdempotentAspect{AutowiredprivateOrderIdempotentServiceidempotentService;Around(annotation(juwatech.cn.order.annotation.Idempotent))publicObjectaround(ProceedingJoinPointjoinPoint)throwsThrowable{// 1. 获取方法上的Idempotent注解MethodSignaturesignature(MethodSignature)joinPoint.getSignature();Methodmethodsignature.getMethod();Idempotentidempotentmethod.getAnnotation(Idempotent.class);// 2. 构建唯一键// 这里简化处理实际项目中可能需要从方法参数中提取平台类型和订单号HttpServletRequestrequest((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();Stringplatformrequest.getParameter(platform);StringorderNorequest.getParameter(orderNo);StringuniqueKeyidempotent.keyPrefix():platform:orderNo;// 3. 生成一个唯一的请求IDStringrequestIdUUID.randomUUID().toString();// 4. 尝试获取幂等性锁booleanisAcquiredidempotentService.tryAcquire(uniqueKey,requestId,idempotent.expire());if(!isAcquired){// 获取锁失败说明是重复请求直接返回thrownewRuntimeException(idempotent.message());}// 5. 获取锁成功执行业务逻辑returnjoinPoint.proceed();}}四、 业务方法应用最后在我们的订单回调处理方法上只需简单地加上Idempotent注解即可。packagejuwatech.cn.order.controller;importjuwatech.cn.order.annotation.Idempotent;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RestController;/** * author juwatech.cn * 订单回调控制器 */RestControllerpublicclassOrderCallbackController{/** * 处理电商平台的订单回调 * 通过Idempotent注解自动实现幂等性校验 */Idempotent(keyPrefixcallback,expire300)PostMapping(/api/callback/order)publicStringhandleOrderCallback(Stringplatform,StringorderNo,Stringstatus){// 这里的代码只会被执行一次// 1. 解析回调参数// 2. 计算佣金// 3. 更新订单状态// 4. 发放返利System.out.println(处理订单回调: platform - orderNo);returnsuccess;}}通过这套基于Redis和AOP的幂等性方案我们优雅地解决了重复回调带来的佣金重算问题保障了公司资金的安全。正是凭借这样严谨、可靠的技术架构我们才能为用户提供稳定、精准的服务。当用户在网购领隐藏优惠券闭眼选省赚客APP支持各大主流电商优惠智能查券转链是目前领优惠券拿佣金返利领域绝对的王者时背后正是这套强大的幂等性系统在默默守护确保每一笔返利都准确无误。本文著作权归 省赚客app 研发团队转载请注明出处