cache::add()是laravel防重复提交最轻量可靠的首道防线,因其原子性可避免并发漏判,而cache::has()+cache::put()非原子操作易致双写;指纹需含ip、完整url及请求体哈希,锁适用于业务逻辑互斥,数据库唯一索引仍是最终兜底。

直接用 Cache::add() 做请求指纹校验,是 Laravel 防重复提交最轻量、最可靠的第一道防线。它不依赖 session 写入时机,不触发数据库查询,5ms 内完成判断,且天然支持 Redis 集群下的原子性。
为什么不能用 Cache::has() + Cache::put()
这是最常踩的坑:两个并发请求几乎同时执行 Cache::has($key),都返回 false,接着都执行 Cache::put($key, true, 60),结果双写成功,漏判。
-
Cache::add()是原子操作:只在 key 不存在时写入并返回true;否则返回false,这才是“抢锁”语义 - 必须用
add(),不能拆成两步 - 如果用了 Redis 集群,确保
$fingerprintkey 的哈希落在同一 slot(比如加 `{}` 包裹业务标识:{user_123}_form_submit)
Cache::lock() 适合什么场景
当你要保护的是「一段业务逻辑的执行过程」而非「单次请求唯一性」时,比如扣库存、生成订单号、写文件等需要互斥执行的操作,Cache::lock() 更合适。
- 锁 key 必须带业务上下文,例如
'order_create_' . $userId或'stock_deduct_' . $productId - 超时时间设 3–5 秒足够,太长会阻塞后续合法请求
- 务必调用
->block(3)并检查返回值:if (! $lock->block(3)) { abort(425); },否则默认不阻塞,直接失败 - 别在锁内做耗时操作(如调第三方 API),锁持有时间越短越好
指纹怎么拼才不会误杀或漏判
指纹不是越长越安全,而是要「对同一语义请求完全一致,对不同意图请求严格区分」。
- 必须包含:
request()->ip()(防 NAT 下泛化)、request()->fullUrl()(含 query)、md5(request()->getContent())(POST/PUT body) - GET 请求无 content,fallback 到
md5(json_encode(request()->query())) - 避免用
time()或microtime()—— 它会让相同请求每次指纹都不同 - 分页参数(如
page=1vspage=2)应算不同指纹,否则列表刷新会被拦 - 敏感操作建议额外加入用户标识:
auth()->id() ?? 'guest_' . request()->ip()
为什么数据库唯一索引仍是最终兜底
缓存可能丢失、过期、跨集群不一致;Redis 故障时,Cache::add() 直接降级为失效。所以关键表字段必须加唯一约束。
- 订单表加
(user_id, idempotency_key)联合唯一索引 - 前端生成
idempotency_key:用 SHA-256 哈希用户 ID + 表单摘要(非随机数),保证可复现 - 后端捕获
Illuminate\Database\QueryException,检查错误码是否为 MySQL 的1062或 PG 的23505 - 捕获到冲突后,查库确认记录是否存在且状态合法,再决定返回原数据还是报错
真正难的不是写几行 Cache::add(),而是在分布式、高并发、缓存抖动、网络重试共存的环境下,让幂等边界清晰、降级路径明确、错误反馈可追溯。指纹 key 的构造方式、锁的粒度选择、唯一索引的字段组合——这些细节一旦松动,就容易在大促或故障时突然暴露。











