• (十四)admin-boot项目之redis注解实现限制重复提交


    (十四)redis注解实现限制重复提交

    项目地址:https://gitee.com/springzb/admin-boot
    如果觉得不错,给个 star

    简介:
    这是一个基础的企业级基础后端脚手架项目,主要由springboot为基础搭建,后期整合一些基础插件例如:redis、xxl-job、flowable、minioio、easyexcel、skyWalking、rabbitmq

    一、实现思路与原理

    这里主要采用 拦截器 的思想去做

    过滤器原理

    过滤器几乎可以对所有进入容器的请求起作用,

    而拦截器只会对中请求或访问****目录下的资源请求起作用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOkLdRwX-1660447299845)(image/image_u7Ge0WRprC.png)]

    **init() **:该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。

    **doFilter() **:容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。

    destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次

    public class RepeatSubmitFilter implements Filter {
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
            System.out.println("Filter 前置");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
            System.out.println("Filter 处理中");
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
            System.out.println("Filter 后置");
        }
    }
    
    @Bean
    public FilterRegistrationBean<RepeatSubmitFilter> xssFilterRegistration() {
        FilterRegistrationBean<RepeatSubmitFilter> registration = new FilterRegistrationBean();
        registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[0]);
        registration.setFilter(new RepeatSubmitFilter());
        registration.addUrlPatterns(new String[]{"/*"});
        registration.setName("xssRequestFilter");
        registration.setOrder(Integer.MAX_VALUE);
        return registration;
    }
    
    
    • 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

    拦截器原理

    拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理

    preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。

    postHandle(): 只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

    afterCompletion(): 只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行

    public class MyInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            System.out.println("Interceptor 前置");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
            System.out.println("Interceptor 处理中");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
            System.out.println("Interceptor 后置");
        }
    }
    
    
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
            registry.addInterceptor(new MyInterceptor1()).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

    二、具体实现

    自定义注解 RepeatSubmit

    package cn.mesmile.admin.common.repeat;
    
    import cn.mesmile.admin.common.constant.AdminConstant;
    
    import java.lang.annotation.*;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author zb
     * @Description 检查重复提交
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit {
    
        /**
         * 间隔时间,小于此时间视为重复提交
         */
        long interval() default 5000;
    
        /**
         * 默认时间单位 毫秒
         */
        TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    
        /**
         * 自定义判断重复的标识 支持 spring el 例如  #user.getName()
         * 当有多个表达式的时候中间用 ; 分割
         * 当此参数不为空的时候,就根据此处表达式获取的值来判断是否重复提交
         */
        String param() default "";
    
        /**
         * 默认缓存key的前缀
         */
        String prefix() default AdminConstant.REPEAT_SUBMIT_PREFIX;
    
        /**
         * 提示消息
         */
        String msg() default "系统正在处理,请勿重复提交";
    
    }
    
    • 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

    定义配置开关RepeatSubmitProperties

    package cn.mesmile.admin.common.repeat;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author zb
     * @Description
     */
    @Data
    @ConfigurationProperties("repeat.submit")
    public class RepeatSubmitProperties {
    
        /**
         * 是否开启重复检查,注解,默认 false
         */
        private Boolean enabled = Boolean.FALSE;
        /**
         * 放行路径
         */
        private List<String> skipUrl = new ArrayList<>();
    
    }
    
    • 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

    定义拦截器RepeatSubmitInterceptor

    package cn.mesmile.admin.common.repeat;
    
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.crypto.digest.MD5;
    import cn.mesmile.admin.common.exceptions.RepeatSubmitException;
    import cn.mesmile.admin.common.filter.xss.WebUtil;
    import cn.mesmile.admin.common.lock.AdminExpressionEvaluator;
    import cn.mesmile.admin.common.result.ResultCode;
    import cn.mesmile.admin.common.utils.AdminRedisTemplate;
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.MediaType;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    
    /**
     * @author zb
     * @Description
     */
    @Slf4j
    public class RepeatSubmitInterceptor implements HandlerInterceptor, ApplicationContextAware {
    
        private final AdminRedisTemplate adminRedisTemplate;
    
        private ApplicationContext applicationContext;
    
        public RepeatSubmitInterceptor(AdminRedisTemplate adminRedisTemplate) {
            this.adminRedisTemplate = adminRedisTemplate;
        }
    
        @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, handlerMethod)) {
                        throw new RepeatSubmitException(ResultCode.FAILURE, annotation.msg());
                    }
                }
            }
            return true;
        }
    
        private static final AdminExpressionEvaluator EVALUATOR = new AdminExpressionEvaluator();
    
        /**
         * 是否是 重复提交
         * @param request 请求体
         * @param annotation 注解
         * @param handlerMethod 拦截方法
         * @return
         */
        private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation, HandlerMethod handlerMethod) {
            Method method = handlerMethod.getMethod();
            List<Object> objects = new ArrayList<>();
            String jsonValue = null;
            Map<String, String[]> parameterMap = null;
            if (request instanceof RepeatSubmitRequestWrapper) {
                RepeatSubmitRequestWrapper requestWrapper = (RepeatSubmitRequestWrapper) request;
                jsonValue = requestWrapper.getRequestBody();
                Parameter[] parameters = method.getParameters();
                if (parameters != null && parameters.length > 0 && StrUtil.isNotEmpty(jsonValue)) {
                    for (Parameter parameter : parameters) {
                        Class<?> type = parameter.getType();
                        Object object = JSONObject.parseObject(jsonValue, type);
                        objects.add(object);
                    }
                }
            } else {
                parameterMap = request.getParameterMap();
                if (parameterMap != null && parameterMap.size() > 0) {
                    Collection<String[]> values = parameterMap.values();
                    objects.addAll(values);
                }
            }
            String param = annotation.param();
            String value = "";
            // 提取自定义参数
            if (StrUtil.isNotBlank(param)) {
                value = EVALUATOR.evalLockParam(method, objects.toArray(), handlerMethod.getBean(), param, applicationContext);
            } else {
                if (request instanceof RepeatSubmitRequestWrapper) {
                    // 提取json数据
                    value = jsonValue;
                } else {
                    // 读取 get 请求数据
                    if (parameterMap != null && parameterMap.size() > 0) {
                        String parameters = JSONObject.toJSONString(parameterMap);
                        value = value + parameters;
                    }
                }
            }
            String uri = request.getRequestURI();
            String key = annotation.prefix() + ":" + uri;
            if (StrUtil.isNotBlank(value)) {
                String md5Str = MD5.create().digestHex(value, StandardCharsets.UTF_8);
                key = key + ":" + md5Str;
            }
            boolean result = adminRedisTemplate.setIfAbsentExpire(key, value, annotation.interval(), annotation.timeUnit());
            if (!result) {
                log.error("重复提交异常:uri:{}, param:{}", uri, value);
            }
            return !result;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    
    
    • 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
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    注册拦截器WebInterceptorConfig

    package cn.mesmile.admin.common.repeat;
    
    import cn.mesmile.admin.common.utils.AdminRedisTemplate;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * @author zb
     * @Description
     */
    @EnableConfigurationProperties({RepeatSubmitProperties.class})
    @Configuration(
            proxyBeanMethods = false
    )
    @ConditionalOnProperty(
            value = {"repeat.submit.enabled"},
            havingValue = "true"
    )
    public class WebInterceptorConfig implements WebMvcConfigurer {
    
        @Resource
        private RepeatSubmitProperties repeatSubmitProperties;
    
        @Resource
        private AdminRedisTemplate adminRedisTemplate;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            RepeatSubmitInterceptor repeatSubmitInterceptor = new RepeatSubmitInterceptor(adminRedisTemplate);
            List<String> skipUrl = repeatSubmitProperties.getSkipUrl();
            // 注册过滤器,并且放行某些路径
            registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(skipUrl);
        }
    }
    
    • 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

    由于在拦截 @RequestBody 请求的json数据的时候,流不能够重复读,所以需要构建 可重复读的流,

    这里用过滤器实现

    定义过滤器包装RepeatSubmitRequestWrapper

    package cn.mesmile.admin.common.repeat;
    
    import cn.mesmile.admin.common.filter.xss.WebUtil;
    
    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;
    
    /**
     * @author zb
     * @Description 构建可重复读的 InputStream
     */
    public class RepeatSubmitRequestWrapper extends HttpServletRequestWrapper {
    
        private final byte[] body;
    
        public String getRequestBody() {
            return requestBody;
        }
    
        private final String requestBody;
    
        public RepeatSubmitRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
            super(request);
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
    
            requestBody = WebUtil.getRequestBody(request.getInputStream());
            body = requestBody.getBytes(StandardCharsets.UTF_8);
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    定义过滤器RepeatSubmitFilter

    package cn.mesmile.admin.common.repeat;
    
    import cn.hutool.core.util.StrUtil;
    import org.springframework.http.MediaType;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * @author zb
     * @Description 包装可重复读的 inputStream
     */
    public class RepeatSubmitFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            if (request instanceof HttpServletRequest
                    // 只拦截 contentType 为 "application/json" 的请求头 做处理,可重复读
                    && StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
                ServletRequest requestWrapper = new RepeatSubmitRequestWrapper((HttpServletRequest) request, response);
                chain.doFilter(requestWrapper, response);
            } else {
                chain.doFilter(request, 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    注册过滤器RequestConfiguration

    注意:这里 repeatSubmitRequestFilter 配置时 order 一定要设置为最大

    order越大优先级越低,最后执行

    package cn.mesmile.admin.common.filter;
    
    import cn.mesmile.admin.common.filter.space.SpaceProperties;
    import cn.mesmile.admin.common.filter.space.SpaceRequestFilter;
    import cn.mesmile.admin.common.filter.xss.XssProperties;
    import cn.mesmile.admin.common.filter.xss.XssRequestFilter;
    import cn.mesmile.admin.common.repeat.RepeatSubmitFilter;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.DispatcherType;
    
    /**
     * @author zb
     * @Description 配置注册过滤器
     */
    @Configuration(
            proxyBeanMethods = false
    )
    @EnableConfigurationProperties({XssProperties.class, SpaceProperties.class})
    public class RequestConfiguration {
    
        private final SpaceProperties spaceProperties;
        private final XssProperties xssProperties;
    
        @Bean
        public FilterRegistrationBean<XssRequestFilter> xssFilterRegistration() {
            FilterRegistrationBean<XssRequestFilter> registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[0]);
            registration.setFilter(new XssRequestFilter(this.xssProperties));
            registration.addUrlPatterns(new String[]{"/*"});
            registration.setName("xssRequestFilter");
            registration.setOrder(Integer.MAX_VALUE - 1);
            return registration;
        }
    
        @Bean
        public FilterRegistrationBean<SpaceRequestFilter> spaceFilterRegistration() {
            FilterRegistrationBean<SpaceRequestFilter> registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[0]);
            registration.setFilter(new SpaceRequestFilter(this.spaceProperties));
            registration.addUrlPatterns(new String[]{"/*"});
            registration.setName("spaceRequestFilter");
            registration.setOrder(Integer.MAX_VALUE - 2);
            return registration;
        }
    
        /**
         * 注意这里  RepeatSubmitFilter 需要放在最后,即  setOrder 最大
         */
        @Bean
        public FilterRegistrationBean<RepeatSubmitFilter> repeatSubmitFilterRegistration() {
            FilterRegistrationBean<RepeatSubmitFilter> registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[0]);
            registration.setFilter(new RepeatSubmitFilter());
            registration.addUrlPatterns(new String[]{"/*"});
            registration.setName("repeatSubmitRequestFilter");
            registration.setOrder(Integer.MAX_VALUE);
            return registration;
        }
    
        public RequestConfiguration(final XssProperties xssProperties, final SpaceProperties spaceProperties) {
            this.xssProperties = xssProperties;
            this.spaceProperties = spaceProperties;
        }
    
    }
    
    
    • 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

    定义重复检查异常

    package cn.mesmile.admin.common.exceptions;
    
    
    import cn.mesmile.admin.common.result.IResultCode;
    import cn.mesmile.admin.common.result.ResultCode;
    
    /**
     * @author zb
     * @Description 频率限制异常
     */
    public class RepeatSubmitException extends RuntimeException {
    
        private final long serialVersionUID = 1L;
    
        private int code = ResultCode.FAILURE.getCode();
    
        private String msg = ResultCode.FAILURE.getMessage();
    
        public RepeatSubmitException() {
            super();
        }
    
        public RepeatSubmitException(String msg) {
            super(msg);
            this.msg = msg;
        }
    
        public RepeatSubmitException(IResultCode resultCode, String msg) {
            super(msg);
            this.code = resultCode.getCode();
            this.msg = msg;
        }
    
        public RepeatSubmitException(String msg, Throwable cause) {
            super(msg, cause);
            this.msg = msg;
        }
    
        public RepeatSubmitException(IResultCode resultCode, String msg, Throwable cause) {
            super(msg, cause);
            this.code = resultCode.getCode();
            this.msg = msg;
        }
    
        public RepeatSubmitException(Throwable cause) {
            super(cause);
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    
    
    • 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

    全局拦截参数重复异常

    package cn.mesmile.admin.common.handler;
    
    import cn.mesmile.admin.common.constant.AdminConstant;
    import cn.mesmile.admin.common.exceptions.*;
    import cn.mesmile.admin.common.result.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.validation.BindException;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import javax.validation.ValidationException;
    import java.util.stream.Collectors;
    
    /**
     * @author zb
     * @Description 全局异常拦截
     * 

    * 如果我同时捕获了父类和子类,那么到底能够被那个异常处理器捕获呢?比如 Exception 和 BusinessException * 当然是 BusinessException 的异常处理器捕获了,精确匹配,如果没有 BusinessException 的异常处理器才会轮到它的 父亲 , * 父亲 没有才会到 祖父 。总之一句话, 精准匹配,找那个关系最近的 *

    */
    @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 重复提交异常信息 * */ @ExceptionHandler(RepeatSubmitException.class) public R handle(RepeatSubmitException repeatSubmitException) { // 获取指定包名前缀的异常信息,减少不必要的日志 // String stackTraceByPn = getStackTraceByPn(rateLimiterException, AdminConstant.BASE_PACKAGE); log.error("重复提交异常信息, 消息:{} 编码:{}", repeatSubmitException.getMessage(), repeatSubmitException.getCode()); return R.fail(repeatSubmitException.getCode(), repeatSubmitException.getMessage()); } }
    • 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

    项目结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-anfRY60k-1660447299846)(image/image_5OiZppmqqL.png)]

    三、结果测试

    package cn.mesmile.admin.modules.system.controller;
    
    import cn.mesmile.admin.common.repeat.RepeatSubmit;
    import cn.mesmile.admin.common.result.R;
    import cn.mesmile.admin.common.utils.AdminRedisTemplate;
    import cn.mesmile.admin.modules.system.entity.Sys;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @author zb
     * @Description
     */
    @Api(tags = "用户登录测试接口")
    @Slf4j
    @RequestMapping("/api/v1/hello")
    @RestController
    public class HelloController {
    
        @Resource
        private AdminRedisTemplate adminRedisTemplate;
    
        /**
        * 这里自定义 两个字段来验证 验证是否重复
        */
        @RepeatSubmit(interval = 5000, param = "#s.hello;#s.world")
        @PostMapping("/test")
        public R submit(@RequestBody Sys s){
            System.out.println("s = " + s);
            return R.data(s);
        }
    
    }
    
    @Data
    public class Sys {
    
        private String hello;
    
        private String world;
        
        private String test;
    }
    
    
    • 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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tj8Kaxyq-1660447299846)(image/image_QcHM2Emhr1.png)]

  • 相关阅读:
    夜天之书 #98 Rust 程序库生态合作的例子
    react: antd组件使用 FC Fragment
    1-辛基-3-甲基咪唑六氟磷酸盐([OMIM] PF6)负载修饰四氧化三铁( Fe3O4)为载体包覆二氧化硅材料
    elementui写一个自定义的rangeInput的组件
    QT 简易计算器
    Spring Boot中的分布式缓存方案
    【CompletableFuture 总结】
    【数据结构与算法分析】0基础带你学数据结构与算法分析08--二叉查找树 (BST)
    [附源码]Python计算机毕业设计电子工厂进销存管理系统
    基于Java Web的汽车租赁系统的设计与实现
  • 原文地址:https://blog.csdn.net/suprezheng/article/details/126329722