一步一步实现若依框架--2.3防止重复提交 repeat_submit

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
原理:
  常见的场景端页面多次点击提交按钮,通常是前端通过点击一次后使按钮disable进行处理,后端同样也需要进行限制。若依使用了注解+拦截器的方式,这里其实也可以用AOP。在缓存中(若依的缓存就是使用redis)记录每个客户端的请求方法和参数,在redis中设置超时时间。如果在超时时间内进行了第二次请求且参数都一致,拦截器进行拦截抛出异常不进行真正的处理。思路其实和限流类似,只是这里多了对请求参数的处理,并且没有采用AOP而是用了拦截器去实现。
  
  1)添加过滤器,处理请求参数不能多次读的问题。
        自定义了一个HttpServletRequest的包装器,通过在变量中保存流中读取的内容,解决了不能多次读入请求参数的问题。然后通过添加过滤器在dofilter中替换了这个增强功能后的request。包装器代码:
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);
        // 把reques的参数读入到byte数组中,就可以多次使用
        body= HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

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

  把这个包装器通过过滤器加入到请求处理中去。

public class RepeatableFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        /**
         * 参数如果是json类型,需要HttpServletRequest包装器进行加强,多次读取参数
         */
        if(request instanceof HttpServletRequest
                && request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)){
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request,response);
        }

        if(requestWrapper == null){
            filterChain.doFilter(request,response);
        }else{
            filterChain.doFilter(requestWrapper, response);
        }
    }

}

  最后要记得把这个过滤器注册到spring环境里。

@Component
public class FilterConfig {
    @Bean
    public FilterRegistrationBean repeatableFilterRegistration(){
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new RepeatableFilter());
        registration.addUrlPatterns("/*");
        registration.setName("repeatableFilter");
        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
        return registration;
    }
}

  springboot通过拦截器获取参数有两种方式,一种通过request.getParameter获取Get方式传递的参数,另外一种是通过request.getInputStream或reques.getReader获取通过POST/PUT/DELETE/PATCH传递的参数。@PathVariable注解是REST风格url获取参数的方式,只能用在GET请求类型,通过getParameter获取参数;@RequestParam注解支持GET和POST/PUT/DELETE/PATCH方式,Get方式通过getParameter获取参数和post方式通过getInputStream或getReader获取参数;@RequestBody注解支持POST/PUT/DELETE/PATCH,可以通过getInputStream和getReader获取参数。通过getInputStream或getReader在拦截器中获取会导致控制器拿到的参数为空,这是因为流读取一次之后流的标志位已经发生了变化,无法多次读取参数,所以通过装饰者模式,增强了HttpServletRequest,把流中的数据读取后暂存在byte[]中,可以实现多次读取的需求。

  2)添加拦截器

        拦截到自定义注解进行处理 。通过AbstractRepeatSubmitInterceptor拦截请求,进行重复提交的判断和处理。
  3)测试
  controller中建立添加注解的测试方法:
  
@PostMapping("/repeatTest")
@RepeatSubmit(interval = 100000, message = "重复提交提示!!!")
public String repeatTest(@RequestBody String json) {
    return json;
}

  

 

  

 

 代码地址:

  https://github.com/hunji/RYMirror/releases/tag/2.3%E9%98%B2%E6%AD%A2%E9%87%8D%E5%A4%8D%E6%8F%90%E4%BA%A4

 

 

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6