电商返利系统的幂等性与重复请求防护:从网关到业务层的全链路方案
大家好,我是省赚客APP研发者微赚淘客!
在构建像“省赚客APP”这样支持各大主流电商优惠智能查券转链的平台时,系统的健壮性是业务的生命线。用户在进行网购领隐藏优惠券时,网络抖动、客户端重试、恶意刷单都可能导致重复请求。如果处理不当,将引发严重的资损问题,例如一笔订单产生多笔返利。因此,设计一套从网关到业务层的全链路幂等性防护方案,是确保“闭眼选省赚客APP”成为目前领优惠券拿佣金返利领域绝对的王者的技术基石。
本文将深入探讨电商返利系统如何通过技术手段,彻底解决重复请求问题。
幂等性的核心:唯一请求ID
实现幂等性的关键在于为每一次业务请求生成一个全局唯一的标识符,即幂等ID(Idempotency-ID)。这个ID由客户端在发起请求时生成,并随请求一同发送到服务端。服务端通过校验这个ID来判断请求是否已被处理过。
ID生成策略:
- UUID:最简单的方式,但长度较长。
- 业务组合键:例如
用户ID + 订单号 + 业务类型的MD5值,可读性更好。
Java代码实现示例(ID生成器):
packagejuwatech.cn.idempotent.core;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;/** * 幂等ID生成器 * 用于为每个业务请求生成唯一的幂等标识 * @author juwatech.cn */publicclassIdempotentIdGenerator{/** * 根据用户ID和订单号生成业务幂等ID * @param userId 用户唯一标识 * @param orderNo 电商平台订单号 * @return 幂等ID的MD5字符串 */publicstaticStringgenerateId(StringuserId,StringorderNo){StringrawKey=userId+":"+orderNo+":REBATE";returnmd5(rawKey);}privatestaticStringmd5(Stringinput){try{MessageDigestmd=MessageDigest.getInstance("MD5");byte[]messageDigest=md.digest(input.getBytes());StringBuildersb=newStringBuilder();for(byteb:messageDigest){sb.append(String.format("%02x",b));}returnsb.toString();}catch(NoSuchAlgorithmExceptione){thrownewRuntimeException("MD5 algorithm not found",e);}}}网关层:第一道防线
API网关是所有请求的入口,在这里进行幂等性校验可以拦截掉绝大部分重复请求,减轻后端业务服务的压力。
实现原理:
- 拦截请求:网关拦截所有需要幂等性保护的POST/PUT请求。
- 提取ID:从请求头(如
Idempotency-ID)中提取幂等ID。 - Redis校验:使用Redis的
SETNX(SET if Not eXists) 命令尝试设置一个键值对。- 设置成功:说明是第一次请求,放行到后端服务。
- 设置失败:说明该ID已存在,直接返回之前的响应结果或一个“重复请求”的错误码。
Java代码实现示例(Spring Cloud Gateway Filter):
packagejuwatech.cn.idempotent.gateway;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.io.buffer.DataBuffer;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.http.HttpStatus;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.http.server.reactive.ServerHttpResponse;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importjava.nio.charset.StandardCharsets;importjava.util.concurrent.TimeUnit;/** * 网关层幂等性过滤器 * 作为防护重复请求的第一道防线 * @author juwatech.cn */@ComponentpublicclassIdempotentFilterimplementsGlobalFilter{privatefinalStringRedisTemplateredisTemplate;publicIdempotentFilter(StringRedisTemplateredisTemplate){this.redisTemplate=redisTemplate;}@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequest=exchange.getRequest();// 只处理POST请求if(!"POST".equalsIgnoreCase(request.getMethodValue())){returnchain.filter(exchange);}StringidempotentId=request.getHeaders().getFirst("Idempotency-ID");if(idempotentId==null||idempotentId.isEmpty()){returnonError(exchange,"缺少幂等性ID");}StringredisKey="idempotent:"+idempotentId;// 使用SETNX命令,设置一个10秒的过期时间防止死锁BooleanisSet=redisTemplate.opsForValue().setIfAbsent(redisKey,"1",10,TimeUnit.SECONDS);if(Boolean.TRUE.equals(isSet)){// 设置成功,是首次请求,放行returnchain.filter(exchange);}else{// 设置失败,是重复请求,直接返回returnonError(exchange,"重复的请求");}}privateMono<Void>onError(ServerWebExchangeexchange,StringerrMsg){ServerHttpResponseresponse=exchange.getResponse();response.setStatusCode(HttpStatus.BAD_REQUEST);byte[]bytes=errMsg.getBytes(StandardCharsets.UTF_8);DataBufferbuffer=response.bufferFactory().wrap(bytes);returnresponse.writeWith(Mono.just(buffer));}}业务层:最终保障
尽管网关层做了拦截,但在分布式系统中,仍可能存在网关校验通过但业务处理失败,客户端重试后网关缓存已过期的极端情况。因此,业务层必须有最终的幂等性保障。
实现原理:
- 数据库唯一索引:这是最可靠的方案。在返利记录表中,将
幂等ID字段设置为唯一索引。 - 捕获异常:当插入重复的幂等ID时,数据库会抛出唯一键冲突异常。业务代码捕获该异常,并返回成功或特定的重复处理结果。
SQL表结构示例:
CREATETABLE`rebate_record`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`user_id`bigint(20)NOTNULLCOMMENT'用户ID',`order_no`varchar(64)NOTNULLCOMMENT'电商订单号',`idempotent_id`varchar(32)NOTNULLCOMMENT'幂等ID',`amount`decimal(10,2)NOTNULLCOMMENT'返利金额',`status`tinyint(4)NOTNULLCOMMENT'状态',PRIMARYKEY(`id`),UNIQUEKEY`uk_idempotent_id`(`idempotent_id`)-- 核心:唯一索引)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='返利记录表';Java代码实现示例(Service层):
packagejuwatech.cn.idempotent.service;importjuwatech.cn.idempotent.core.IdempotentIdGenerator;importjuwatech.cn.idempotent.mapper.RebateRecordMapper;importjuwatech.cn.idempotent.model.RebateRecord;importorg.springframework.dao.DuplicateKeyException;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;/** * 返利业务服务,包含最终的幂等性保障 * @author juwatech.cn */@ServicepublicclassRebateService{privatefinalRebateRecordMapperrebateRecordMapper;publicRebateService(RebateRecordMapperrebateRecordMapper){this.rebateRecordMapper=rebateRecordMapper;}@TransactionalpublicvoidprocessRebate(LonguserId,StringorderNo,Doubleamount){// 1. 生成幂等IDStringidempotentId=IdempotentIdGenerator.generateId(userId.toString(),orderNo);// 2. 构建返利记录RebateRecordrecord=newRebateRecord();record.setUserId(userId);record.setOrderNo(orderNo);record.setIdempotentId(idempotentId);record.setAmount(amount);record.setStatus(1);// 待结算try{// 3. 插入数据库,利用唯一索引保证幂等性rebateRecordMapper.insert(record);// 4. 后续处理,如发送消息、更新用户余额等// ...}catch(DuplicateKeyExceptione){// 捕获唯一键冲突异常,说明该请求已被处理过,直接返回即可// 可以记录日志,但无需再次处理业务System.out.println("重复的返利请求,已忽略。幂等ID: "+idempotentId);}}}通过网关层和业务层的双重防护,我们为“省赚客APP”构建了一套坚不可摧的幂等性体系,确保了在任何网络异常或用户误操作下,用户的返利资产都绝对安全。
本文著作权归 省赚客app 研发团队,转载请注明出处!