上面要求添加 traceId,需求很简单。
唯一的难点是,jakarta.servlet.http.HttpServletRequest 不支持直接 put 请求头。
所以需要创建一个可修改的对象,包装请求。
另外,由于我们应用还使用了 openFeign ,需要将 traceId 传递进去。
PS: 老大非要把appName用做traceId前缀,无语~
过滤器
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 追踪id过滤器
*
*/
@WebFilter(urlPatterns = "/*")
@Component
@Slf4j
public class TraceIdFilter extends HttpFilter {
@Value("${spring.application.name:appName}")
private String appName;
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
try {
// 添加 Header
String traceId = request.getHeader(HConst.TRACE_ID);
// 已存在则放行
if (StringUtils.isNotBlank(traceId)) {
// 放入日志
MDC.put(HConst.TRACE_ID, traceId);
response.setHeader(HConst.TRACE_ID,traceId);
chain.doFilter(request, response);
} else {
// 不存在则自生成
traceId = appName + "-" + UUIDGenerator.getUUID();
log.info("appGenerator traceId : {}",traceId);
// 推入请求
MutableHttpServletRequest muRequest = new MutableHttpServletRequest(request);
response.setHeader(HConst.TRACE_ID,traceId);
muRequest.putHeader(HConst.TRACE_ID, traceId);
// 放入日志
MDC.put(HConst.TRACE_ID, traceId);
chain.doFilter(muRequest, response);
}
} catch (Exception e) {
log.error("TRACE_id异常:", e);
throw new RuntimeException("TRACE_id异常");
}
}
}
可修改的请求适配器
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.util.*;
/**
* 可修改的请求对象
*
*/
public final class MutableHttpServletRequest extends HttpServletRequestWrapper {
/**
* 保持自定义头、值
*/
private final Map<String, String> customHeaders;
public MutableHttpServletRequest(HttpServletRequest request){
super(request);
this.customHeaders = new HashMap<>();
}
public void putHeader(String name, String value){
this.customHeaders.put(name, value);
}
@Override
public String getHeader(String name) {
// 检查
String headerValue = customHeaders.get(name);
if (headerValue != null){
return headerValue;
}
// 返回原头
return ((HttpServletRequest) getRequest()).getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
// 创建自定义头容器
Set<String> set = new HashSet<String>(customHeaders.keySet());
// 添加头
@SuppressWarnings("unchecked")
Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
while (e.hasMoreElements()) {
// 复制到新容器
String n = e.nextElement();
set.add(n);
}
// 重新构建并返回
return Collections.enumeration(set);
}
}
调用了 openFeign,将 traceId 传入代码
@Slf4j
@Component
public class CipherInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
try {
// 添加traceId
addTraceId(template);
byte[] body = template.body();
String bodyStr = new String(body, StandardCharsets.UTF_8);
// 打印请求消息
log.info("feign-request-path: {}", template.path());
log.info("feign-request-bodyStr : {}", bodyStr);
} catch (Exception e) {
log.error("CipherInterceptor error :", e);
}
}
/**
* 添加请求头
* @param template 请求对象
*/
private static void addTraceId(RequestTemplate template) {
Map<String, Collection<String>> headers = template.headers();
String traceId = ServletUtil.getHeader(HConst.TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = "feign-" + UUIDGenerator.getUUID();
}
headers.put(HConst.TRACE_ID, Collections.singletonList(traceId));
log.info("feign-request-headers: {}", headers);
}
}
其它代码
从请求上下文中获取请求头方法,生成 UUID
public static String getHeader(String name){
// 获取请求头信息
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (requestAttributes != null) {
request = requestAttributes.getRequest();
return request.getHeader(name);
}
return null;
}
public static String getUUID() {
String s = UUID.randomUUID().toString();
return s.replaceAll("-", "");
}