我的编程空间,编程开发者的网络收藏夹
学习永远不晚

若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor

摘要

若依(Ruoyi)是一款基于Spring Boot和MyBatis的开源后台管理系统,它提供了一系列的拦截器(Interceptor)用于处理请求。其中,RepeatSubmitInterceptor(重复提交拦截器)是若依系统中的一个关键拦截器,用于防止用户重复提交表单请求。

在Web应用程序中,用户可能会重复提交表单,例如在点击提交按钮后多次点击或者网络延迟造成用户误以为提交未成功而再次提交。这可能导致一些问题,例如重复的数据插入或重复的业务逻辑处理。

RepeatSubmitInterceptor 的主要作用是在用户提交表单请求时,对请求进行拦截和处理,防止重复提交。判断该url是否有RepeatSubmit注解,如果有的话,就里面取到了:【参数,url,用户】然后和RepeatSubmit里的过期时间一起放到了redis。

下次再来的时候会去redis里面去查询,如果已经过期没有,那么通过,然后再重新放入redis.如果还有,那么就不通过。

但这里有个问题,如果参数是从body里面去取来的,那么流会只读一次就再读不到了。于是在此之前使用了RepeatableFilter做了关于这个的封装的字节数组Requestwrapper。

配置拦截器:WebMvcConfigurer

WebMvcConfigurer配置RepeatSubmitInterceptor :
在继承自WebMvcConfigurer的ResourcesConfig会加入RepeatSubmitInterceptor ,RepeatSubmitInterceptor 最主要的方法:this.isRepeatSubmit(request, annotation)是由继承自ResourcesConfig 的SameUrlDataInterceptor 实现的。

@Configurationpublic class ResourcesConfig implements WebMvcConfigurer{    @Autowired    private RepeatSubmitInterceptor repeatSubmitInterceptor;        @Override    public void addInterceptors(InterceptorRegistry registry)    {        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("    public int interval() default 5000;        public String message() default "不允许重复提交,请稍候再试";}

拦截器具体实现:RepeatSubmitInterceptor和SameUrlDataInterceptor

preHandle:在请求处理之前进行拦截处理。

它会从请求中提取出重复提交所需的标识,并进行重复提交的检查。如果检查到重复提交,可以返回错误信息或者采取其他处理方式。

@Componentpublic abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter{    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception    {        if (handler instanceof HandlerMethod)        {            HandlerMethod handlerMethod = (HandlerMethod) handler;            Method method = handlerMethod.getMethod();            //只有标注了@RepeatSubmit 注解才需要防止表单重复提交,其他的请求直接返回 true。            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);            if (annotation != null)            {                if (this.isRepeatSubmit(request, annotation))                {                    AjaxResult ajaxResult = AjaxResult.error(annotation.message());                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));                    return false;                }            }            return true;        }        else        {            return super.preHandle(request, response, handler);        }    }        public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);}

此处的核心是:

//只有标注了@RepeatSubmit 注解才需要防止表单重复提交,其他的请求直接返回 true。RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);

验证是否重复提交由子类实现具体的防重复提交的规则

RepeatSubmitInterceptor 最主要的方法:this.isRepeatSubmit(request, annotation)是由继承自ResourcesConfig 的SameUrlDataInterceptor 实现的。
判断该url是否有RepeatSubmit注解,如果有的话,就里面取到了:【参数,url,用户】然后和RepeatSubmit里的过期时间一起放到了redis

@Componentpublic class SameUrlDataInterceptor extends RepeatSubmitInterceptor{    public final String REPEAT_PARAMS = "repeatParams";    public final String REPEAT_TIME = "repeatTime";    // 令牌自定义标识    @Value("${token.header}")    private String header;    @Autowired    private RedisCache redisCache;    @SuppressWarnings("unchecked")    @Override    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)    {        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(header);        if (StringUtils.isEmpty(submitKey))        {            submitKey = url;        }        // 唯一标识(指定key + 消息头)        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;        Object sessionObj = redisCache.getCacheObject(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, annotation.interval()))                {                    return true;                }            }        }        Map<String, Object> cacheMap = new HashMap<String, Object>();        cacheMap.put(url, nowDataMap);        redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);        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, int interval)    {        long time1 = (Long) nowMap.get(REPEAT_TIME);        long time2 = (Long) preMap.get(REPEAT_TIME);        if ((time1 - time2) < interval)        {            return true;        }        return false;    }}

解决参数读取问题:HttpServletRequest和RepeatedlyRequestWrapper

在项目中经常出现多次读取HTTP请求体的情况,这时候可能就会报错,原因是读取HTTP请求体的操作,最终都要调用HttpServletRequest的getInputStream()方法和getReader()方法,而这两个方法总共只能被调用一次,第二次调用就会报错。

RepeatableFilter用RepeatedlyRequestWrapper 包装了HttpServletRequest。将HttpServletRequest的字节流的数据,保存到一个变量中,重写getInputStream()方法和getReader()方法,从变量中读取数据,返回给调用者。

public class RepeatableFilter implements Filter{    @Override    public void init(FilterConfig filterConfig) throws ServletException    {    }    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)            throws IOException, ServletException    {        ServletRequest requestWrapper = null;        if (request instanceof HttpServletRequest                && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))        {        //包装了HttpServletRequest            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);        }        if (null == requestWrapper)        {            chain.doFilter(request, response);        }        else        {            chain.doFilter(requestWrapper, response);        }    }    @Override    public void destroy()    {    }}
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper{    private final byte[] body;    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException    {        super(request);        request.setCharacterEncoding("UTF-8");        response.setCharacterEncoding("UTF-8");        body = HttpHelper.getBodyString(request).getBytes("UTF-8");    }    @Override    public BufferedReader getReader() throws IOException    {        return new BufferedReader(new InputStreamReader(getInputStream()));    }// 重写了,核心:final ByteArrayInputStream bais = new ByteArrayInputStream(body);    @Override    public ServletInputStream getInputStream() throws IOException    {        final ByteArrayInputStream bais = new ByteArrayInputStream(body);        return new ServletInputStream()        {            @Override            public int read() throws IOException            {                return bais.read();            }            @Override            public int available() throws IOException            {                return body.length;            }            @Override            public boolean isFinished()            {                return false;            }            @Override            public boolean isReady()            {                return false;            }            @Override            public void setReadListener(ReadListener readListener)            {            }        };    }}

来源地址:https://blog.csdn.net/qq_27575627/article/details/130731877

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor

文章目录 摘要配置拦截器:WebMvcConfigurerRepeatSubmit注解拦截器具体实现:RepeatSubmitInterceptor和SameUrlDataInterceptorpreHandle:在请求处理之前进行
若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor
2023-12-23

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录