自定义注解 RepeatSubmit , 用于防止表单重复提交

package com.goodsoft.shrk.common.annotation;import java.lang.annotation.*;/*** @author YXY* 自定义注解防止表单重复提交** @Inherited* 1.类继承关系中@Inherited的作用:类继承关系中,子类会继承父类使用的注解中被@Inherited修饰的注解* 2.接口继承关系中@Inherited的作用:接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰* 3.类实现接口关系中@Inherited的作用:类实现接口时不会继承任何接口中定义的注解* @Documented* 标注生成javadoc的时候是否会被记录。*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{}

使用方式:
在需要拦截的接口上添加 @RepeatSubmit() 注解

自定义拦截器

package com.goodsoft.shrk.common.interceptor;import com.alibaba.fastjson.JSONObject;
import com.goodsoft.shrk.common.annotation.RepeatSubmit;
import com.goodsoft.shrk.common.dto.ReturnData;
import com.goodsoft.shrk.common.util.ServletUtils;
//import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.web.method.HandlerMethod;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;/*** @author YXY* 防止重复提交拦截器*/
@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
{/*** preHandle方法都会在Controller方法调用之前执行,当preHandler中返回值为false时整个请求结束* postHandle方法 在请求完成后执行,会在DispatcherServlet进行视图的渲染之前执行* afterCompletion方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图执行* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{if (handler instanceof HandlerMethod){// HandlerMethod 封装 方法参数,方法返回值和方法注释HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);if (annotation != null){if (this.isRepeatSubmit(request)){ReturnData result = ReturnData.error("不允许重复提交,请稍后再试");//将字符串渲染到客户端ServletUtils.renderString(response, JSONObject.toJSONString(result));return false;}}return true;}else{return super.preHandle(request, response, handler);}}/*** 验证是否重复提交由子类实现具体的防重复提交的规则* @param request* @return* @throws Exception*/public abstract boolean isRepeatSubmit(HttpServletRequest request);
}

这里有个小问题,就是HandlerMethod的导包问题,
如果导入的是import org.springframework.messaging.handler.HandlerMethod; 这个包下的,
会导致 handler instanceof HandlerMethod 一直为false ,
应该导入:import org.springframework.web.method.HandlerMethod;
这里的 ReturnData 是我们项目封装的统一返回数据结果,需要替换为自己项目的

