本文介绍的拦截器是基于 Filter 的实现,没有使用 spring AOP 切面的技术
场景:在实际开发中,部分接口是通过 jar 包引入的,由于 Pointcut 切入的是工程包内的方法,所以引入的接口并不好覆盖。例如引入 spring security 后拥有的 /oauth/token 请求
传参
自定义包装类
- 继承:HttpServletRequest 有个特性,就是 getReader() 方法获取一次之后,便无法再次获取,所以这里使用包装类,继承 HttpServletRequestWrapper
- 构造方法:传入 HttpServletRequest ,判断是否文件流,是则不做处理
- 获取 body:读取流 getReader(),获取 body 传参
- 获取 params:通过自定义方法 getParams() 获取,使用了 cn.hutool.json 的 JSONObject 类
import cn.hutool.json.JSONObject;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
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.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class RequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private Map<String, String[]> paramMap;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
if (ServletFileUpload.isMultipartContent(request)) {
return;
}
StringBuilder sb = new StringBuilder();
String line;
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
this.body = body.getBytes(StandardCharsets.UTF_8);
this.paramMap=request.getParameterMap();
}
public String getBody() {
return new String(this.body, StandardCharsets.UTF_8);
}
@Override
public ServletInputStream getInputStream() {
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();
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getParams() {
Map<String, String[]> map = this.paramMap;
JSONObject obj = new JSONObject();
if (Optional.ofNullable(map).isPresent()) {
Set<String> set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
String[] arr = map.get(key);
if (Optional.ofNullable(arr).isPresent()) {
for (String v : arr) {
obj.putOnce((String) key, v);
}
}
}
}
return obj.toString();
}
}
返回值
自定义包装类
- 继承:继承 HttpServletResponseWrapper
- 构造方法:传入 HttpServletResponse
- 获取返回值字节流:getContent()
- 获取 params:通过自定义方法 getParams() 获取,使用了 cn.hutool.json 的 JSONObject 类
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public ResponseWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
public String getResponseContent() {
return new String(buffer.toByteArray());
}
class WrapperOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
}
}
}
过滤器
对于一般的传入参记录,我们需要记录以下字段
- 接口 uri
- 接口方法 method
- 传参 params,即问号传参
- 传参 body,即 Content-Type = application/json
自定义过滤器,实现接口 Filter
- urlPatterns:过滤相应规则的 url,可设置多个,传参为字符串数组
- POINT:url 含有字符串点的不过滤,例如脚本、图片(.jsp、.jpg) 等
- doFilter:传入 RequestWrapper 和 ResponseWrapper
- ServletResponse:返回输出流,若不设置,返回值为空
import cn.hutool.json.JSONUtil;
import com.njc.java.property.KafkaTopicProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
@WebFilter(filterName = "channelFilter", urlPatterns = {"/*"})
public class ChannelFilter implements Filter {
private static final String POINT = ".";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String uri = httpRequest.getRequestURI();
if (uri.contains(POINT)) {
try {
chain.doFilter(request, response);
} catch (Exception e) {
log.error(e.getMessage());
}
} else {
try {
RequestWrapper requestWrapper = new RequestWrapper(httpRequest);
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
String method = httpRequest.getMethod();
String paramBody = requestWrapper.getBody();
String param = requestWrapper.getParams();
chain.doFilter(requestWrapper, responseWrapper);
String msgStr = responseWrapper.getResponseContent();
ServletOutputStream out = response.getOutputStream();
out.write(responseWrapper.getContent());
out.flush();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
}
日志收集
- 一般做法可以把日志收集到当前服务的数据库中
- 若使用微服务架构开发,建议规划日志收集服务,通过 kafka 统一收集日志,用 MongoDB 存储日志信息