Java自定义拦截器处理防抖

自定义注解 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
     */
    @Override
    public 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";


    @Autowired
    private CommonCache commonCache;

    /**
     * 间隔时间,单位:秒 默认10秒
     *
     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
     */
    private int intervalTime = 10;

    public void setIntervalTime(int intervalTime)
    {
        this.intervalTime = intervalTime;
    }

    /**
     * @SuppressWarnings("unchecked"): 告诉编译器忽略 unchecked 警告信息
     * @param request
     * @return
     */
    @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 (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);
    }

    @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) {

            }
        };
    }
}

这里需要导入 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 {
    @Autowired
    RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }
}

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值