Sprintboot 防止重复请求

本文介绍了如何在SpringBoot应用中使用过滤器和拦截器防止重复请求,包括创建重复请求过滤器、包装请求、拦截器的isRepeatSubmit方法以及防重复注解的使用,通过Redis缓存管理请求数据和检查请求间隔。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、过滤器

过滤器配置

/*
* 重复请求过滤器
* */
@Bean
public FilterRegistrationBean repeatFilterRegistration(){
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    //设置一个filter
    registrationBean.setFilter(new RepeatableFilter());
    //设置filter生效地址
    registrationBean.addUrlPatterns("/*");
    //设置filter的名字
    registrationBean.setName("repeatableFilter");
    //设置filter优先级
    registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
    return registrationBean;
}

过滤器

public class RepeatableFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest servletRequest = null;
        if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)){
            servletRequest = new RepeatedlyRequestWarpper((HttpServletRequest) request, response);
        }

        if (null == servletRequest){
            chain.doFilter(request,response);
        }else {
            chain.doFilter(servletRequest,response);
        }
    }

}

过滤器指定包装实现

public class RepeatedlyRequestWarpper extends HttpServletRequestWrapper {

    private final byte [] body;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public RepeatedlyRequestWarpper(HttpServletRequest request, ServletResponse response) throws IOException{
        super(request);

        request.setCharacterEncoding(Constants.UTF8);
        response.setCharacterEncoding(Constants.UTF8);

        body = HttpUtil.getString(request.getInputStream(), StandardCharsets.UTF_8,false).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 boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public int available() throws IOException {
                return body.length;
            }
        };
    }
}

二、拦截器

拦截器抽象

@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 annotition = method.getAnnotation(RepeatSubmit.class);

            if (annotition != null){
                if (this.isRepeatSubmit(request,annotition)){
                    JSONResult result = JSONResult.error(annotition.message());
                    ServletUtils.renderString(response, JSON.toJSONString(result));
                    return false;
                }
            }
            return true;
        }else {
            return true;
        }
    }

    public abstract boolean isRepeatSubmit(HttpServletRequest request,RepeatSubmit annotion);
}

重点方法isRepeatSubmit

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;


    /**
     * 判断是不是重复提交
     * @param request
     * @param annotion
     * @return
     */
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotion) {
        //定义一个本次请求的参数
        String nowParams = "";
        /*
        * instanceof关键字,测试它左边的对象是否是它右边的类的实例,判断有没有经过重复包装(utf8字符集转换)
        * RepeatedlyRequestWarpper---继承-----》HttpServletRequestWrapper----继承-----》HttpServletRequest=HttpServletRequest
        *
        * */
        if (request instanceof RepeatedlyRequestWarpper){
            RepeatedlyRequestWarpper repeatedlyRequestWarpper = (RepeatedlyRequestWarpper) request;
            nowParams = HttpUtils.getBodyString(request);
        }
        if (StringUtils.isNull(nowParams) || StringUtils.isEmpty(nowParams)){
            nowParams = JSON.toJSONString(request.getParameterMap());
        }
        //获取请求体参数后存储到一个map中
        Map<String,Object> nowDataMap = new HashMap<>();
        nowDataMap.put(REPEAT_PARAMS,nowParams);
        nowDataMap.put(REPEAT_TIME,System.currentTimeMillis());
        //获取访问地址
        String url = request.getRequestURI();
        //获得token令牌
        String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
        //拼接成最终的redis key(这个只能说明一个令牌在一个访问地址)
        String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY+url+submitKey;

        //尝试去redis中查找有没有对应的key
        Object cacheObject = redisCache.getCacheObject(cacheRepeatKey);

        if (cacheObject != null){
            //将redis中查到的对象强转
            Map<String,Object> cacheMap = (Map<String,Object>)cacheObject;
            if (cacheMap.containsKey(url)){
                Map<String,Object> preDataMap = (Map<String, Object>)cacheMap.get(url);
                if (compareParams(nowDataMap,preDataMap) && compareTime(nowDataMap,preDataMap,annotion.interval())){
                    return true;
                }
            }
        }
        //查不到说明redis中没有这个访问地址的请求,进行缓存
        Map<String,Object> cacheMap = new HashMap<String,Object>();
        //key是访问地址,value是访问参数
        cacheMap.put(url,nowDataMap);
        redisCache.setCacheObject(cacheRepeatKey,annotion.interval(),annotion.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    /** 判断参数是否相同
     * @param nowMap
     * @param preMap
     * @return
     */
    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);
    }

    /** 判断两次间隔时间
     * @param nowMap
     * @param preMap
     * @param interval
     * @return
     */
    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;
    }
}

三、防重复注解

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    public int interval() default 5000;
    public String message() default "不允许重复提交,请稍后再试";
}

四、最终使用

@RequestMapping("/test/repeat")
@RestController
public class TestRepeatController {


    @RepeatSubmit(interval = 1000,message = "请求过于频繁,请稍后重试~")
    @RequestMapping("/add")
    public String addUser(String id) {
        return "ok";
    }
}

五、总结

所谓重复请求,就是两个请求体里内容相同,我们要做的就是比对两个请求。
过滤器主要作用是保证请求体内字符编码相同
拦截器拦截我们指定的方法,将相同请求地址封装成key,请求体内容为value
保存到redis缓存数据库,并设置失效时间
进行比对即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值