在spring中,通常可以使用切面编程方式对web请求记录操作日志。但是这种方式存在一个问题,那就是只能记录url中的请求参数,无法记录POST或者PUT请求的报文体,因为报文体是放在request对象的InputStream中的,只能读取一次。解决方法就是利用HttpServletRequestWrapper先读取InputStream,记录到一个头参数中,然后再重新放到InputStream中去。
代码如下:
先创建一个WrappedHttpServletRequest类:
import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class WrappedHttpServletRequest extends HttpServletRequestWrapper { private byte[] bytes; private WrappedServletInputStream wrappedServletInputStream; public WrappedHttpServletRequest(HttpServletRequest request) throws IOException { super(request); // 读取输入流里的请求参数,并保存到bytes里 bytes = IOUtils.toByteArray(request.getInputStream()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); this.wrappedServletInputStream = new WrappedServletInputStream(byteArrayInputStream); // 很重要,把post参数重新写入请求流 reWriteInputStream(); } /** * 把参数重新写进请求里 */ public void reWriteInputStream() { wrappedServletInputStream .setStream(new ByteArrayInputStream(bytes != null ? bytes : new byte[0])); } @Override public ServletInputStream getInputStream() throws IOException { return wrappedServletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(wrappedServletInputStream)); } /** * 获取post参数,可以自己再转为相应格式 */ public String getRequestParams() throws IOException { return new String(bytes, this.getCharacterEncoding()); } private class WrappedServletInputStream extends ServletInputStream { public void setStream(InputStream stream) { this.stream = stream; } private InputStream stream; public WrappedServletInputStream(InputStream stream) { this.stream = stream; } @Override public int read() throws IOException { return stream.read(); } @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} } }
再创建一个LogFilter对象:
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Component @WebFilter(value = "/*", filterName = "logFilter") @Slf4j public class LogFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { WrappedHttpServletRequest requestWrapper = new WrappedHttpServletRequest((HttpServletRequest) request); // 获取请求参数 String requestBody = requestWrapper.getRequestParams(); if (!StringUtils.isBlank(requestBody)) { if (requestBody.length() >= 8192) { requestBody = requestBody.substring(0, 8192); } request.setAttribute("request-body", requestBody); // 这里创建一个参数头,把要记录的报文放到参数头里面,在切面中读取这个参数头 } // 这里doFilter传入我们实现的子类 chain.doFilter(requestWrapper, response); } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public void destroy() {} }