thinkphp 6.0 中通过 redis 实现分布式幂等性校验:客户端先获取唯一时效 token,业务接口用 lua 脚本原子性校验并删除 token,确保同一请求只执行一次;需结合中间件封装、命名空间隔离、redis 降级等机制保障可靠性。

在 ThinkPHP 6.0 中使用 Redis 实现分布式幂等性接口校验,核心目标是:**同一业务请求(如下单、支付、提交表单)无论被重复发起多少次,后端只执行一次核心逻辑,且结果一致**。这不仅防重复提交,也具备防重放能力(即拦截过期或已被消费的 Token)。关键不在于“锁住整个接口”,而在于为每次合法请求绑定唯一、有时效的凭证,并用 Redis 的原子操作做“存在即通过、通过即失效”的判断。
Token 生成与下发(客户端首次获取)
用户触发需幂等的操作前(如点击“提交订单”),先调用一个专用接口(如 /api/v1/idempotent/token)获取 Token:
- 服务端生成全局唯一字符串,推荐用 TP6 内置的 think\helper\Str::random(32) 或结合时间戳+微秒+随机数拼接,避免依赖外部 UUID 库;
- 将 Token 作为 Key 存入 Redis,Value 可存空值或简要上下文(如用户 ID、业务类型),并设置合理 TTL(如 10 分钟);
- 务必使用 SET key value EX seconds NX 命令(TP6 Redis 封装中对应
$redis->set($key, $value, ['ex' => 600, 'nx' => true])),确保写入原子性,防止并发生成重复 Token; - 响应中将 Token 放入 JSON body(如
{"token": "abc123..."}),前端需在后续业务请求中携带(建议放在请求头 X-Idempotent-Token)。
业务接口的幂等校验(带 Token 请求时)
真正执行业务的接口(如 /api/v1/order/create)需在逻辑开始前完成校验:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 从请求头或参数中提取 Token,拼接成 Redis Key(如 idempotent:token:abc123...);
- 用 Lua 脚本执行“判断是否存在 + 删除”两个动作,保证原子性(这是防并发重复的关键):
-- Lua 脚本示例(保存为 resources/script/check_and_del.lua)if redis.call("exists", KEYS[1]) == 1 then<br> return redis.call("del", KEYS[1])<br>else<br> return 0<br>end - TP6 中调用:
$result = $redis->eval($luaScript, [$tokenKey], 1);; - 若
$result === 1,说明 Token 有效且已成功标记为“已使用”,可继续执行下单等业务;
若$result === 0,直接返回 409 Conflict 或自定义错误码(如{"code": 4001, "msg": "请求已处理,请勿重复提交"})。
集成到中间件或 AOP 风格拦截(降低侵入性)
为避免每个控制器方法都手写校验逻辑,建议封装为中间件:
- 创建中间件 app/middleware/IdempotentCheck.php;
- 在
handle()中解析请求头 Token,执行上述 Lua 校验; - 校验失败时中断请求并返回统一错误响应,成功则
return $next($request); - 在路由或控制器类注解中绑定该中间件,例如:
Route::post('order/create', 'OrderController@create')->middleware('idempotent');
注意事项与边界处理
实际落地时需关注几个易忽略点:
- Token 失败重试机制:若业务执行中途异常(如数据库写入失败),Token 已被删除,此时前端不应直接重发原 Token。应设计“重试时重新申请新 Token”流程,或服务端在捕获异常后尝试回写 Token(需谨慎,避免状态不一致);
- Key 命名空间隔离:不同环境(dev/test/prod)或不同业务线(支付/订单/退款)的 Token Key 应加前缀区分,如 idempotent:pay:${token},避免误删;
- Redis 故障降级:生产环境需考虑 Redis 不可用时的兜底策略,例如记录日志告警 + 允许有限次数的重复(配合数据库唯一索引兜底),而非直接报错阻断;
- 不适用于 GET 查询类接口:幂等校验主要针对有副作用的写操作(POST/PUT/DELETE),GET 本身语义幂等,无需额外 Token。










