实现不可重复提交功能

实现不可重复提交功能

获取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结构图

img

demo地址

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

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

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

posted @ 2022-03-08 15:52  进击的小蔡鸟  阅读(108)  评论(0编辑  收藏  举报