文章目录
前言
在Web开发中,防止用户重复提交表单是一个常见的需求。用户可能会因为网络延迟、误操作等原因多次点击提交按钮,导致后台接收到多个相同的请求。这不仅会浪费服务器资源,还可能导致数据不一致等问题。本文将介绍几种在Spring Boot中实现接口防重复提交的方法。
使用Token机制
Token机制是一种常见的防重复提交方法。具体步骤如下:
生成Token:用户每次请求表单页面时,服务器生成一个唯一的Token,并将其存储在Session中。
传递Token:将Token嵌入到表单中,随表单一起提交。
验证Token:服务器接收到请求后,首先验证Token是否有效,如果有效则继续处理请求,并从Session中移除该Token;如果无效,则返回错误信息。
实现步骤
1.生成Token
在Controller中生成Token并存储在Session中:
/**
* form
* @param session
* @author senfel
* @date 2024/11/12 11:29
* @return org.springframework.web.servlet.ModelAndView
*/
@GetMapping("/form")
public ModelAndView showForm(HttpSession session) {
ModelAndView form = new ModelAndView("form");
String token = UUID.randomUUID().toString();
session.setAttribute("token", token);
form.addObject("token", token);
return form;
}
2.传递Token
在表单中添加隐藏字段来传递Token:
<form action="/base/submit" method="post">
<input type="hidden" name="token" th:value="${token}">
<!-- 其他表单字段 -->
<button type="submit">Submit</button>
</form>
3.验证Token
在Controller中验证Token:
/**
* handleForm
* @param token
* @param session
* @author senfel
* @date 2024/11/12 11:34
* @return java.lang.String
*/
@PostMapping("/submit")
public String handleForm(@RequestParam String token, HttpSession session) {
String sessionToken = (String) session.getAttribute("token");
if (sessionToken == null || !sessionToken.equals(token)) {
throw new RuntimeException("Duplicate submit detected");
}
// 移除Token
session.removeAttribute("token");
// 处理表单数据
return "success";
}
使用Redis
Redis是一个高性能的键值存储系统,可以用来存储和验证Token。具体步骤如下:
生成Token:用户每次请求表单页面时,服务器生成一个唯一的Token,并将其存储在Redis中。
传递Token:将Token嵌入到表单中,随表单一起提交。
验证Token:服务器接收到请求后,首先验证Token是否存在于Redis中,如果存在则继续处理请求,并从Redis中删除该Token;如果不存在,则返回错误信息。
实现步骤
1.引入Redis依赖
在 pom.xml 中添加Redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.生成Token
在Controller中生成Token并存储在Redis中:
@Autowired
private StringRedisTemplate redisTemplate;
/**
* formByRedis
* @author senfel
* @date 2024/11/12 11:50
* @return org.springframework.web.servlet.ModelAndView
*/
@GetMapping("/formByRedis")
public ModelAndView showFormByRedis() {
ModelAndView form = new ModelAndView("form");
String token = UUID.randomUUID().toString();
// 设置过期时间
redisTemplate.opsForValue().set(token, token, 5, TimeUnit.MINUTES);
form.addObject("token", token);
return form;
}
3.传递Token
在表单中添加隐藏字段来传递Token:
<form action="/base/submitByRedis" method="post">
<input type="hidden" name="token" th:value="${token}">
<!-- 其他表单字段 -->
<button type="submit">Submit</button>
</form>
4.验证Token
在Controller中验证Token:
/**
* submitByRedis
* @param token
* @author senfel
* @date 2024/11/12 11:50
* @return java.lang.String
*/
@PostMapping("/submitByRedis")
public String handleFormByRedis(@RequestParam String token) {
String redisToken = redisTemplate.opsForValue().get(token);
if (redisToken == null) {
throw new RuntimeException("Duplicate submit detected");
}
// 删除Token
redisTemplate.delete(token);
// 处理表单数据
return "success";
}
使用Spring AOP
Spring AOP(Aspect-Oriented Programming)可以用来实现切面编程,从而在多个方法中复用防重复提交的逻辑。
实现步骤
1.定义注解
创建一个自定义注解 @PreventDuplicateSubmit:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* PreventDuplicateSubmit
* @author senfel
* @date 2024/11/12 11:56
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicateSubmit {
/**重复请求时间*/
int expireSeconds() default 10;
}
2.创建切面
创建一个切面类 DuplicateSubmitAspect:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* DuplicateSubmitAspect
* @author senfel
* @version 1.0
* @date 2024/11/12 11:57
*/
@Slf4j
@Aspect
@Component
public class DuplicateSubmitAspect {
protected static final Logger logger = LoggerFactory.getLogger(DuplicateSubmitAspect.class);
@Autowired
private StringRedisTemplate redisTemplate;
/**
* around
* @param joinPoint
* @author senfel
* @date 2024/11/12 15:45
* @return java.lang.Object
*/
@Around("@annotation(com.example.ccedemo.aop.PreventDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
StringBuilder key = new StringBuilder();
//获取class
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
key.append(simpleName);
// 获取请求方法
MethodSignature signature=(MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
key.append(":").append(methodName);
//获取请求参数
Object[] args=joinPoint.getArgs();
for (Object arg : args) {
key.append(":").append(arg.toString());
}
//TODO 获取客户端IP
// 获取注解信息
PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
// 判断是否已经请求过
if(redisTemplate.hasKey(key.toString())){
throw new RuntimeException("请勿重复提交");
}
//标记请求已经处理过
redisTemplate.opsForValue().set(key.toString(),"1",annotation.expireSeconds(), TimeUnit.SECONDS);
return joinPoint.proceed();
}
}
3.使用注解
在Controller方法上使用 @PreventDuplicateSubmit 注解:
/**
* handleFormByAnnotation
* @param param
* @author senfel
* @date 2024/11/12 11:59
* @return java.lang.String
*/
@PostMapping("/submitByAnnotation")
@PreventDuplicateSubmit
public String handleFormByAnnotation(@RequestParam String param) {
// 处理表单数据
return "success";
}
总结
本文介绍了三种在Spring Boot中实现接口防重复提交的方法:使用Token机制、使用Redis和使用Spring AOP。每种方法都有其适用场景和优缺点,可以根据实际需求选择合适的方法。通过这些方法,可以有效防止用户重复提交表单,提高系统的稳定性和用户体验。