在日常開發過程中,通常會遇到前端或接口的重複送出問題。本文介紹了什麼是重複送出問題,并提供了幾種防止重複送出的方式。
什麼是重複送出問題?重複送出問題是指當使用者在短時間内多次送出同一個請求時,伺服器可能會處理多次請求,導緻結果出現異常。例如使用者在送出表單之後,突然網絡卡頓,然後迅速重試送出,此時伺服器可能會認為該請求是多次請求,導緻資料出現問題。為了避免這種情況的發生,需要對請求進行防重複送出處理。以下是幾種解決方案:
1. 禁用按鈕
這種方法相對而言比較簡單,隻需要在表單送出後禁用送出按鈕即可,這樣就可以避免使用者多次點選送出按鈕。
let button = document.getElementById('submitButton');
button.addEventListener('click', function() {
// 禁用送出按鈕,防止重複送出
button.disabled = true;
// 發送表單請求
fetch('/submitForm', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
// request data
})
}).then(response => {
if (response.ok) {
// 成功送出表單,可以進行其他操作,如頁面跳轉
} else {
// 送出表單失敗,可以進行其他操作,如提示錯誤資訊
}
// 啟用送出按鈕
button.disabled = false;
}).catch(error => {
console.error('請求錯誤:', error);
// 啟用送出按鈕
button.disabled = false;
});
});
在上面的代碼中,當使用者點選送出按鈕時,按鈕将被禁用,防止重複送出表單。當表單請求完成後,按鈕将被重新啟用。如果請求成功,則可以進行其他操作,如頁面跳轉等;如果請求失敗,則可以提示錯誤資訊等。
2. 前端錯開請求時間
這種方法的思路是在每次請求之間添加一定的時間間隔,可以使用 JavaScript 的 setTimeout() 方法實作:
var isSubmitting = false;
function submitForm() {
if (!isSubmitting) {
isSubmitting = true;
setTimeout(function() {
// 送出表單
isSubmitting = false;
}, 1000);
}
}
也可以使用以下方式:
<form onsubmit="return checkForm(this);">
<input type="hidden" name="timestamp" value="<%=System.currentTimeMillis()%>">
...
<button type="submit">送出</button>
</form>
<script>
function checkForm(form) {
var timestamp = form.timestamp.value;
var current = new Date().getTime();
if (current - timestamp < 5000) { // 限制送出時間為 5 秒
alert("請勿重複送出");
return false;
}
return true;
}
</script>
3. 限制請求次數
這種方法的思路是限制每個使用者在一定時間内可以送出的次數,可以在後端記錄使用者每次請求的時間,并在較短的時間内拒絕過多的請求。
private static Map<String, List<Long>> requestHistory = new HashMap<>();
private static final long REQUEST_INTERVAL = 1000; // 1s 内僅能請求一次
private static final int REQUEST_LIMIT = 10; // 10 秒内最多請求 10 次
@PostMapping("/submitForm")
public String submitForm(@RequestParam String name, HttpSession session) {
String sessionId = session.getId();
List<Long> history = requestHistory.getOrDefault(sessionId, new ArrayList<>());
long currentTimeMillis = System.currentTimeMillis();
int recentRequestsCount =
(int) history.stream().filter(time -> currentTimeMillis - time < REQUEST_INTERVAL).count();
if (recentRequestsCount >= REQUEST_LIMIT) {
// 達到請求限制
return "rate_limit";
} else {
// 處理請求
history.add(currentTimeMillis);
requestHistory.put(sessionId, history);
return "success";
}
}
4. 接口幂等性
接口幂等性是指對于一個接口,無論調用多少次,其結果都是一樣的,不會因為調用的次數的不同而産生不同的結果。
舉個例子,比如說銀行轉賬接口,如果你調用一次将100元轉賬到某個賬戶,那麼第二次調用就會傳回錯誤,因為轉賬已經完成了,再次調用就沒有任何意義了。這就是幂等性的應用,保證接口在調用時的結果是一緻的。
下面是一個使用 HTTP POST 請求建立資源并實作接口幂等性的代碼示例。
import requests
def create_user(name: str, email: str) -> None:
data = {
"name": name,
"email": email,
}
headers = {
"Content-Type": "application/json",
"Idempotency-Key": "unique-id-for-each-request",
}
url = "http://example.com/users/"
response = requests.post(url, headers=headers, json=data)
if response.status_code in (200, 201):
print("User created successfully.")
else:
print("Failed to create user.")
在上面的示例中, Idempotency-Key 是一個唯一辨別符,用于做接口幂等性的實作。用戶端發送請求時,需要在請求頭中加入這個辨別符。服務端會将這個辨別符與接口的請求參數一起記錄,如果用戶端重複發送相同的請求,服務端會判斷 Idempotency-Key 是否已經存在,如果已經存在,則直接傳回之前的結果,否則執行請求并将結果記錄下來,保證接口的結果始終是一緻的,不會産生重複資料或錯誤的結果。
5.通過緩存和唯一辨別
類似于token校驗機制。示例如下:
@PostMapping("/submitForm")
public Result submitForm(@RequestBody Form form, HttpServletRequest request) {
// 擷取請求的唯一辨別符
String uuid = request.getHeader("uuid");
// 判斷是否已經處理過此請求
if (redisTemplate.opsForValue().setIfAbsent(uuid, "lock")) {
// 設定此請求的過期時間為5秒
redisTemplate.expire(uuid, 5, TimeUnit.SECONDS);
// 處理表單請求
return formService.submit(form);
} else {
// 請求已經處理過,傳回重複送出結果
return Result.error("您已經送出過該請求,請不要重複送出!");
}
}
在上面的代碼中,當用戶端發送表單請求時,首先從請求頭中擷取唯一辨別符 uuid。如果目前請求的 uuid 在緩存中不存在,則将這個 uuid 存儲到緩存中,并設定過期時間為 5 秒。然後處理表單請求并傳回處理結果。如果目前請求的 uuid 在緩存中已經存在,則說明此請求已經被處理過,傳回重複送出結果。
以上幾種方法都可以防止資料重複送出,需要根據具體情況選擇合适的方法。可能寫的有點亂,大家了解一下就好。
除此之外,還可以使用定時器、攔截器等技術來解決前端重複送出的問題。