From 437787ca4439b83c93e22f298b3dbe2e43707c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E9=B9=8F=E9=A3=9E?= Date: Fri, 30 May 2025 11:13:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0traceId=EF=BC=9B?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=AF=B7=E6=B1=82=E5=92=8C=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starter/filter/TraceIdFilter.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 nflg-mobilebroken-starter/src/main/java/com/nflg/mobilebroken/starter/filter/TraceIdFilter.java diff --git a/nflg-mobilebroken-starter/src/main/java/com/nflg/mobilebroken/starter/filter/TraceIdFilter.java b/nflg-mobilebroken-starter/src/main/java/com/nflg/mobilebroken/starter/filter/TraceIdFilter.java new file mode 100644 index 00000000..69a54aa0 --- /dev/null +++ b/nflg-mobilebroken-starter/src/main/java/com/nflg/mobilebroken/starter/filter/TraceIdFilter.java @@ -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 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); + } +}