防重复提交需组合x-idempotency-key头、请求指纹与cache::remember()实现原子幂等控制,仅token_check()无法应对重试/刷新/回调场景。

ThinkPHP 接口防重复提交不能只靠 token_check(),它是一次性校验,不是幂等控制;真要防重试、防刷新、防自动回调,得用 Cache::remember() + 请求指纹 + X-Idempotency-Key 头组合。
接口幂等中间件怎么写才不漏请求
直接在中间件里取 request()->header('X-Idempotency-Key') 不够——空值、空白、超长都得过滤。否则所有无头请求会撞进同一个缓存键,导致误拦截或全放行。
- 先 trim 并校验长度:
$key = trim($request->header('X-Idempotency-Key', '')); if (empty($key) || strlen($key) 64) { return $next($request); } - 指纹不要拼全参:排除
timestamp、nonce、sign等动态字段,只取$user_id+$uri+ksort($filtered_params)后的 JSON - 用
Cache::remember('idempotent_' . md5($fingerprint), 120, function () use ($next, $request) { ... }),窗口期设 120 秒,比 300 更安全,也避免 Redis key 泛滥
为什么 validate 规则里写 'token' => 'require|token' 在接口里总不生效
__token 字段默认不会进 $this->request->param() 的主数据集,验证器根本收不到它。V6+ 的 Validate 类不自动提取这个特殊字段,硬塞规则等于白配。
- 别在
$validate->check($data)里传原始$data,得手动补:$data['__token'] = $this->request->param('__token'); - 更推荐绕过验证器:直接用中间件
think\middleware\Token,但必须确认config/app.php里已设'token_on' => true,否则中间件压根不启动 - API 场景下,
Token::check()仅适合前端手动提交且无重试逻辑的表单类接口;支付回调、APP 自动重试等场景,它会导致第二次请求直接 400,用户感知是“失败”,而非“已成功”
Cache::remember 为什么有时还是挡不住并发重复
单纯 Cache::has() + Cache::set() 有竞态:两个请求几乎同时进来,都查到缓存不存在,然后都执行业务逻辑。必须用原子操作。
-
Cache::remember()是唯一安全选择,它的底层是Cache::store()->get()+Cache::store()->set()的原子封装,Redis 驱动下能保证只有一个请求进回调函数 - 别用数据库或文件缓存做幂等锁,高并发下 I/O 成瓶颈,且没法原子判断+写入
- 如果用了自定义缓存驱动(比如本地数组或 APCu),确认它支持原子 setIfNotExists —— 大部分不支持,会退化成竞态
最易被忽略的是:幂等窗口期和业务生命周期要对齐。比如下单接口设了 300 秒幂等,但库存扣减是最终一致的,延迟 5 秒才落库,那第 298 秒来的重试请求可能查到“库存还够”,实际已扣完。这时候光靠缓存指纹不够,得结合数据库唯一索引兜底。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











