你有没有遇到过这样的情况?点一下“提交订单”,页面没反应,再点一次,结果收到两条支付通知;或者填完注册表单,手快连点两下,邮箱里冒出两个激活链接。这不是服务器抽风,是表单被重复提交了。
为啥会重复提交?
用户不是故意的——网络稍慢、按钮没反馈、页面卡顿、误触,都可能让人下意识多点几下。而默认情况下,浏览器可不管这些,只要收到点击,就照发不误。
前端:加个“锁”,先拦住第一波
最直接的办法,是让提交按钮点完就“变灰”且不可再点:
<form id="myForm">
<input type="text" name="username" required>
<button type="submit" id="submitBtn">提交</button>
</form>
<script>
document.getElementById('myForm').addEventListener('submit', function(e) {
const btn = document.getElementById('submitBtn');
btn.disabled = true;
btn.textContent = '提交中...';
});
</script>这个小动作成本低、见效快,但有个漏洞:用户刷新页面或重开标签页,照样能再提。所以它只是第一道防线,不能全靠它。
更稳一点:用一次性 Token
服务端每次生成一个随机 token(比如 UUID),塞进表单隐藏域,同时存到 session 或 Redis 里。提交时校验 token 是否存在且未使用过,验证通过就立即作废。
PHP 示例(简化版):
<?php
session_start();
$token = bin2hex(random_bytes(16));
$_SESSION['form_token'] = $token;
?>
<form method="post">
<input type="hidden" name="token" value="<?= $token ?>">
<input type="text" name="email">
<button type="submit">确认注册</button>
</form>接收时验证:
<?php
if ($_POST['token'] !== $_SESSION['form_token'] || empty($_SESSION['form_token'])) {
die('非法请求或已提交过');
}
// 处理业务逻辑...
unset($_SESSION['form_token']); // 立即清除
?>后端兜底:幂等性才是真保险
哪怕前端和 token 都失效了,后端也要扛住。核心思路是:同一笔操作,执行一次和执行十次,结果一样。
比如订单提交,别只看“用户点了提交”,而是查“这笔订单号是否已存在”。用数据库唯一索引约束是最简单有效的手段:
ALTER TABLE `orders` ADD UNIQUE KEY `uk_order_no` (`order_no`);插入前生成带业务含义的唯一单号(如时间戳+用户ID+随机数),插入失败就说明已存在,直接返回“订单已创建”。
顺手提醒几个细节
• 不要用 setTimeout 模拟防抖来替代禁用按钮——延迟期间用户仍可操作;
• AJAX 提交时,除了禁用按钮,还要在请求发出后置 loading 状态,成功/失败后再恢复;
• 移动端注意 touchend 和 click 的触发时机差异,建议统一监听 submit 事件而非按钮点击;
• 登录、支付、评论这类敏感操作,必须前后端配合,缺一不可。
防重复提交不是炫技,是把用户无心的手滑,变成系统无声的承接。