实现不可重复提交功能
实现不可重复提交功能
获取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结构图
参考文档地址1: 拦截器获取HttpServletRequest里body数据
参考文档地址2: SpringBoot中过滤器的简介及使用方式
参考文档地址3: 注解 @RequiredArgsConstructor