1、项目结构
2、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.javaboy</groupId>
<artifactId>org.javaboy.repeat_submit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>org.javaboy.repeat_submit</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
4、项目结构
4.1、RepeatSubmit(注解)
/**
* 描述: 定义注解防止表单重复提交
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {
/**
* 两个请求之间的间隔时间
* @return
*/
int interval() default 5000;
/**
* 重复提交时候的提示文本
* @return
*/
String message() default "不允许重复提交";
}
4.2 RepeatSubmitInterceptor(拦截器)
/**
* 描述:防止重复提交拦截器
*/
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
public static final String REPEAT_PARAMS = "repeat_params";
public static final String REPEAT_TIME = "repeat_time";
public static final String REPEAT_SUBMIT_KEY = "repeat_submit_key";
public static final String HEADER = "Authorization";
@Autowired
RedisCache redisCache;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//已经定义接口的方法的 封装成一个叫HandlerMethod
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
if (repeatSubmit != null) {
//判断是否重复提交
if (isRepeatSubmit(request,repeatSubmit)){
Map<String, Object> map = new HashMap<>();
map.put("status",500);
map.put("message",repeatSubmit.message());
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(map));
return false;
}
}
}
return true;
}
/**
* 判断是否重复提交,是就返回true ,表示是重复提交
* @param request
* @param repeatSubmit
* @return
*/
private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit repeatSubmit) {
//请求参数字符串
String nowParams = "";
if (request instanceof RepeatableReadRequestWrapper){
try {
nowParams = ((RepeatableReadRequestWrapper)request).getReader().readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
// 请求参数是key-value格式的
if (StringUtils.isEmpty(nowParams)){
try {
nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Map<String, Object> nowDataMap = new HashMap<>();
nowDataMap.put(REPEAT_PARAMS,nowParams);
nowDataMap.put(REPEAT_TIME,System.currentTimeMillis());
String requestURI = request.getRequestURI();
String header = request.getHeader(HEADER);
String cacheKey = REPEAT_SUBMIT_KEY + requestURI + header.replace("Bearer","");
Object cacheObject = redisCache.getCacheObject(cacheKey);
if (cacheObject != null){
Map<String,Object> map = (Map<String,Object>) cacheObject;
if (compareParams(map,nowDataMap) && compareTime(map,nowDataMap,repeatSubmit.interval())){
return true;
}
}
redisCache.setCacheObject(cacheKey,nowDataMap,repeatSubmit.interval(), TimeUnit.MILLISECONDS);
return false;
}
private boolean compareParams(Map<String,Object> map,Map<String,Object> nowDataMap){
String dataParams = (String) map.get(REPEAT_PARAMS);
String nowParams = (String) nowDataMap.get(REPEAT_PARAMS);
return nowParams.equals(dataParams);
}
private boolean compareTime(Map<String,Object> map ,Map<String,Object> nowDataMap,int inerval) {
Long time1 = (Long) map.get(REPEAT_TIME);
Long time2 = (Long) nowDataMap.get(REPEAT_TIME);
if ((time2-time1)<inerval){
return true;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
4.3 RepeatableRequestFilter(过滤器)
/**
* 描述:
*/
public class RepeatableRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (StringUtils.startsWithIgnoreCase(request.getContentType(),"application/json"))
{
RepeatableReadRequestWrapper requestWrapper = new RepeatableReadRequestWrapper(request, (HttpServletResponse) servletResponse);
filterChain.doFilter(requestWrapper,servletResponse);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
}
4.4 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
@Bean
FilterRegistrationBean<RepeatableRequestFilter> repeatableRequestFilterFilterRegistrationBean(){
FilterRegistrationBean<RepeatableRequestFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RepeatableRequestFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
4.5 RedisCache
@Component
public class RedisCache {
@Autowired
RedisTemplate redisTemplate;
public <T> void setCacheObject(final String key, final T value, Integer timeout , final TimeUnit timeUnit){
redisTemplate.opsForValue().set(key,value,timeout,timeUnit);
}
public <T> T getCacheObject(final String key){
ValueOperations<String,T> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
}
4.6 RepeatableReadRequestWrapper
/**
* 描述: 构建可反复读请求 、装饰者模式
*/
public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {
private final byte[] bytes;
public RepeatableReadRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
bytes = request.getReader().readLine().getBytes();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public int available() throws IOException {
return bytes.length;
}
};
}
}
4.7 HelloController
@RestController
public class HelloController {
@PostMapping("/hello")
@RepeatSubmit(interval = 15000)
public String hello(@RequestBody String json) {
return json;
}
}
5 测试结果