java springboot 防重复操作
时间: 2025-06-09 11:23:57 浏览: 13
### 如何在Spring Boot中防止重复操作
在Spring Boot应用程序中,防止重复提交或接口重复调用是一个常见的需求。以下是一些最佳实践和方法,可以帮助开发者实现这一目标。
#### 1. 使用Token机制防止表单重复提交
Token机制是一种有效的方法,用于防止用户在浏览器中多次提交相同的表单。当用户访问表单页面时,服务器生成一个唯一的Token并将其存储在会话中或数据库中。同时,该Token也被嵌入到表单中作为隐藏字段。当用户提交表单时,服务器验证提交的Token是否与存储的Token匹配,并在成功处理后删除或标记该Token为已使用[^5]。
```java
@Controller
public class FormController {
@GetMapping("/form")
public String showForm(Model model, HttpSession session) {
String token = UUID.randomUUID().toString();
session.setAttribute("token", token);
model.addAttribute("token", token);
return "form";
}
@PostMapping("/submit")
public String handleForm(@RequestParam String token, HttpSession session) {
String sessionToken = (String) session.getAttribute("token");
if (sessionToken != null && sessionToken.equals(token)) {
session.removeAttribute("token"); // 防止重复提交
return "success";
} else {
return "error";
}
}
}
```
#### 2. 使用分布式锁(如Redis)防止接口重复调用
对于分布式系统,可以使用Redis等工具来实现分布式锁,以防止接口被重复调用。每次请求到达时,服务器会在Redis中设置一个唯一键值对,键为用户ID或其他唯一标识符,值为当前时间戳。如果键已经存在,则拒绝处理该请求[^6]。
```java
@Service
public class RedisLockService {
private final StringRedisTemplate redisTemplate;
public RedisLockService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean acquireLock(String key, long timeout) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "LOCKED", Duration.ofSeconds(timeout));
return result != null && result;
}
public void releaseLock(String key) {
redisTemplate.delete(key);
}
}
@RestController
public class ApiController {
private final RedisLockService redisLockService;
public ApiController(RedisLockService redisLockService) {
this.redisLockService = redisLockService;
}
@PostMapping("/api/endpoint")
public ResponseEntity<String> handleRequest(@RequestParam String userId) {
String lockKey = "lock:" + userId;
if (redisLockService.acquireLock(lockKey, 10)) { // 设置10秒超时
try {
// 处理业务逻辑
return ResponseEntity.ok("Success");
} finally {
redisLockService.releaseLock(lockKey); // 释放锁
}
} else {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("Duplicate request detected");
}
}
}
```
#### 3. 使用Spring AOP拦截重复请求
通过Spring AOP,可以在方法执行前检查是否已经存在相同的请求。例如,可以通过注解方式定义一个自定义注解`@PreventDuplicate`,并在切面中实现逻辑来拦截重复请求[^7]。
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicate {
String key() default "";
int seconds() default 10;
}
@Aspect
@Component
public class DuplicateRequestAspect {
private final StringRedisTemplate redisTemplate;
public DuplicateRequestAspect(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Around("@annotation(preventDuplicate)")
public Object preventDuplicate(ProceedingJoinPoint joinPoint, PreventDuplicate preventDuplicate) throws Throwable {
String key = preventDuplicate.key();
if (key.isEmpty()) {
key = Arrays.toString(joinPoint.getArgs());
}
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "LOCKED", Duration.ofSeconds(preventDuplicate.seconds()));
if (result != null && result) {
try {
return joinPoint.proceed();
} finally {
redisTemplate.delete(key);
}
} else {
throw new RuntimeException("Duplicate request detected");
}
}
}
```
#### 4. 使用幂等性设计接口
幂等性是指同一个操作无论执行多少次,其结果都是一致的。为了实现接口的幂等性,通常需要在数据库中记录操作的状态,并在后续请求中检查该状态。例如,在支付系统中,可以使用订单号作为唯一标识符,确保每个订单只被处理一次[^8]。
```java
@PostMapping("/api/pay")
public ResponseEntity<String> payOrder(@RequestParam String orderId) {
Optional<Order> orderOptional = orderRepository.findById(orderId);
if (orderOptional.isPresent() && orderOptional.get().getStatus() == OrderStatus.PENDING) {
// 执行支付逻辑
orderOptional.get().setStatus(OrderStatus.COMPLETED);
orderRepository.save(orderOptional.get());
return ResponseEntity.ok("Payment successful");
} else {
return ResponseEntity.badRequest().body("Order already processed or invalid");
}
}
```
阅读全文
相关推荐


















