必须前端锁UI、中止并发请求、后端幂等校验三者同时生效,漏一环即导致重复订单等故障;submit事件需批量禁用所有提交控件并配合checkValidity、preventDefault;fetch须用AbortController中止前序请求;submit_token须后端生成、绑定session、一次性消费且校验加原子锁。

单纯禁用按钮不能避免业务异常,必须前端锁 UI + 中止并发请求 + 后端幂等校验三者同时生效。漏掉任意一环,都可能造成订单重复创建、扣款两次、库存超卖等真实故障。
submit 事件里必须批量禁用所有 type="submit" 控件
只禁用当前点击的按钮或只写 document.getElementById('btn').disabled = true 是无效的。用户按回车、点“保存并继续”、甚至用 JS 调用 form.requestSubmit() 都会绕过这个限制。
- 正确做法是:在
form.addEventListener('submit', ...)的第一行,用form.querySelectorAll('button[type="submit"], input[type="submit"]')获取全部提交控件,统一设disabled = true - 禁用前建议调用
form.checkValidity(),校验失败时不锁按钮,避免用户卡死在错误表单上 - 禁用后必须配
event.preventDefault(),否则禁用动作还没生效,表单已发出原生请求 - 不要用内联
onclick="this.disabled=true"—— 它不响应回车,也不管其他按钮,且破坏可维护性
fetch 提交时必须用 AbortController 中止前序请求
禁用按钮只拦住 UI 层点击,但 fetch 发起后网络延迟导致多次请求已发出,按钮灰了后端还在收包。这是高频重复提交最隐蔽的来源。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
- 声明一个全局变量
currentController,类型为AbortController - 每次提交前先执行
currentController?.abort(),再新建实例并传入signal选项 -
catch块中必须过滤AbortError,否则会把中止当业务错误上报 - IE 不支持
AbortController,老项目需降级为Promise.race([fetch(), timeoutPromise])
后端 submit_token 必须绑定 session 且一次性消费
前端任何控制都可被绕过,真正兜底的是服务端 token 校验。用 Math.random() 或时间戳生成的隐藏字段值毫无防护力。
立即学习“前端免费学习笔记(深入)”;
- token 必须由后端生成,带签名、有时效(5–15 分钟),且绑定当前
session_id和表单类型(如order_form) - 提交时校验通过后,必须立即在服务端标记为“已使用”,再次提交返回
400 Bad Request - 若用 fetch 提交,需手动取
document.querySelector('[name="submit_token"]').value拼进 body,不能依赖表单自动携带 - 别信“前端禁用+token”就万事大吉 —— 若 token 校验逻辑没加锁(如 Redis
SETNX或 DB 行锁),高并发下仍可能穿透
最容易被忽略的是:abort 后没恢复按钮、token 校验没加原子操作、以及 PRG 模式下重定向前业务未真正落库。这三处任一出错,都会让整套防重机制形同虚设。


















