必须用限流+幂等双重机制拦截重复请求:先通过ratelimiter控制频次,再用idempotency-key(前端哈希/后端令牌/数据库唯一索引)校验,最后在事务中完成状态标记与业务写入。

当用户在电商下单或支付时因网络延迟反复点击提交按钮,或Nginx因超时自动重试请求,导致同一笔订单创建三次、余额被扣三次——这类数据错乱必须用限流+幂等双重机制拦截,不能只靠前端禁用按钮或单层校验。
第一步:用RateLimiter中间件做请求频率压制
在app/Http/Kernel.php的$middlewareGroups['api']中追加throttle:3,1,表示每分钟最多允许3次请求;若需按用户粒度限流,改用throttle:3,1,by=ip(按IP)或throttle:3,1,by=id(需配合Auth::id())。
这一步仅控制请求频次,【不解决同一请求重复执行的问题】,比如用户第1秒发了3次完全相同的支付请求,仍会全部进入控制器逻辑。
第二步:为关键接口生成并校验幂等键(Idempotency Key)
方法一:前端生成稳定指纹
在提交前用JavaScript对关键字段哈希:const key = sha256(userId + amount + orderId + timestamp).substring(0, 16);将key作为请求头Idempotency-Key发送。
方法二:后端生成一次性令牌
控制器中调用 $token = Str::uuid()->toString() → 存入Redis:Cache::put('idempotency:'.$token, ['status' => 'pending'], 300) → 将token嵌入表单隐藏域或返回给前端用于后续请求。
方法三:数据库唯一索引兜底
在orders表添加联合唯一索引:UNIQUE KEY `user_id_payment_id_unique` (`user_id`, `payment_id`);payment_id字段由前端传入或服务端生成,确保业务维度唯一。
第三步:事务内完成幂等状态标记与业务写入
第一步:开启DB事务 → DB::beginTransaction()
第二步:查询Redis中该Idempotency-Key是否存在且状态为pending;若存在,直接跳过业务逻辑,进入第四步。
第三步:若Key不存在,先用Cache::add('idempotency:'.$key, ['status' => 'processing'], 300)抢占写入权;失败则说明已被其他并发请求抢先处理,直接返回409 Conflict。
第四步:执行核心业务逻辑(如扣减库存、创建订单)→ 若成功,更新Redis状态为completed;若失败,更新为failed并记录错误日志。
第五步:事务提交 → DB::commit();【注意:Redis状态更新必须在DB事务提交之后,否则可能产生状态与数据不一致】











