防止多次提交请求主要是为了避免用户由于不耐烦、误操作或者网络延迟等原因造成的重复提交,可能会导致数据的重复处理或者是非法状态。以下是一些常见的策略,以及它们实现的代码示例。
1. 使用客户端JavaScript防止重复点击
在Web应用中,可以通过JavaScript在第一次提交后禁用提交按钮,这样可以防止用户多次点击。
HTML + JavaScript 示例:
<!DOCTYPE html>
<html>
<head>
<script>
function submitForm() {
var btn = document.getElementById('submitBtn');
btn.disabled = true; // 禁用按钮
btn.value = '提交中...';
document.getElementById('myForm').submit(); // 提交表单
}
</script>
</head>
<body>
<form id="myForm" action="/submit" method="post">
<!-- 表单内容 -->
<input type="button" id="submitBtn" onclick="submitForm()" value="提交"/>
</form>
</body>
</html>
2. 使用Token防止重复提交
服务器端生成一个唯一的token,并在渲染表单时将其传递到客户端表单中。提交表单时,服务器检查token是否有效,并处理请求。处理后,token即被销毁或标记为已使用,使得相同的表单不能被再次提交。
Spring MVC + Thymeleaf 示例:
@Controller
public class MyController {
@GetMapping("/form")
public String getForm(Model model, HttpSession session) {
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("CSRF_TOKEN", csrfToken);
model.addAttribute("csrfToken", csrfToken);
return "form";
}
@PostMapping("/submit")
public String handleSubmit(@RequestParam("csrfToken") String csrfToken, HttpSession session) {
String sessionToken = (String) session.getAttribute("CSRF_TOKEN");
if (sessionToken == null || !sessionToken.equals(csrfToken)) {
return "error"; // token不匹配或不存在
}
session.removeAttribute("CSRF_TOKEN");
// 处理请求
return "success";
}
}
Thymeleaf表单示例:
<form action="#" th:action="@{/submit}" method="post">
<input type="hidden" name="csrfToken" th:value="${csrfToken}" />
<!-- 表单内容 -->
<button type="submit">提交</button>
</form>
3. 使用锁或同步机制
这个策略主要用于服务器端,并发环境中,通过锁或数据库状态避免重复处理。
伪代码实现:
public class OrderService {
private final Lock lock = new ReentrantLock();
public void processOrder(String orderId) {
if (!lock.tryLock()) {
throw new IllegalStateException("Order is already being processed.");
}
try {
// 检查订单状态,如果已处理则返回
if (orderAlreadyProcessed(orderId)) {
return;
}
// 处理订单
process(orderId);
// 标记订单为已处理
markOrderAsProcessed(orderId);
} finally {
lock.unlock();
}
}
}
4. 唯一性数据库约束
如果是插入数据到数据库,可以使用数据库的唯一性约束来防止重复记录的插入。
SQL 示例:
CREATE TABLE orders (
order_id INT PRIMARY KEY,
... -- 其他字段
);
-- 当尝试插入相同order_id的记录时,数据库将拒绝
5. 使用Redis等NoSQL数据库实现锁或去重机制
伪代码实现:
public class RedisOrderService {
private final StringRedisTemplate redisTemplate;
public void processOrder(String orderId) {
// 使用 Redis 的原子操作来检查和设置锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent("order_lock:" + orderId, "locked", 10, TimeUnit.SECONDS);
if (locked == null || !locked) {
throw new IllegalStateException("Order is already being processed.");
}
try {
// 处理订单
process(orderId);
} finally {
redisTemplate.delete("order_lock:" + orderId);
}
}
private void process(String orderId) {
// 实际的订单处理逻辑
}
}
这些方法可以单独使用,也可以结合使用,以确保在不同层次上防止重复提交。在设计一个防止请求多次提交的方案时,需要考虑到用户体验、系统的健壮性以及实现的复杂性。