package com.goodsoft.shrk.common.interceptor;import com.alibaba.fastjson.JSONObject;
import com.goodsoft.shrk.common.constant.Constants;
import com.goodsoft.shrk.common.util.HttpHelper;
import com.goodsoft.shrk.common.util.StringUtils;
import com.goodsoft.shrk.component.cache.CommonCache;
import com.goodsoft.shrk.component.filter.RepeatedlyRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @author YXY* 判断请求url和数据是否和上一次相同,* 如果和上次相同,则是重复提交表单。 有效时间为10秒内*/
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{public final String REPEAT_PARAMS = "repeatParams";public final String REPEAT_TIME = "repeatTime";@Autowiredprivate CommonCache commonCache;/*** 间隔时间,单位:秒 默认10秒** 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据*/private int intervalTime = 10;public void setIntervalTime(int intervalTime){this.intervalTime = intervalTime;}/*** @SuppressWarnings("unchecked"): 告诉编译器忽略 unchecked 警告信息* @param request* @return*/@SuppressWarnings("unchecked")@Overridepublic boolean isRepeatSubmit(HttpServletRequest request){String nowParams = "";if (request instanceof RepeatedlyRequestWrapper){RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;nowParams = HttpHelper.getBodyString(repeatedlyRequest);}// body参数为空,获取Parameter的数据if (StringUtils.isEmpty(nowParams)){nowParams = JSONObject.toJSONString(request.getParameterMap());}Map<String, Object> nowDataMap = new HashMap<String, Object>();nowDataMap.put(REPEAT_PARAMS, nowParams);nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());// 请求地址(作为存放cache的key值)String url = request.getRequestURI();// 唯一值(没有消息头则使用请求地址)String submitKey = request.getHeader( "token");if (StringUtils.isEmpty(submitKey)){submitKey = url;}// 唯一标识(指定key + 消息头)String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;Object sessionObj = commonCache.getVal(cacheRepeatKey);if (sessionObj != null){Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;if (sessionMap.containsKey(url)){Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)){return true;}}}Map<String, Object> cacheMap = new HashMap<String, Object>();cacheMap.put(url, nowDataMap);commonCache.set(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);return false;}/*** 判断参数是否相同*/private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap){String nowParams = (String) nowMap.get(REPEAT_PARAMS);String preParams = (String) preMap.get(REPEAT_PARAMS);return nowParams.equals(preParams);}/*** 判断两次间隔时间*/private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap){long time1 = (Long) nowMap.get(REPEAT_TIME);long time2 = (Long) preMap.get(REPEAT_TIME);if ((time1 - time2) < (this.intervalTime * 1000)){return true;}return false;}
}

CommonCache 是操作redis缓存用 ,这里替换成自己系统中的操作redis存取方法即可

commonCache.getVal()
commonCache.set()
   /*** 根据key获取缓存中的val** @param key 键 不能为null* @return 缓存中的值*/public <T> T getVal(String key) {return (T) redisTemplate.opsForValue().get(key);}/*** 缓存放入并设置过期时间** @param key   键* @param value 值* @param time  时间(毫秒) time要大于0 如果time小于等于0 将设置无限期* @param unit  默认毫秒* @return true成功 false 失败*/public boolean set(String key, Object value, long time, TimeUnit unit) {try {if (time > 0 && unit != null) {redisTemplate.opsForValue().set(key, value, time, unit);return true;}} catch (Exception e) {LOG.error("redis缓存放入并设置过期时间", e);}return false;}

HttpHelper工具类

package com.goodsoft.shrk.common.util;import javax.servlet.ServletRequest;
import java.io.*;
import java.nio.charset.Charset;/***  @author YXY*/
public class HttpHelper {/** * 获取post请求中的Body * * @param request * @return */public static String getBodyString(ServletRequest request) {StringBuilder sb = new StringBuilder();InputStream inputStream = null;BufferedReader reader = null;try {inputStream = request.getInputStream();//读取流并将流写出去,避免数据流中断;reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));String line = "";while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return sb.toString();}//添加自定义的信息到请求体中;public static String appendCustomMsgToReqBody(String newReqBodyStr) {StringBuilder sb = new StringBuilder();InputStream inputStream = null;BufferedReader reader = null;String newReqBody = null;try {//通过字符串构造输入流;inputStream = String2InputStream(newReqBodyStr);reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));String line = "";while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}//返回字符串;newReqBody = sb.toString();return newReqBody;}//将字符串转化为输入流;public static InputStream String2InputStream(String str) {ByteArrayInputStream stream = null;try {stream = new ByteArrayInputStream(str.getBytes("UTF-8"));} catch (UnsupportedEncodingException e) {e.printStackTrace();}return stream;}
}

Constants 常量

package com.goodsoft.shrk.common.constant;/*** 通用常量信息* * @author ruoyi*/
public class Constants
{/*** UTF-8 字符集*/public static final String UTF8 = "UTF-8";/*** GBK 字符集*/public static final String GBK = "GBK";/*** 防重提交 redis key*/public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";/*** 限流 redis key*/public static final  String RATE_LIMIT_KEY = "rate_limit:";}

RepeatedlyRequestWrapper过滤器

package com.goodsoft.shrk.component.filter;import cn.hutool.core.io.IoUtil;
import com.goodsoft.shrk.common.constant.Constants;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;/*** 构建可重复读取inputStream的request** @author ruoyi*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {super(request);request.setCharacterEncoding(Constants.UTF8);response.setCharacterEncoding(Constants.UTF8);body = IoUtil.readUtf8(request.getInputStream()).getBytes(StandardCharsets.UTF_8);}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic int available() throws IOException {return body.length;}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}
}

这里需要导入 hutool 工具包

        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.6</version></dependency>

注册RepeatSubmitInterceptor拦截器,需要继承WebMvcConfigurerAdapter
需要重写addInterceptors方法,这里是对根目录"/"进行拦截,可以指定拦截url请求目录

package com.goodsoft.shrk.common.config;import com.goodsoft.shrk.common.interceptor.RepeatSubmitInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {@AutowiredRepeatSubmitInterceptor repeatSubmitInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");}
}

Java自定义拦截器处理防抖相关推荐

  1. java interceptor用法_java自定义拦截器用法实例

    本文实例讲述了java自定义拦截器及其用法.分享给大家供大家参考.具体如下: LoginInterceptor.java文件如下: package com.tq365.util; import jav ...

  2. Hadoop生态圈-Flume的组件之自定义拦截器(interceptor)

    Hadoop生态圈-Flume的组件之自定义拦截器(interceptor) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客只是举例了一个自定义拦截器的方法,测试字节传输速 ...

  3. Struts2自定义类型转换器、自定义拦截器和用户输入数据的验证

    一.自定义类型转换器 1.编写一个类,继承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter 2.覆盖掉其中的public Obj ...

  4. WebServices中使用cxf开发日志拦截器以及自定义拦截器

    首先下载一个cxf实例,里面包含cxf的jar包.我下的是apache-cxf-2.5.9 1.为什么要设置拦截器? 为了在webservice请求过程中,能动态操作请求和响应数据, CXF设计了拦截 ...

  5. SpringBoot中自定义拦截器

    场景 自定义拦截器,通过继承WebMvcConfigureAdapter然后重写父类中的方法进行扩展. 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZH ...

  6. Struts2 自定义拦截器(方法拦截器)

    转自:http://05061107cm.iteye.com/blog/365504 struts2系统自带了很多拦截器,有时需要我们自己定义,一般有两种方式: 一.实现Interceptor接口 J ...

  7. Struts自定义拦截器拦截器工作原理

    0.拦截器的调用原理: 拦截器是一个继承了序列化接口的普通接口.其工作原理是讲需要被拦截的对象作为参数传到intercept()方法内,在方法内部对此对象进行处理之后再执行原方法.intercept( ...

  8. 从struts2拦截器到自定义拦截器

    http://www.cnblogs.com/withyou/p/3170440.html 拦截器可谓struts2的核心了,最基本的bean的注入就是通过默认的拦截器实现的,一般在struts2.x ...

  9. SpringMVC自定义拦截器与异常处理(自定义异常)

    SpringMVC自定义拦截器与异常处理 拦截器概念 拦截器代码演示 创建maven工程 pom.xml <?xml version="1.0" encoding=" ...

最新文章

  1. 设置commit 提交模板
  2. perfect book
  3. 智能车竞赛车模轮子与电机齿轮的参数
  4. 压缩感知的阶段性总结
  5. 返回txt格式的文本使用编码 js_Node.js学习笔记第一天
  6. Lambda表达式和表达式树
  7. ubuntu16.04安装cuda8./9.
  8. 机器学习算法—集成算法原理详解
  9. linux上ln命令详细说明
  10. ORB-SLAM3 代码解读
  11. 顶部固定不动 下部分滚动_彻底解决背景跟随弹窗滚动问题
  12. zabbix 利用脚本发邮件(mail)
  13. 无线联网常见问题[1]-搜不到无线网络(请先耐心看完)
  14. [除一波线段树和平衡树的草]
  15. 小米4充不了电处理办法(图文)小米4不能充电的维修教程
  16. 集运转运系统源码,快递物流一件代付系统源码
  17. Win11安装Ubuntu子系统报错WslRegisterDistribution failed with error: 0x800701bc
  18. 2022年P气瓶充装考试试题及答案
  19. leaflet的引入
  20. [转]Visual C++ 和 C++ 有什么区别?

热门文章

  1. iOS-汤姆猫项目总结
  2. Cowardly refusing to save to a terminal. Use the -o flag or redirect
  3. python locust 时间戳过期_Locust源码分析之runners.py模块(6)
  4. 3ds Max 子物体的编辑
  5. (C语言详解)05-树9 Huffman Codes(详细解析)
  6. 网课学习第二天(第三天?)
  7. Linux虚拟机上离线安装ansible awx_亲测成功
  8. 小旋风蜘蛛池站群X5+五套mip模板
  9. 超51亿的移动用户,TensorFlow移动端方兴未艾
  10. 复合函数求导经典例题_复合函数求导练习试题.doc