feat: 添加traceId;记录请求和响应数据

This commit is contained in:
曹鹏飞 2025-05-30 11:13:21 +08:00
parent 64079598ef
commit 437787ca44
1 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,135 @@
package com.nflg.mobilebroken.starter.filter;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.mobilebroken.common.util.AdminUserUtil;
import com.nflg.mobilebroken.common.util.AppUserUtil;
import com.nflg.mobilebroken.common.util.SaTokenAdminUtil;
import com.nflg.mobilebroken.common.util.SaTokenAppUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
public class TraceIdFilter extends OncePerRequestFilter {
private static final String TRACE_ID_HEADER = "X-Trace-Id";
private static final String MDC_TRACE_ID = "traceId";
// 需要跳过的二进制内容类型
private static final List<String> BINARY_CONTENT_TYPES = Arrays.asList("image", "video", "audio", "stream", "pdf", "zip", "excel");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
try {
// 从请求头获取或生成Trace ID
String traceId = requestWrapper.getHeader(TRACE_ID_HEADER);
if (StrUtil.isBlank(traceId)) {
traceId = IdUtil.getSnowflakeNextIdStr();
}
// 存入MDC和响应头
MDC.put(MDC_TRACE_ID, traceId);
responseWrapper.addHeader(TRACE_ID_HEADER, traceId);
filterChain.doFilter(requestWrapper, responseWrapper);
} finally {
logRequest(requestWrapper);
logResponse(responseWrapper);
responseWrapper.copyBodyToResponse();
// 请求结束时清除MDC
MDC.remove(MDC_TRACE_ID);
}
}
private void logResponse(ContentCachingResponseWrapper response) {
log.debug("【HTTP Response】");
log.debug("STATUS : {}", response.getStatus());
log.debug("BODY : {}", getResponseBody(response));
}
private String getResponseBody(ContentCachingResponseWrapper response) {
if (shouldSkipBodyLogging(response.getContentType())){
return "[不记录]"+response.getContentType();
}
byte[] buf = response.getContentAsByteArray();
if (buf.length > 0) {
return new String(buf, StandardCharsets.UTF_8);
}
return "[无数据]";
}
private void logRequest(ContentCachingRequestWrapper request) {
log.debug("【HTTP Request】");
log.debug("URL : {}", request.getRequestURL().toString());
log.debug("METHOD : {}", request.getMethod());
log.debug("HEADERS: {}", getHeadersAsString(request));
log.debug("USER : {}", getUserInfo(request));
log.debug("BODY : {}", getRequestBody(request));
}
private String getUserInfo(ContentCachingRequestWrapper request) {
String token=request.getHeader("authorization");
if (StrUtil.isBlank(token)){
return "未登录";
}
if (SaTokenAdminUtil.isLogin()) {
return StrUtil.format("id:{},姓名:{},邮箱:{}"
, AdminUserUtil.getUserId()
, AdminUserUtil.getUserName()
, AdminUserUtil.getEmail());
}
if (SaTokenAppUtil.isLogin()) {
return StrUtil.format("id:{},姓名:{},邮箱:{},是否主账号:{}"
, AppUserUtil.getUserId()
, AppUserUtil.getUserName()
, AppUserUtil.getEmail()
, AppUserUtil.isPrimary());
}
return "无效token或已过期";
}
private String getRequestBody(ContentCachingRequestWrapper request) {
if (shouldSkipBodyLogging(request.getContentType())){
return "[不记录]"+request.getContentType();
}
byte[] buf = request.getContentAsByteArray();
if (buf.length > 0) {
return new String(buf, StandardCharsets.UTF_8).replaceAll("\\s", "");
}
return "[无数据]";
}
private String getHeadersAsString(HttpServletRequest request) {
StringBuilder headers = new StringBuilder();
request.getHeaderNames().asIterator().forEachRemaining(headerName ->
headers.append(headerName)
.append("=")
.append(request.getHeader(headerName))
.append("; ")
);
return headers.toString();
}
private boolean shouldSkipBodyLogging(String contentType) {
if (contentType == null) return false;
return BINARY_CONTENT_TYPES.stream().anyMatch(contentType::contains);
}
}