实现基于redis的表单重复提交拦截器

672

首先编写request的封装类,用于重复读取request中的body流

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.test.common.enums.RedisKeyEnum;
import com.test.common.utils.AjaxResult;
import com.test.common.utils.RedisComponent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebFilter(filterName = "RepeatedSubmitFilter", urlPatterns = {"/*"})
@Order(2)
/**
 * @author Neal.Zhi
 * @description 防止表单重复提交的过滤器
 * @date 2020/09/17 14:10
 **/
public class RepeatedSubmitFilter implements Filter {

    private static final AjaxResult ajaxResult = AjaxResult.failNoTime("请勿重复提交!");

    @Resource
    RedisComponent redisComponent;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //获取当前的时间戳
        long currentTimeMillis = System.currentTimeMillis();
        //使用包装类封装request,解决servlet无法重复获取body流的问题
        MultiReadHttpServletRequest requestWrapper = new MultiReadHttpServletRequest(request);
        //请求参数
        String queryString = "";
        //请求类型
        String contentType = requestWrapper.getContentType();
        //请求url
        String requestURI = requestWrapper.getRequestURI();
        //请求尾接参数
        String requestQueryString = requestWrapper.getQueryString();
        //获取token
        String token = requestWrapper.getHeader("Authorization");
        //如果token为空放行
        if (StringUtils.isBlank(token)) {
            filterChain.doFilter(requestWrapper, servletResponse);
            return;
        }

        //获取请求参数
        //如果url有尾接参数
        if (StringUtils.isNotBlank(requestQueryString)) {
            queryString = requestQueryString;
        }
        //如果为表单请求
        if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(contentType)) {
            queryString = requestWrapper.getBodyStr();
        }
        //如果为json请求
        if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
            queryString = requestWrapper.getBodyStr();
        }
        //如果参数为空放行
        if (StringUtils.isBlank(queryString)) {
            filterChain.doFilter(requestWrapper, servletResponse);
            return;
        }
        //将参数进行散列加密
        String queryStringMd5 = SecureUtil.md5(queryString);

        String redisMapKey = token + ":" + requestURI + ":" + queryStringMd5;
        String redisKey = RedisKeyEnum.REPEATED.getValue();
        //在redis查询此接口是否被1秒内被查询过
        Object hget = redisComponent.hget(redisKey, redisMapKey);
        if (hget != null) {
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            ServletOutputStream outputStream = response.getOutputStream();
            String result = JSON.toJSONString(ajaxResult);
            outputStream.write(result.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
            return;
        }
        //存入1秒
        redisComponent.hset(redisKey, redisMapKey, currentTimeMillis, 1);
        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) {
        //do nothing
    }

    @Override
    public void destroy() {
        //do nothing
    }
}

再编写过滤器,并且将request进行封装使用

注意:doFilter中放行的request一定要用封装类,否则后续使用request获取流或者reader时,会进行报错

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.test.common.enums.RedisKeyEnum;
import com.test.common.utils.AjaxResult;
import com.test.common.utils.RedisComponent;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebFilter(filterName = "RepeatedSubmitFilter", urlPatterns = {"/*"})
@Order(2)
/**
 * @author Neal.Zhi
 * @description 防止表单重复提交的过滤器
 * @date 2020/09/17 14:10
 **/
public class RepeatedSubmitFilter implements Filter {

    private static final AjaxResult ajaxResult = AjaxResult.failNoTime("请勿重复提交!");

    //这里可以注入你自己业务中的redis操作类	
    @Resource
    RedisComponent redisComponent;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //获取当前的时间戳
        long currentTimeMillis = System.currentTimeMillis();
        //使用包装类封装request,解决servlet无法重复获取body流的问题
        MultiReadHttpServletRequest requestWrapper = new MultiReadHttpServletRequest(request);
        //请求参数
        String queryString = "";
        //请求类型
        String contentType = requestWrapper.getContentType();
        //请求url
        String requestURI = requestWrapper.getRequestURI();
        //请求尾接参数
        String requestQueryString = requestWrapper.getQueryString();
        //获取token(你可以根据你的业务自行获取相关token)
        String token = requestWrapper.getHeader("Authorization");
        //如果token为空放行
        if (StringUtils.isBlank(token)) {
            filterChain.doFilter(requestWrapper, servletResponse);
            return;
        }

        //获取请求参数
        //如果url有尾接参数
        if (StringUtils.isNotBlank(requestQueryString)) {
            queryString = requestQueryString;
        }
        //如果为表单请求
        if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(contentType)) {
            queryString = requestWrapper.getBodyStr();
        }
        //如果为json请求
        if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
            queryString = requestWrapper.getBodyStr();
        }
        //如果参数为空放行
        if (StringUtils.isBlank(queryString)) {
            filterChain.doFilter(requestWrapper, servletResponse);
            return;
        }
        //将参数进行散列加密
        String queryStringMd5 = SecureUtil.md5(queryString);

        String redisMapKey = token + ":" + requestURI + ":" + queryStringMd5;
	//这里可以定义你自己的rediskey常量	
        String redisKey = RedisKeyEnum.REPEATED.getValue();
        //在redis查询此接口是否被1秒内被查询过
        Object hget = redisComponent.hget(redisKey, redisMapKey);
        if (hget != null) {
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            ServletOutputStream outputStream = response.getOutputStream();
            String result = JSON.toJSONString(ajaxResult);
            outputStream.write(result.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
            return;
        }
        //存入1秒
        redisComponent.hset(redisKey, redisMapKey, currentTimeMillis, 1);
        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) {
        //do nothing
    }

    @Override
    public void destroy() {
        //do nothing
    }
}