表单防重复提交需前后端协同:前端提交时禁用按钮并提示,后端用一次性Token校验、数据库唯一约束或乐观锁兜底,AJAX场景还需loading状态管理与防抖。

表单提交后立即禁用提交按钮
用户点击提交后若不阻止二次点击,浏览器会发出多个请求,后端哪怕做了校验也已产生冗余压力。最直接的前端防护就是用 JavaScript 在 submit 事件中禁用按钮,并改写文案提示“提交中…”。
注意:仅靠前端禁用不可靠,必须配合后端措施;但不做这一步,用户体感差、重复提交概率陡增。
- 用
document.querySelector('form').addEventListener('submit', ...)绑定,避免重复绑定 - 禁用前先检查
button.disabled,防止多次触发时重复操作 - 不要用
return false或event.preventDefault()单独拦截——它可能绕过表单验证逻辑
服务端用一次性 Token(Token + Session)校验
这是防重复提交最常用且有效的后端方案:session_start() 后生成唯一 $_SESSION['form_token'],输出到表单隐藏域;提交时比对并立即销毁该 token。
关键点在于「一次性」——比对成功即 unset($_SESSION['form_token']),下次再提就会因 token 为空或不匹配而拒绝。
立即学习“PHP免费学习笔记(深入)”;
- token 建议用
bin2hex(random_bytes(16))生成,避免可预测性 - 不要把 token 存在 Cookie 或 URL 中,防止被重放或泄露
- 若用户刷新页面,需重新生成 token 并更新表单,否则刷新后提交必失败
- 注意 session 生命周期,超时后 token 失效应友好提示“请重新加载页面”而非 500 错误
数据库层加唯一约束或乐观锁
当业务本身有天然唯一标识(如订单号、支付流水号、用户+时间戳组合),直接在数据库加 UNIQUE 索引是最省力的兜底手段。插入失败时捕获 SQLSTATE[23000] 类错误即可返回“操作已存在”。
不适合所有场景(比如纯留言表无业务唯一键),但对订单、注册、投票等强一致性操作非常有效。
- MySQL 报错示例:
Integrity constraint violation: 1062 Duplicate entry 'xxx' for key 'uk_order_no' - PHP 中用
try...catch捕获PDOException,判断$e->getCode() === '23000' - 乐观锁适合更新场景:
UPDATE table SET status=1, version=version+1 WHERE id=? AND version=?,影响行为 0 行即说明已被他人更新
AJAX 提交配合 loading 状态与防抖
如果表单走 AJAX(如用 fetch 或 axios),除了禁用按钮,还需管理请求状态。单纯禁用按钮在请求卡住或超时后无法恢复,用户可能强行刷新再点——这时需要更细粒度控制。
推荐做法是维护一个 isSubmitting 标志位,并在请求完成(无论 success/fail)后重置;同时对连续快速点击做简单防抖(如 500ms 内只认第一次)。
- 避免在
then()和catch()里分别重置按钮状态,统一放在finally块 - 防抖不是必须,但能减少网络异常时的误操作;可用
setTimeout+clearTimeout实现 - 不要依赖前端 timestamp 或随机数做“去重 ID”,服务端无感知,不可靠











