前端防止用戶重復(fù)提交表單的4種可靠方案(附防坑指南)
別再只靠“禁用按鈕”了!真正的防重提交,需要前后端協(xié)同。
在電商下單、用戶注冊(cè)、支付發(fā)起等關(guān)鍵場(chǎng)景中,用戶連點(diǎn)多次“提交”按鈕是再常見(jiàn)不過(guò)的行為。
輕則造成數(shù)據(jù)庫(kù)寫(xiě)入多條重復(fù)記錄,重則導(dǎo)致用戶被扣款兩次、庫(kù)存超賣(mài)——這絕不是危言聳聽(tīng)。
那么,前端該如何有效防止重復(fù)提交?
本文將從用戶體驗(yàn)和系統(tǒng)可靠性兩個(gè)維度,為你梳理 4 種主流方案,并告訴你:為什么“禁用按鈕”遠(yuǎn)遠(yuǎn)不夠?
方案一:提交時(shí)禁用按鈕(基礎(chǔ)但必要)
最直觀的做法:點(diǎn)擊后立即禁用提交按鈕。
const submitBtn = document.getElementById('submit-btn');
const form = document.getElementById('my-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
if (submitBtn.disabled) return; // 防止多次觸發(fā)
submitBtn.disabled = true;
submitBtn.textContent = '提交中...';
fetch('/api/submit', { method: 'POST', body: new FormData(form) })
.then(res => res.json())
.then(data => {
alert('提交成功!');
})
.catch(err => {
alert('提交失敗,請(qǐng)重試');
})
.finally(() => {
submitBtn.disabled = false;
submitBtn.textContent = '提交';
});
});
優(yōu)點(diǎn):簡(jiǎn)單、直觀、提升 UX。
致命缺陷:
- 用戶刷新頁(yè)面后狀態(tài)丟失;
- 無(wú)法阻止通過(guò)腳本、Postman 等繞過(guò) UI 的重復(fù)請(qǐng)求;
- 僅靠前端,防不住!
結(jié)論:這是必備的第一道防線,但絕不能是唯一防線。
方案二:使用防重 Token(推薦!前后端協(xié)同)
這才是企業(yè)級(jí)應(yīng)用的標(biāo)準(zhǔn)做法。
原理:
- 頁(yè)面加載時(shí),后端生成一個(gè)一次性 token(如 UUID),并存入 Session 或 Redis;
- 前端在表單中攜帶該 token 提交;
- 后端收到請(qǐng)求后:
- 檢查 token 是否存在且未使用;
- 若有效,則標(biāo)記為“已使用”并處理業(yè)務(wù);
- 若無(wú)效或已用過(guò),直接拒絕。
<!-- 表單中隱藏 token --> <input type="hidden" name="antiReplayToken" value="a1b2c3d4-5678-90ef..." />
// 提交時(shí)無(wú)需額外處理,token 隨表單自動(dòng)發(fā)送
優(yōu)勢(shì):
- 即使用戶刷新、多開(kāi)標(biāo)簽頁(yè),每個(gè) token 只能用一次;
- 能防御腳本刷接口、自動(dòng)化工具攻擊;
- 與業(yè)務(wù)解耦,通用性強(qiáng)。
注意:
- Token 必須有時(shí)效性(如 5 分鐘過(guò)期);
- 必須由后端生成和校驗(yàn),前端不可偽造。
方案三:前端加鎖 + 請(qǐng)求去重(適用于 API 場(chǎng)景)
如果你調(diào)用的是無(wú)表單的 API(如點(diǎn)擊“領(lǐng)取優(yōu)惠券”按鈕),可用“請(qǐng)求指紋”去重。
const pendingRequests = new Set();
function requestWithDedup(key, apiCall) {
if (pendingRequests.has(key)) {
console.log('請(qǐng)求正在進(jìn)行,忽略重復(fù)');
return Promise.reject('Duplicate request');
}
pendingRequests.add(key);
return apiCall().finally(() => {
pendingRequests.delete(key);
});
}
// 使用示例
document.getElementById('claim-btn').addEventListener('click', () => {
const userId = 'user_123';
requestWithDedup(`claim_${userId}`, () =>
fetch('/api/claim-coupon', { method: 'POST' })
);
});
適用場(chǎng)景:
- 按鈕觸發(fā)的獨(dú)立操作(非完整表單);
- 需要防止同一用戶短時(shí)間內(nèi)多次觸發(fā)同一操作。
方案四:結(jié)合 loading 狀態(tài) + 全局?jǐn)r截(提升體驗(yàn))
在大型應(yīng)用中,可借助狀態(tài)管理(如 Redux、Pinia)或 Axios 攔截器統(tǒng)一處理。
// Axios 示例
let isSubmitting = false;
axios.interceptors.request.use(config => {
if (config.url === '/api/submit-order') {
if (isSubmitting) throw new Error('請(qǐng)勿重復(fù)提交');
isSubmitting = true;
}
return config;
});
axios.interceptors.response.use(
res => {
if (res.config.url === '/api/submit-order') isSubmitting = false;
return res;
},
err => {
if (err.config?.url === '/api/submit-order') isSubmitting = false;
return Promise.reject(err);
}
);
這種方式適合 SPA 應(yīng)用,能覆蓋所有相關(guān)請(qǐng)求。
最終建議:分層防御,才是王道
| 層級(jí) | 措施 | 作用 |
|---|---|---|
| 前端 UX 層 | 禁用按鈕 + loading 提示 | 阻止普通用戶誤操作 |
| 前端邏輯層 | 請(qǐng)求去重 / 狀態(tài)鎖 | 防止快速連點(diǎn) |
| 后端安全層 | 防重 Token / 冪等設(shè)計(jì) | 根本性防御重復(fù)提交 |
| 數(shù)據(jù)庫(kù)層 | 唯一索引(如訂單號(hào)) | 最后一道保險(xiǎn) |
記?。?strong>前端可以被繞過(guò),后端必須守住底線。
結(jié)語(yǔ)
防止重復(fù)提交,不是“加個(gè) disabled 就完事”,而是一套縱深防御體系。
從用戶體驗(yàn)到系統(tǒng)安全,每一步都不可或缺。
下次當(dāng)你看到“提交中…”的按鈕時(shí),不妨想想:你的系統(tǒng),真的扛得住用戶狂點(diǎn)十次嗎?
到此這篇關(guān)于前端防止用戶重復(fù)提交表單的4種可靠方案的文章就介紹到這了,更多相關(guān)前端防止用戶重復(fù)提交表單內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
layui lay-verify form表單自定義驗(yàn)證規(guī)則詳解
今天小編就為大家分享一篇layui lay-verify form表單自定義驗(yàn)證規(guī)則詳解,具有很好的參考價(jià)值,相信我對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
JavaScript獲取標(biāo)簽的幾種方式小結(jié)
本文主要介紹了JavaScript獲取標(biāo)簽的幾種方式小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
JavaScript中的宏任務(wù)和微任務(wù)執(zhí)行順序
在?JavaScript?中,宏任務(wù)和微任務(wù)是指在執(zhí)行代碼的過(guò)程中的兩種不同的任務(wù)類型,這篇文章主要介紹了JavaScript中的宏任務(wù)和微任務(wù)執(zhí)行順序,需要的朋友可以參考下2022-12-12
layui button 按鈕彈出提示窗口,確定才進(jìn)行的方法
今天小編就為大家分享一篇layui button 按鈕彈出提示窗口,確定才進(jìn)行的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
JS實(shí)現(xiàn)圖片延遲加載并淡入淡出效果的簡(jiǎn)單方法
我們大家都知道,對(duì)于一個(gè)網(wǎng)站最占用帶寬,最影響頁(yè)面顯示速度的東西就是圖片。圖片是很重要的,作為一個(gè)站長(zhǎng)我們是千方百計(jì)的使用各種技巧來(lái)優(yōu)化圖片,但其實(shí)有一種簡(jiǎn)單的方法,只需要幾行代碼就能達(dá)到這種效果。同時(shí)還附加一種淡入淡出的顯示效果,下面一起來(lái)看看。2016-08-08
淺析$(function) ready和onload 的區(qū)別
新手剛學(xué)習(xí)js和jq的時(shí)候難免會(huì)接觸題目所標(biāo)識(shí)的相關(guān)內(nèi)容,下面小編通過(guò)本教程給大家講解(function) ready和onload 的區(qū)別,感興趣的朋友一起看看吧2016-09-09
javascript框架設(shè)計(jì)讀書(shū)筆記之?dāng)?shù)組的擴(kuò)展與修復(fù)
本文是司徒正美的《javascript框架設(shè)計(jì)》的第三章第2節(jié)的讀書(shū)筆記,本節(jié)主要介紹的是javascript數(shù)組的擴(kuò)展與修復(fù),本文則是選取了其中的重點(diǎn)部分展示給大家。2014-12-12

