• Springboot-实现防重复提交功能


    Springboot-实现防重复提交功能

    基于若依分离版防重复提交 进行适配开发

    前提知识

    防重复提交功能 基于 request 完成防重复提交功能,但HttpServletRequest 一旦获取参数,请求中将不会存储参数信息,故需要开发可重复获取参数的请求对象。然后再springboot提供的拦截器中进行判断,实现防重复提交.

    如果对原生的request进行包装,哪个时机进行包装?

    这是需要引入过滤器,因过滤器的执行顺序在拦截器的前面,并且过滤器只能拿到请求对象和响应对象,所以在过滤器中可以很好的完成我们的需求

    1.开发可重复读取inputStream的request

    /**
     * 构建可重复读取inputStream的request
     */
    public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
        /**
         * UTF-8 字符集
         */
        public static final String UTF8 = "UTF-8";
    
        private final byte[] body;
    
        public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
            super(request);
            request.setCharacterEncoding(UTF8);
            response.setCharacterEncoding(UTF8);
    
            body = HttpUtil.getBodyString(request).getBytes(UTF8);
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @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) {
    
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    2.实现请求包装

    实现Filter接口

    /**
     * Repeatable 过滤器  实现可重复读参数请求
     */
    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) {
                requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
            }
            if (requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(requestWrapper, response);
            }
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3.开发FilterConfig

    /**
     * @author zhangrl
     * @description 过滤器相关配置类
     * @date 2022/11/16 13:30
     */
    @Configuration
    public class FilterConfig {
    
        /**
         * 主要作用:实现重复读取参数的request
         * @return  FilterRegistrationBean
         */
        @Bean
        public FilterRegistrationBean filterRegistrationBean(){
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter(new RepeatableFilter());
            registrationBean.addUrlPatterns("/*");
            registrationBean.setName("repeatableFilter");
            registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
            return registrationBean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4.创建自定义注解类

    /**
     * 防重复提交注解
     */
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit {
        /**
         * 间隔时间(ms),小于此时间视为重复提交
         */
        public int interval() default 5000;
    
        /**
         * 提示消息
         */
        public String message() default "不允许重复提交,请稍候再试";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5.开发拦截器

    /**
     * @author rikylinz
     * @description 防重复提交
     * @date 2022/11/15 14:35
     */
    @Component
    public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
    
        @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 annotation = method.getAnnotation(RepeatSubmit.class);
                if (annotation != null) {
                    if (this.isRepeatSubmit(request, annotation))
                    {
                        CommonResult commonResult = new CommonResult();
                        commonResult.setCode(503);
                        commonResult.setMessage(annotation.message());
                        this.result(HttpUtil.getResponse(), JSON.toJSONString(commonResult));
                        return false;
                    }
                }
                return true;
            } else {
                return true;
            }
        }
    
        /**
         * 验证是否重复提交由子类实现具体的防重复提交的规则,待实现
         *
         * @param request
         * @return
         * @throws Exception
         */
        public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
    
        /**
         * 被拦截后返回给调用者的信息
         * @param response  响应
         * @param res   返回信息
         */
        private void result(HttpServletResponse response, String res){
            PrintWriter writer = null;
            try {
                response.setStatus(503);
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.setCharacterEncoding(Consts.UTF_8.toString());
                writer = response.getWriter();
                writer.write(res);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                writer.flush();
                writer.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    6.开发防重复提交规则

    /**
     * @author rikylinz
     * @description 判断请求url和数据是否和上一次相同,如果和上次相同,则是重复提交表单。 有效时间为5秒内。
     * @date 2022/11/15 11:21
     */
    @Slf4j
    @Component
    public class RepeatSubmitRule extends RepeatSubmitInterceptor {
    
        public final String REPEAT_PARAMS = "repeatParams";
    
        public final String REPEAT_TIME = "repeatTime";
    
        /**
         * 防重提交 redis key
         */
        public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
            UserPo user = ThreadLocalUtil.getUser();
            String mobile = user.getMobile();
    
            String nowParams = "";
            if (request instanceof RepeatedlyRequestWrapper) {
                RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
                //获取body中的数据方法下面会有展示
                nowParams = HttpUtil.getBodyString(repeatedlyRequest);
            }else{
                //获取body中的数据方法下面会有展示
                nowParams = HttpUtil.getBodyString(request);
            }
    
            // body参数为空,获取Parameter的数据
            if (StringUtils.isEmpty(nowParams))
            {
                nowParams = JSON.toJSONString(request.getParameterMap());
            }
            Map<String, Object> nowDataMap = new HashMap<>();
            nowDataMap.put(REPEAT_PARAMS, nowParams);
            nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
    
            // 请求地址(作为存放cache的key值)
            String url = request.getRequestURI();
    
            // 唯一标识(指定key + url + 手机号)
            String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + mobile;
    
            Map<String, Object> cacheMap = new HashMap<>();
            cacheMap.put(url, nowDataMap);
            //原子操作  添加  返回结果   true 添加成功(redis不存在)  false 添加失败
            Boolean isExist = null;
            try {
                isExist = redisUtil.setStringByAtomicity(cacheRepeatKey, JSON.toJSONString(cacheMap),
                        Integer.valueOf(annotation.interval()).longValue(), TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                log.error("[校验防重复提交]原子性添加数据失败,原因:{}",e);
                return false;
            }
    
            if (Boolean.FALSE.equals(isExist))
            {
                String sessionObj = null;
                try {
                    sessionObj = redisUtil.getString(cacheRepeatKey);
                } catch (Exception e) {
                    log.error("[校验防重复提交]redis获取数据失败,原因:{}",e);
                    return false;
                }
                JSONObject sessionMap = JSONObject.parseObject(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;
                    }
                }
            }
            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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    RedisUtil.java

    	/**
         * string类型-获取value
         * @param key   key
         * @return      value
         * @throws Exception
         */
        public String getString(String key) throws Exception{
            return stringRedisTemplate.opsForValue().get(key);
        }
    
    	/**
         * string类型-设置key-value 原子性操作
         * @param key           key
         * @param value         value
         * @throws Exception
         */
        public Boolean setStringByAtomicity(String key,String value,Long timeout,TimeUnit timeUnit) throws Exception{
            return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    HttpUtil.java

    	/**
         * 获取body消息体中的参数列表
         * @param request   请求对象
         * @return
         */
        public static String getBodyString(HttpServletRequest request)
        {
            StringBuilder sb = new StringBuilder();
            BufferedReader reader = null;
            try (InputStream inputStream = request.getInputStream())
            {
                reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                String line = "";
                while ((line = reader.readLine()) != null)
                {
                    sb.append(line);
                }
            }
            catch (IOException e)
            {
                LOGGER.warn("getBodyString出现问题!");
            }
            finally
            {
                if (reader != null)
                {
                    try
                    {
                        reader.close();
                    }
                    catch (IOException e)
                    {
                        LOGGER.error(ExceptionUtils.getMessage(e));
                    }
                }
            }
            return sb.toString();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    爬虫笔记15——爬取网页数据并使用redis数据库set类型去重存入,以爬取芒果踢V为例
    Baklib帮助中心:自助服务指南
    BI业务分析思维:供应链生产制造策略(一) “推式” 、 “拉式”
    物理学专业英语(写作整理)04 物理中常用的空间表示
    SEO外语网站批量翻译软件
    C# LINQ常用操作方法——提升你的编程效率
    国际结算模拟试题及答案
    LeetCode二叉树系列——226.翻转二叉树
    机器学习(python)笔记整理
    无人机航拍技术基础入门,无人机拍摄的方法与技巧
  • 原文地址:https://blog.csdn.net/zhangruilinmoo/article/details/127887127