为什么要保存请求参数

在springboot中,前端请求发送到后端,其中的数据是通过字节流的形式发送的,默认情况下只能读取一次。然而,在项目中,我们会有多种原因对数据进行解析、校验,其中包括但不局限于以下功能:

  • 对敏感词语进行屏蔽
  • 前后端的session校验
  • 数据的脱敏

因此,我们可能在interceptor中提前获取数据,并在interceptor、controller中使用。在这种情况下,我们需要将前端请求的数据保存下来,方面后续交易过程中的数据使用。

如何保存请求参数

我们通过封装RequestWrapper类,实现将数据放入字段body中,并在filter层调用方法进行设置。

封装RequestWrapper

该功能是一个所有的请求均适用的功能,那么在springboot中,我们可以通过切面,在Filter中完成该功能。 其中,我们需要定制一个新的继承HttpServletRequestWrapper的类,该类可以读取请求的字节流,并保存到改类中的某个字段(比如body)。 其中,该类可以如下定义:

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
    
    // 保存请求的参数
    private String body;
  
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        // 保存请求的数据流
        try {
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = 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 byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }

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

    public String getBody() {
        return this.body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

修改Filter代码

在定义完RequestWrapper后,我们可以在Filter中配合使用RequestWrapper,从而保存请求的参数。如果Filter未引入项目中,则可以参考springboot--过滤器功能

在引入Filter后,我们可以在chain.doFilter()函数前面添加代码。具体地,修改后的代码为:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    long now = System.currentTimeMillis();
    String requestURI = ((HttpServletRequest) request).getRequestURI();
    try {
        // 修改代码行 开始
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
        // 修改代码行 结束
    }finally {
        // 输出每一笔交易的耗时
        log.info("requestUrl: {}, responseTime: {}ms", requestURI, System.currentTimeMillis()-now);
    }
}

其中,代码行RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);会将request的请求参数获取并保存在body中。后续具体的使用数据可以通过request.getBody()获得。

最终我们在封装的request中可以看到请求的参数: alt