实现不可重复提交功能


实现不可重复提交功能

获取httpServletRequest中body数据
过滤器(Filter)和拦截器(Interceptor)之间的区别
过滤器生效
解决使用stringRedisTemplate的hasKey()方法时的警告
不可重复提交功能实现
@RequiredArgsConstructor

实现思路

通过在拦截器中获取request中的json数据,判断是否重复提交

实现细节:

1> 为什么要新建一个类继承HttpServletRequestWrapper实现对 httpServletRequest 的装饰,用来获取 body 数据

参数只能在拦截器里获取一次,往后在controller层就无法获取数据,提示body为空。

在网上查找资料后发现,request的输入流只能读取一次

2> 为什么需要在 filter 里进行对 httpServletRequest 的包装转换,直接在拦截器里进行包装不行嘛?

过滤器(Filter)和拦截器(Interceptor)之间的最大区别就是,过滤器可以包装Request和Response,而拦截器并不能

3> 过滤器生效

在启动类上加@ServletComponentScan //开启servlet的组件扫描

 

实现步骤

01 不可重复提交拦截器
/**
* 不可重复提交拦截器
* 描述:
*
* @author lyn
* @date 2022/3/7 16:38
*/
@Component
@Order
@Slf4j
public class NoRepeatSubmitInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
?
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!handler.getClass().equals(HandlerMethod.class)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
//拦截加上不可重复提交注解接口请求
boolean repeatSubmit = handlerMethod.getMethod().isAnnotationPresent(NoRepeatSubmit.class);
if (repeatSubmit) {
BodyReaderHttpServletRequestWrapper wrapper = null;
if (request instanceof BodyReaderHttpServletRequestWrapper) {
wrapper = (BodyReaderHttpServletRequestWrapper) request;
} else {
return true;
}
//拦截器获取HttpServletRequest里body数据
String body = wrapper.getBodyString(request);
String md5 = SecureUtil.md5(body);
String uri = wrapper.getRequestURI();
String key = "repeat:" + uri + ":" + md5;
synchronized (key) {
?
if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
return false;
} else {
//测试时延长过期时间
stringRedisTemplate.opsForValue().set(key, "1", 20L, TimeUnit.SECONDS);
}
}
}
return true;
}
}
02 测试controller接口
/**
* 测试不可重复提交
* 描述: TODO
*
* @author lyn
* @date 2022/3/7 17:29
*/
@RequestMapping("/test")
@RestController
public class TestController {
private int count;
@NoRepeatSubmit
@PostMapping("/no-repeat-submit")
public void testNoRepeatSubmit(@RequestBody Map<String,String> map){
count++;
System.out.println("count; "+count);
System.out.println(map);
}
}

具体细节实现

03 过滤器

需要在 filter 里进行对 httpServletRequest 的包装转换

/**
* 过滤器
* @author lyn
*/
@WebFilter(filterName = "httpServletRequestWrapperFilter", urlPatterns = {"/*"})
public class HttpServletRequestWrapperFilter implements Filter {
?
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
?
  }
?
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {
?
       ServletRequest requestWrapper = null;
?
       if (request instanceof HttpServletRequest) {
           HttpServletRequest httpRequest = (HttpServletRequest) request;
           //遇到post方法才对request进行包装
           String methodType = httpRequest.getMethod();
           if ("POST".equals(methodType)) {
               requestWrapper = new BodyReaderHttpServletRequestWrapper(
                      (HttpServletRequest) request);
          }
      }
       if (null == requestWrapper) {
           chain.doFilter(request, response);
      } else {
?
           chain.doFilter(requestWrapper, response);
      }
  }
?
   @Override
   public void destroy() {
?
  }
}

 

04 BodyReaderHttpServletRequestWrapper

新建一个类继承HttpServletRequestWrapper实现对 httpServletRequest 的装饰,用来获取 body 数据

/**
* 新建一个类继承HttpServletRequestWrapper实现对 httpServletRequest 的装饰,用来获取 body 数据
* @author lyn
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
?
?
   private final byte[] body;
   private String bodyStr;
?
   public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
       super(request);
       String bodyString = getBodyString(request);
       body = bodyString.getBytes(Charset.forName("UTF-8"));
       bodyStr=bodyString;
  }
?
   public String getBodyStr() {
       return bodyStr;
  }
?
   @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 boolean isFinished() {
               return false;
          }
?
           @Override
           public boolean isReady() {
               return false;
          }
?
           @Override
           public void setReadListener(ReadListener readListener) {
          }
      };
  }
?
?
   public String getBodyString(HttpServletRequest request) throws IOException {
       StringBuilder sb = new StringBuilder();
       InputStream inputStream = null;
       BufferedReader reader = null;
       try {
           inputStream = request.getInputStream();
           reader = new BufferedReader(
                   new InputStreamReader(inputStream, Charset.forName("UTF-8")));
?
           char[] bodyCharBuffer = new char[1024];
           int len = 0;
           while ((len = reader.read(bodyCharBuffer)) != -1) {
               sb.append(new String(bodyCharBuffer, 0, len));
          }
      } 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();
  }
}

 

05 @ServletComponentScan
@SpringBootApplication
@ServletComponentScan// 扫描 Servlet 相关的组件,使过滤器生效
public class StartApp {
   public static void main(String[] args) {
       SpringApplication.run(StartApp.class, args);
  }
}

 

06 在web mvc配置类中注册拦截器
/**
* web mvc 配置
* @author lyn
*/
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
?
   private final NoRepeatSubmitInterceptor noRepeatSubmitInterceptor;
?
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       //注册不可重复提交拦截器
       registry.addInterceptor(noRepeatSubmitInterceptor).addPathPatterns("/**");
  }
?
}
07 不可重复注解
/**
* 不允许重复提交注解
* @author lyn
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
?
}

测试demo

demo结构图

demo地址

参考文档地址1: 拦截器获取HttpServletRequest里body数据

参考文档地址2: SpringBoot中过滤器的简介及使用方式

参考文档地址3: 注解 @RequiredArgsConstructor