目录

思考过程

遇到的问题

过滤器Filter使用步骤

全局去除入参前后空格代码实现

处理过程中自己造成的一些问题


需求背景:

前端所有的条件查询去除前后空格,如搜 【"   测试    "】后端将其转为【测试】。之前都是前端统一处理的,但是这次要后端处理,那么就得考虑全局对入参进行去前后空格再进一步去查询,所以通过滤器Filter来实现。

思考过程

最开始想到的是拦截器Interceptor,但是拦截器一般用于对接口参数进行取值的一些操作,对于GET请求,如果进行入参修改好像更复杂,个人暂时定论拦截器对于GET入参只能取不能改(不确定),但是对于POST请求,不管是拦截器还是过滤器,都会面临请求体丢失问题,所以用到拦截器最终还是用到过滤器来配合解决这个问题,下文再详细说明,所以本文采用拦截器Filter进行解决

遇到的问题

1.POST请求 Required request body is missing


对于GET请求,通过request.getParameterMap()获取所有参数,进行处理后能正常传给接口,但是对于POST请求,通过request.getInputStream()获取流从而读取流获取参数。但request.getInputStream()只能读取一次,所以到接口的时候,@RequestBody注解也是通过该方法获取参数,所以此时如果没有做处理就没参数可以读,就会报错。所以自定义HttpServletRequestWrapper,重写getInputStream方法,把处理完的数据流保存下来传给接口获取


2.@WebFilter中urlPatterns = {"/test/*","/test2/*"}配置过滤规则失效


原因是同时添加了@Component与@WebFilter导致拦截不起作用,就默认所有接口都进过滤器。解决方法去掉@Component保留@WebFilter,在启动类添加@ServletComponentScan即可解决。如果保留@Component去除@WebFilter,则需要单独建一个配置类配置拦截规则。

过滤器Filter使用步骤

实现Filter接口,重写doFilter方法,通过@WebFilter进行拦截路径配置即可

全局去除入参前后空格代码实现

@WebFilter中urlPatterns表示需要过滤得接口,如果是全部则用/*,
 单个规则写法urlPatterns = "/test/*", 多个规则用{}
 filterName 表示过滤器名字,可采用@WebFilter配置也可以单独写个配置类

/**
 * 请求参数去除前后空格过滤器
 */
@Log4j2
@WebFilter(urlPatterns = {"/test/*","/test2/*"}, filterName = "ParameterTrimFilter")
public class ParameterTrimFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("进入请求参数去前后空格过滤器");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //防止流读取一次就没,自定义HttpServletRequestWrapper处理
        ParameterTrimWrapper parameterTrimWrapper = new ParameterTrimWrapper(request);
        filterChain.doFilter(parameterTrimWrapper, servletResponse);
        log.info("参数去空格过滤器处理完成");
    }

    @Override
    public void destroy() {
    }
}
/**
 * 请求参数去除前后空格HttpServletRequestWrapper
 */
@Log4j2
public class ParameterTrimWrapper extends HttpServletRequestWrapper {

    //用于存放post请求体数据
    private byte[] body;

    //用于存放get请求参数
    private Map<String, String[]> params = new HashMap<>();

    private static final String CONTENT_TYPE = "Content-Type";

    private static final String GET = "GET";

    private static final String POST = "POST";

    public ParameterTrimWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //处理get请求 url路径携带参数情况
        if(GET.equals(request.getMethod())){
            //获取到所有入参
            Map<String, String[]> requestMap = request.getParameterMap();
            if(ObjectUtil.isNotEmpty(requestMap)){
                this.params.putAll(requestMap);
                //对每个入参进行去除前后空格
                this.dealUrlParameter();
            }
        }
        //处理post请求 对请求体进行情况
        if(POST.equals(request.getMethod())){
            //getInputStream()读取请求体数据流
            String bodyJson = new String(IOUtils.toByteArray(super.getInputStream()), StandardCharsets.UTF_8);
            if(StringUtils.isNotEmpty(bodyJson)){
                //处理每个参数去除前后空格
                Map<String, Object> finalMap = dealJson(bodyJson);
                //将处理完的结果再次塞进body
                this.body = JSON.toJSONString(finalMap).getBytes(StandardCharsets.UTF_8);
            }
        }
    }

    /**
     * get请求参数处理 将参数中的值去除前后空格后写回
     */
    public void dealUrlParameter(){
        params.forEach((k,v) ->{
            for(int i = 0 ;i < v.length ; i++ ){
                v[i] = v[i].trim();
            }
            params.put(k,v);
        });
    }

    /**
     * post请求参数处理 处理json参数 去除前后空格
     * @param bodyJson json
     * @return map
     */
    public static Map<String, Object> dealJson(String bodyJson){
        Map<String, Object> jsonMap = new HashMap<>();
        //将body字符串转为json对象
        JSONObject jsonObject = JSONObject.parseObject(bodyJson);
        if (jsonObject != null) {
            //对键值对进行处理
            jsonObject.forEach((k,v) -> {
                //根据字段类型进行处理
                if(v instanceof JSONArray){
                    //如果第一层字段为数组,再对数组中每个元素类型判断递归处理
                    List<Object> list = new ArrayList<>();
                    for (Object obj : (JSONArray) v){
                        if(obj instanceof JSONObject){
                            list.add(dealJson(obj.toString()));
                        }else if(obj instanceof String){
                            list.add(obj.toString().trim());
                        }else {
                            list.add(obj);
                        }
                    }
                    jsonMap.put(k , list);
                }else if(v instanceof JSONObject){
                    //第一层字段为对象 对象内部进行递归处理
                    jsonMap.put(k , dealJson(v.toString()));
                }else if(v instanceof String){
                    //第一层字段为字符串 直接处理
                    jsonMap.put(k , v.toString().trim());
                }else {
                    //其他
                    jsonMap.put(k , v);
                }
            });
        }
        return jsonMap;
    }

    //重写getInputStream(),最后程序回来调用这个方法
    @Override
    public ServletInputStream getInputStream() throws IOException {
        //bais处理过后的最新数据流
        final ByteArrayInputStream bais =new ByteArrayInputStream(body);
        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() {
                return bais.read();
            }
        };

    }

    //已下几个方法是设计参数读写处理 照着写就行
    public String getBody() {
        return new String(this.body, StandardCharsets.UTF_8);
    }

    @Override
    public String[] getParameterValues(String name) {
        return params.get(name);
    }

    @Override
    public Map<String,String[]> getParameterMap(){
        return this.params;
    }

    @Override
    public String getParameter(String name) {
        String[]values = params.get(name);
        if(values == null || values.length == 0) {
            return null;
        }
        return values[0];
    }

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

处理过程中自己造成的一些问题

1.本地测试get请求与post请求都可以,但是一部署到测试环境,还是会造成body丢失

解决:在自定义的ParameterTrimWrapper.getInputStream() 方法中,为了之过滤application/json的post请求,加了判断

public ServletInputStream getInputStream() throws IOException {
     //其他非application/json类型不做处理
     if(!super.getHeader("Content-Type").equalsIgnoreCase("application/json")){
          return super.getInputStream();
      }else{
          ByteArrayInputStream bais =new ByteArrayInputStream(this.body);......


本地测试正常,但是测试环境中,Content-Type传的是application/json;charset=UTF-8导致一直调用原来的getInputStream(),所以导致到达接口的时候body丢失