栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于注解和拦截器防止表单重复提交

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于注解和拦截器防止表单重复提交

基于注解和拦截器防止表单重复提交 1.定义防止重复提交标识注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {
    
    int interval() default 5000;

    
    String message() default "不允许重复提交,请稍后再试";
}
2.重写HttpServletRequestWrapper

默认是不允许通过request.getInputStream获取请求数据的,这里通过自定义的HttpServletRequestWrapper 备份一下流的数据,自定义HttpServletRequestWrapper 调用父类request.getInputStream()读取全部数据出来保存在一个byte数组内,当再次获取流数据的时候,自定义的HttpServletRequestWrapper 就会用byte数组重新生成一个新的流。备份的流数据仍然保留在byte数组中。

public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] bytes;

    public RepeatableReadRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
        super(request);
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        bytes = request.getReader().readLine().getBytes();
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        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 bais.read();
            }

            @Override
            public int available() throws IOException {
                return bytes.length;
            }
        };
    }
}
3.定义过滤器
public class RepeatableRequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (StringUtils.startsWithIgnoreCase(request.getContentType(), "application/json")) {
            RepeatableReadRequestWrapper requestWrapper = new RepeatableReadRequestWrapper(request, (HttpServletResponse) servletResponse);
            filterChain.doFilter(requestWrapper,servletResponse);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

}
4.封装一个Redis缓存工具类
@Component
public class RedisCache {
    @Autowired
    RedisTemplate redisTemplate;

    public  void setCacheObject(final String key, final T value, Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    public  T getCacheObject(final String key) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        return valueOperations.get(key);
    }
}
5.定义重复提交校验拦截器
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {

    public static final String REPEAT_PARAMS = "repeat_params";
    public static final String REPEAT_TIME = "repeat_time";
    public static final String REPEAT_SUBMIT_KEY = "repeat_submit_key";
    public static final String HEADER = "Authorization";

    @Autowired
    RedisCache redisCache;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否是映射controller层的方法直接通过
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //获取Method对象
            Method method = handlerMethod.getMethod();
            //获取@RepeatSubmit 注解
            RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
            //判断是否为空,如果为空则直接放行,如果不为空则进行重复提交校验
            if (repeatSubmit != null) {
                //校验是否重复提交,如果没有则放行,如果是重复提交则返回状态码:500 message:"重复提交时的文本描述"
                if (isRepeatSubmit(request, repeatSubmit)) {
                    Map map = new HashMap<>();
                    //设置状态码
                    map.put("status", 500);
                    //设置描述信息
                    map.put("message", repeatSubmit.message());
                    //设置响应的contentType和编码格式
                    response.setContentType("application/json;charset=utf-8");
                    //new ObjectMapper().writeValueAsString(map)将map对象转换成json字符串
                    response.getWriter().write(new ObjectMapper().writeValueAsString(map));
                    return false;
                }
            }
        }
        return true;
    }

    
    private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit repeatSubmit) {
        //请求参数字符串
        String nowParams = "";
        if (request instanceof RepeatableReadRequestWrapper) {
            try {
                //获取请求体
                nowParams = ((RepeatableReadRequestWrapper) request).getReader().readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //否则说明请求参数是 key-value 格式的
        if (StringUtils.isEmpty(nowParams)) {
            try {
                //获取所有请求参数并转换为json字符串
                nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        Map nowDataMap = new HashMap<>();
        //key: repeat_params value: nowParams
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        //key: repeat_time value: 时间戳
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
        // /工程路径/controller层路径
        String requestURI = request.getRequestURI();
        //request.getHeader(“Authorization”) Bearer xxxx
        String header = request.getHeader(HEADER);
        //cacheKey = repeat_submit_key + /工程路径/controller层路径 / xxxx
        String cacheKey = REPEAT_SUBMIT_KEY + requestURI + header.replace("Bearer ", "");
        //从缓存中获取cacheKey的值
        Object cacheObject = redisCache.getCacheObject(cacheKey);
        if (cacheObject != null) {
            Map map = (Map) cacheObject;
            //校验参数、间隔时间是否满足重复提交的校验条件,均满足则返回true,否则返回false
            if (compareParams(map, nowDataMap) && compareTime(map, nowDataMap, repeatSubmit.interval())) {
                return true;
            }
        }
        //从缓存中没有获取到cacheKey的值,设置到redis中
        
        redisCache.setCacheObject(cacheKey, nowDataMap, repeatSubmit.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    
    private boolean compareTime(Map map, Map nowDataMap, int interval) {
        // 上一次请求的时间
        Long time1 = (Long) map.get(REPEAT_TIME);
        // 本次请求的时间
        Long time2 = (Long) nowDataMap.get(REPEAT_TIME);
        //对比时间差,如果小于我们设置的两个请求之间的间隔时间,则说明满足重复提交的条件返回true
        if ((time2 - time1) < interval) {
            return true;
        }
        //不满足重复提交的间隔时间则返回false
        return false;
    }

    
    private boolean compareParams(Map map, Map nowDataMap) {
        String nowParams = (String) nowDataMap.get(REPEAT_PARAMS);
        String dataParams = (String) map.get(REPEAT_PARAMS);
        return nowParams.equals(dataParams);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
6.定义一个Dto
@Data
public class UserDto {

    private String name;

    private Integer age;

    private String nickName;

    private String skill;
}
7.Controller测试类
@RestController
public class HelloController {

    @PostMapping("/hello")
    @RepeatSubmit(interval = 10000)
    public String hello(@RequestBody String json) {
        return json;
    }

    @RepeatSubmit
    @Authorization
    @PostMapping("/test")
    public String authorization(@RequestBody UserDto userDto) {
        System.out.println(userDto);
        return "succes";
    }
}

测试结果

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/999060.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号