必须用服务端一次性表单令牌+数据库唯一约束双重保障:先在视图生成并存入session的随机token,提交时校验并立即清除;再通过UNIQUE索引拦截重复插入,确保刷新F5不导致订单重复或积分误发。

用户在Laravel中完成表单提交后跳转到新页面,再按F5刷新导致后端重复执行创建/更新逻辑,引发订单重复、积分重复发放等数据异常问题。
禁用按钮 + 前端加载态(基础防护)
这一步操作起来很简单,直接在提交瞬间冻结用户交互,防止手快连点或误刷新触发二次提交。
在表单提交事件中获取按钮元素,设置 disabled 属性并修改文字为“提交中”;请求完成后无论成功失败,都必须在 finally 中恢复按钮状态。
注意:该方法仅防误操作,无法拦截 Postman 或脚本重放请求。
服务端生成一次性表单令牌(核心保障)
这是真正能阻止刷新重发的关键措施,必须配合后端校验才能生效。
第一步:在控制器返回表单视图前生成随机 token 并存入 session:
使用 Str::random(40) 生成唯一字符串 → 调用 session(['form_token' => $token]) 写入当前会话。
第二步:在 Blade 表单中插入隐藏字段:
。
第三步:在表单处理逻辑开头立即校验并销毁:
检查 $request->form_token 是否与 session 中的值完全一致 → 若不匹配或 session 里已无该 key,则返回错误响应 → 校验通过后立刻执行 session()->forget('form_token')。
【form_token 必须在首次验证成功后立即清除,否则刷新页面仍可提交】
使用 redirect()->route() 替代 return route()
很多开发者在 destroy 或 store 方法末尾写 return route('xxx'),结果页面没跳转反而显示一串 URL 字符串——这是因为 route() 只生成路径,不触发 HTTP 重定向。
正确做法是调用 redirect()->route('xxx'),它会返回一个带 302 状态码和 Location 头的响应,浏览器收到后自动跳转,且刷新时只会重新请求目标页,不会重放原始 POST 请求。
若路由带参数(如 locale),需确保 compact 参数与路由定义中的占位符名称严格一致,否则抛出 RouteNotFoundException。
启用 throttle 中间件限制频率
方法一:针对提交路由添加限流中间件
在 routes/web.php 中为 POST 路由追加 ->middleware('throttle:2,1'),表示同一 IP 每分钟最多允许 2 次提交。
方法二:按用户身份限流(需登录)
自定义中间件,在 key 中拼接 Auth::id(),避免未登录用户共享限流计数器。
该方案对暴力刷接口有抑制作用,但不能替代幂等性设计。
数据库层增加唯一约束 + 乐观锁
在业务关键字段(如订单号、用户邮箱+时间戳组合)上添加 UNIQUE 索引,使重复插入直接报错而非静默成功。
对更新类操作,在 WHERE 条件中加入 version 字段比对,例如 WHERE id = ? AND version = ?;更新后 version 自增,下次再用旧 version 提交将无法命中记录。
【乐观锁必须配合 SELECT FOR UPDATE 或先查后更逻辑,否则并发下仍有竞态风险】


















