laravel防重复提交有五种方法:一、csrf加一次性表单令牌;二、throttle中间件限流;三、前端禁用按钮并加加载态;四、数据库唯一约束+乐观锁;五、redis原子操作实现分布式防重。

当用户在Laravel应用中快速多次点击提交按钮,或因网络延迟导致页面未及时响应而重复提交表单时,可能引发数据重复插入、余额重复扣减等安全风险。以下是几种可立即实施的防护方法:
一、使用CSRF令牌与一次性表单令牌结合
CSRF令牌默认存在于Laravel表单中,但其本身不防止重复提交;需配合服务端生成的一次性令牌(One-Time Token)实现提交后即失效机制。该令牌在表单渲染时写入session,并在首次验证成功后立即销毁。
1、在控制器中生成并存入session:
$token = Str::random(40);<br> session(['form_token' => $token]);
2、在Blade模板表单中添加隐藏字段:<input type="hidden" name="form_token" value="{{ session('form_token') }}">
3、在表单处理逻辑开头校验并清除:
if (!session()->has('form_token') || $request->form_token !== session('form_token')) {<br>
return back()->withErrors(['message' => '无效或已使用的表单令牌']);<br>
}<br>
session()->forget('form_token');
二、启用Laravel内置的throttle中间件限制提交频率
通过IP地址与路由组合进行请求频次控制,适用于登录、注册、评论等易受刷量攻击的表单入口,可在不修改业务逻辑的前提下快速生效。
1、在routes/web.php中为表单提交路由添加中间件:Route::post('/submit', [FormController::class, 'store'])->middleware('throttle:3,1');
2、该配置表示同一IP地址每分钟最多允许3次提交,超出则返回429状态码。
3、如需按用户身份限流,可自定义中间件并使用Auth::id()作为key前缀。
三、前端禁用提交按钮并添加加载状态
在客户端层面阻止用户二次点击,是第一道轻量级防线。需配合后端验证,不可单独依赖。
1、在表单提交事件中获取按钮元素:const submitBtn = document.querySelector('button[type="submit"]');
2、添加点击事件监听并禁用按钮:
submitBtn.addEventListener('click', function(e) {<br>
if (this.disabled) return;<br>
this.disabled = true;<br>
this.textContent = '提交中...';<br>
});
3、确保表单提交失败后恢复按钮状态:
document.querySelector('form').addEventListener('submit', function(e) {<br>
const btn = e.target.querySelector('button[type="submit"]');<br>
btn.disabled = false;<br>
btn.textContent = '提交';<br>
});
四、使用数据库唯一约束配合乐观锁检测
当业务场景允许时,在数据库层面设置联合唯一索引(如user_id + action_type + date),使重复提交触发唯一键冲突,再由应用捕获异常并返回友好提示。
1、在迁移文件中添加唯一索引:$table->unique(['user_id', 'action_type', 'created_at_date']);
2、在模型中定义日期字段的虚拟列(MySQL 5.7+)或使用DB::raw生成当日日期:
3、在控制器中捕获异常:
try {<br>
Transaction::create($data);<br>
} catch (\Illuminate\Database\QueryException $e) {<br>
if ($e->getCode() === '23000') {<br>
return back()->withErrors(['message' => '您已在此时段完成该操作']);<br>
}<br>
}
五、采用Redis原子操作实现分布式表单防重
在高并发多实例部署环境下,利用Redis的SETNX命令确保同一用户对同一业务动作的提交请求仅有一个能获得执行许可,其余被拒绝。
1、构造唯一key:$key = 'form_lock:'.auth()->id().':'.$request->input('action');
2、尝试获取锁(过期时间设为30秒):$locked = Redis::setex($key, 30, time());
3、若未获取到锁,立即终止流程:
if (!$locked) {<br>
return response()->json(['error' => '操作过于频繁,请稍后再试'], 429);<br>
}
4、关键操作完成后无需手动释放,依靠Redis自动过期即可;避免因异常未释放导致死锁。










