• springboot自定义注解防止表单重复提交


    功能实现:使用了自定义注解、自定义拦截器、redis缓存等知识点。

    1.注解类

    package com.ahzx.custcent.config.interceptor;
    
    import java.lang.annotation.*;
    
    /**
     * 自定义注解类
     */
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.防止重复提交拦截器

    package com.ahzx.custcent.config.interceptor;
    import com.ahzx.common.domain.AjaxResult;
    import com.ahzx.custcent.util.ServletUtils;
    import com.alibaba.fastjson.JSONObject;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.lang.reflect.Method;
    
    /**
     * 防止重复提交拦截器
     */
    @Component
    public 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 annotation = method.getAnnotation(RepeatSubmit.class);
                if (annotation != null) {
                    if (this.isRepeatSubmit(request)) {
                        AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
                        ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
                        return false;
                    }
                }
                return true;
            } else {
                return super.preHandle(request, response, handler);
            }
        }
    
        /**
         * 验证是否重复提交由子类实现具体的防重复提交的规则
         * 
         * @param request
         * @return
         * @throws Exception
         */
        public abstract boolean isRepeatSubmit(HttpServletRequest request);
    }
    
    
    • 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

    3.具体拦截逻辑类

    package com.ahzx.custcent.config.interceptor;
    import cn.hutool.core.util.StrUtil;
    import com.ahzx.common.consts.Constants;
    import com.ahzx.common.filter.RepeatedlyRequestWrapper;
    import com.ahzx.common.service.RedisCache;
    import com.ahzx.custcent.util.HttpHelper;
    import com.ahzx.custcent.util.StringUtils;
    import com.alibaba.fastjson.JSONObject;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 判断请求url和数据是否和上一次相同,
     * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
     *
     * @author ruoyi
     */
    @Component
    public 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;
    
        /**
         * 间隔时间,单位:秒 默认10秒
         * 

    * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据 */ private int intervalTime = 10; public void setIntervalTime(int intervalTime) { this.intervalTime = intervalTime; } @SuppressWarnings("unchecked") @Override public boolean isRepeatSubmit(HttpServletRequest request) { String nowParams = ""; if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; nowParams = HttpHelper.getBodyString(repeatedlyRequest); } // body参数为空,获取Parameter的数据 if (StrUtil.isBlank(nowParams)) { nowParams = JSONObject.toJSONString(request.getParameterMap()); } Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 请求地址(作为存放cache的key值) String url = request.getRequestURI(); // 唯一值(没有消息头则使用请求地址) String submitKey = request.getHeader(header); if (StrUtil.isBlank(submitKey)) { submitKey = url; } // 唯一标识(指定key + 消息头) String cache_repeat_key = "repeat_submit:" + submitKey; Object sessionObj = redisCache.getCacheObject(cache_repeat_key); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; if (sessionMap.containsKey(url)) { Map preDataMap = (Map) sessionMap.get(url); if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) { return true; } } } Map cacheMap = new HashMap(); cacheMap.put(url, nowDataMap); redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS); return false; } /** * 判断参数是否相同 */ private boolean compareParams(Map nowMap, Map preMap) { String nowParams = (String) nowMap.get(REPEAT_PARAMS); String preParams = (String) preMap.get(REPEAT_PARAMS); return nowParams.equals(preParams); } /** * 判断两次间隔时间 */ private boolean compareTime(Map nowMap, Map 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; } }

    • 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
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    4.添加自定义拦截器类(必须有这个拦截才会生效)

    package com.ahzx.custcent.config;
    
    
    import com.ahzx.custcent.config.interceptor.JwtInterceptor;
    import com.ahzx.custcent.config.interceptor.RepeatSubmitInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebAppConfig implements WebMvcConfigurer {
    
        @Autowired
        private JwtInterceptor jwtInterceptor;
        @Autowired
        private CorsInterceptor corsInterceptor;
    
        @Autowired
        private RepeatSubmitInterceptor repeatSubmitInterceptor;
    
        @Bean
        InterceptorRegistry interceptorRegistry() {
            return new InterceptorRegistry();
        }
    
        @Autowired
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
            registry.addInterceptor(jwtInterceptor).addPathPatterns("/**");
            registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
        }
    }
    
    
    • 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

    5.过滤器(构建可重复读取inputStream的request)

    package com.ahzx.common.filter;
    
    
    import com.ahzx.custcent.util.HttpHelper;
    
    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;
    
    /**
     * 构建可重复读取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("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()));
        }
    
        @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 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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    ======================== 以下是上述主要逻辑的配套工具类 ========================

    6.配套工具类,上面代码封装的一些方法可以在这里面找

    6.1 ServletUtils

    package com.ahzx.custcent.util;
    
    import cn.hutool.core.convert.Convert;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    
    /**
     * 客户端工具类
     * 
     * @author
     */
    public class ServletUtils
    {
        /**
         * 将字符串渲染到客户端
         * 
         * @param response 渲染对象
         * @param string 待渲染的字符串
         * @return null
         */
        public static String renderString(HttpServletResponse response, String string)
        {
            try
            {
                response.setStatus(200);
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print(string);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    
    • 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

    6.2 HttpHelper

    package com.ahzx.custcent.util;
    
    import org.apache.commons.lang3.exception.ExceptionUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.ServletRequest;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    
    /**
     * 通用http工具封装
     *
     * @author ruoyi
     */
    public class HttpHelper {
        private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
    
        public static String getBodyString(ServletRequest request) {
            StringBuilder sb = new StringBuilder();
            BufferedReader reader = null;
            try (InputStream 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) {
                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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
  • 相关阅读:
    剑指OfferⅡ 045.二叉树最底层最左边的值 dfs
    【双指针】 LCR 023. 相交链表
    怎样把1.ts-10.ts的文件拼接成一个MP4文件
    vue-element-admin 后台管理系统项目总结(1)思维导图
    2022的七夕,奉上7个精美的表白代码,同时教大家快速改源码自用
    TS和JS 的区别
    一文带你详细了解 JVM 运行时内存
    关于二叉树插入空节点的占位问题(Python)
    使用Navicat Premium进行Oracle数据库schema的复制
    如何认识python
  • 原文地址:https://blog.csdn.net/whlqunzhu/article/details/125776922