在日常开发过程中,通常会遇到前端或接口的重复提交问题。本文介绍了什么是重复提交问题,并提供了几种防止重复提交的方式。
什么是重复提交问题?重复提交问题是指当用户在短时间内多次提交同一个请求时,服务器可能会处理多次请求,导致结果出现异常。例如用户在提交表单之后,突然网络卡顿,然后迅速重试提交,此时服务器可能会认为该请求是多次请求,导致数据出现问题。为了避免这种情况的发生,需要对请求进行防重复提交处理。以下是几种解决方案:
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 在缓存中已经存在,则说明此请求已经被处理过,返回重复提交结果。
以上几种方法都可以防止数据重复提交,需要根据具体情况选择合适的方法。可能写的有点乱,大家了解一下就好。
除此之外,还可以使用定时器、拦截器等技术来解决前端重复提交的问题。