From 24c11201054a083a33e3f3c5aed66291891d197c Mon Sep 17 00:00:00 2001 From: zhugongping Date: Sun, 29 Mar 2026 11:31:31 +0800 Subject: [PATCH] =?UTF-8?q?20260326:traceId=E9=93=BE=E8=B7=AF=E8=BF=BD?= =?UTF-8?q?=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fastbee/common/utils/TraceIdUtils.java | 104 +++++------------- .../framework/config/ResourcesConfig.java | 5 - .../framework/config/SecurityConfig.java | 10 ++ .../interceptor/TraceIdInterceptor.java | 51 --------- .../security/filter/TraceIdFilter.java | 48 ++++++++ .../iot/data/job/DeviceAbstractQuartzJob.java | 3 +- .../quartz/util/AbstractQuartzJob.java | 3 +- 7 files changed, 85 insertions(+), 139 deletions(-) delete mode 100644 fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java create mode 100644 fastbee-framework/src/main/java/com/fastbee/framework/security/filter/TraceIdFilter.java diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java index fdd7138c..133212b5 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java @@ -5,79 +5,36 @@ import org.slf4j.MDC; import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.ThreadLocalRandom; /** * 日志追踪ID工具类 - * 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom + * 统一生成 TraceId 并通过 MDC 传递,使同一请求/消息链路的所有日志可关联。 * * @author Sumuxi */ public class TraceIdUtils { - /** - * TraceId在MDC中的key - */ public static final String TRACE_ID_KEY = "traceId"; /** - * IdWorker实例(单例模式) + * 生成一个新的 TraceId(纯生成,不写入 MDC)。 + * 使用 32 位 UUID(去横线),全局唯一且无溢出风险。 */ - private static volatile IdUtils idWorker; - - /** - * 获取IdWorker实例(懒加载单例) - * - * @return IdWorker实例 - */ - private static IdUtils getIdWorker() { - if (idWorker == null) { - synchronized (TraceIdUtils.class) { - if (idWorker == null) { - // 使用机器ID,可以根据实际情况配置 - // 这里使用1作为默认workerId,可以通过配置文件读取 - // IdUtils构造函数:workerId, datacenterId,-1表示随机生成 - idWorker = new IdUtils(-1L, -1L); - } - } - } - return idWorker; + public static String generateTraceId() { + return IdUtils.fastSimpleUUID(); } /** - * 生成TraceId - * 格式:IdWorker.nextId() * 1000 + ThreadLocalRandom.nextInt(1000) - * 参考:generateClueId() 的实现方式 - * 使用Math.abs确保TraceId为正数 - * - * @return TraceId + * 生成 TraceId 并写入当前线程 MDC,返回生成的值。 */ - public static long generateTraceId() { - long id = getIdWorker().nextId(); - // 使用绝对值确保TraceId为正数,避免负数显示 - // 注意:Math.abs(id) * 1000 可能会溢出,所以需要对最终结果再次使用Math.abs() - long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000); - // 确保最终结果为非负数(处理溢出情况) - traceId = Math.abs(traceId); - MDC.put(TRACE_ID_KEY, String.valueOf(traceId)); + public static String generateAndSetTraceId() { + String traceId = generateTraceId(); + MDC.put(TRACE_ID_KEY, traceId); return traceId; } /** - * 生成TraceId并放入MDC - * 返回字符串格式的TraceId - * - * @return TraceId字符串 - */ - public static String generateTraceIdString() { - long traceId = generateTraceId(); - return String.valueOf(traceId); - } - - /** - * 设置TraceId到MDC - * - * @param traceId TraceId + * 设置指定 TraceId 到 MDC */ public static void setTraceId(String traceId) { if (traceId != null && !traceId.isEmpty()) { @@ -86,18 +43,7 @@ public class TraceIdUtils { } /** - * 设置TraceId到MDC(long类型) - * - * @param traceId TraceId - */ - public static void setTraceId(long traceId) { - MDC.put(TRACE_ID_KEY, String.valueOf(traceId)); - } - - /** - * 从MDC获取TraceId - * - * @return TraceId + * 从 MDC 获取当前线程的 TraceId */ public static String getTraceId() { return MDC.get(TRACE_ID_KEY); @@ -105,20 +51,19 @@ public class TraceIdUtils { /** * 确保当前线程 MDC 中存在 TraceId。 - * - 已存在:直接返回 - * - 不存在:生成并写入 MDC + * 已存在则返回,不存在则生成并写入 MDC。 */ public static String ensureTraceId() { String traceId = getTraceId(); if (traceId == null || traceId.isEmpty()) { - traceId = generateTraceIdString(); + traceId = generateAndSetTraceId(); } return traceId; } /** * 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。 - * 主要用于跨线程/跨进程透传后的落地。 + * 用于跨线程/跨进程透传后的落地。 */ public static String ensureTraceId(String traceId) { if (traceId == null || traceId.isEmpty()) { @@ -129,7 +74,15 @@ public class TraceIdUtils { } /** - * 包装 Runnable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。 + * 清除 MDC 中的 TraceId + */ + public static void clearTraceId() { + MDC.remove(TRACE_ID_KEY); + } + + /** + * 包装 Runnable:透传提交线程的 MDC 上下文到执行线程, + * 无 traceId 则补齐;执行完自动还原,避免线程复用污染。 */ public static Runnable wrapWithMdc(Runnable task) { final Map captured = MDC.getCopyOfContextMap(); @@ -154,7 +107,8 @@ public class TraceIdUtils { } /** - * 包装 Callable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。 + * 包装 Callable:透传提交线程的 MDC 上下文到执行线程, + * 无 traceId 则补齐;执行完自动还原,避免线程复用污染。 */ public static Callable wrapWithMdc(Callable task) { final Map captured = MDC.getCopyOfContextMap(); @@ -177,12 +131,4 @@ public class TraceIdUtils { } }; } - - /** - * 清除MDC中的TraceId - */ - public static void clearTraceId() { - MDC.remove(TRACE_ID_KEY); - } } - diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java b/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java index 2ade13aa..442287cb 100644 --- a/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java +++ b/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java @@ -4,7 +4,6 @@ import com.fastbee.common.config.RuoYiConfig; import com.fastbee.common.constant.Constants; import com.fastbee.framework.interceptor.LanguageInterceptor; import com.fastbee.framework.interceptor.RepeatSubmitInterceptor; -import com.fastbee.framework.interceptor.TraceIdInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,8 +29,6 @@ public class ResourcesConfig implements WebMvcConfigurer { private RepeatSubmitInterceptor repeatSubmitInterceptor; @Resource private LanguageInterceptor languageInterceptor; - @Autowired - private TraceIdInterceptor traceIdInterceptor; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { @@ -51,8 +48,6 @@ public class ResourcesConfig implements WebMvcConfigurer { */ @Override public void addInterceptors(InterceptorRegistry registry) { - // TraceId拦截器放在最前面,确保所有请求都有TraceId - registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**").order(0); registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); //这里配置国际化拦截器的白名单 registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs", diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java b/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java index f0691eb9..6265d04b 100644 --- a/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java +++ b/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java @@ -2,6 +2,7 @@ package com.fastbee.framework.config; import com.fastbee.framework.config.properties.PermitAllUrlProperties; import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter; +import com.fastbee.framework.security.filter.TraceIdFilter; import com.fastbee.framework.security.handle.AuthenticationEntryPointImpl; import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.web.filter.CorsFilter; /** @@ -52,6 +54,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; + /** + * TraceId 过滤器(最高优先级,保证整个请求链路 TraceId 一致) + */ + @Autowired + private TraceIdFilter traceIdFilter; + /** * 跨域过滤器 */ @@ -152,6 +160,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { // 添加CORS filter httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + // TraceId filter 放在 Security 过滤链最前面,确保所有日志都有同一个 TraceId + httpSecurity.addFilterBefore(traceIdFilter, SecurityContextPersistenceFilter.class); } /** diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java b/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java deleted file mode 100644 index b892f4ea..00000000 --- a/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fastbee.framework.interceptor; - -import com.fastbee.common.utils.TraceIdUtils; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 日志追踪ID拦截器 - * 用于生成和传递TraceId,方便同一个链路调用追查问题 - * - * @author Sumuxi - */ -@Component -public class TraceIdInterceptor implements HandlerInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - // 获取请求URI - String requestURI = request.getRequestURI(); - - // 尝试从请求头中获取TraceId(支持分布式追踪) - String traceId = request.getHeader("X-Trace-Id"); - - // 如果请求头中没有TraceId,则生成一个新的 - if (traceId == null || traceId.isEmpty()) { - traceId = TraceIdUtils.generateTraceIdString(); - } else { - // 如果请求头中有TraceId,设置到MDC - TraceIdUtils.setTraceId(traceId); - } - - // 将TraceId放入响应头,方便前端获取 - response.setHeader("X-Trace-Id", traceId); - - // 打印请求路径日志 - org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TraceIdInterceptor.class); - log.info("拦截请求路径: {}", requestURI); - - return true; - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { - // 请求完成后清除MDC中的TraceId,避免内存泄漏 - TraceIdUtils.clearTraceId(); - } -} - diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/TraceIdFilter.java b/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/TraceIdFilter.java new file mode 100644 index 00000000..9eaa8eff --- /dev/null +++ b/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/TraceIdFilter.java @@ -0,0 +1,48 @@ +package com.fastbee.framework.security.filter; + +import com.fastbee.common.utils.TraceIdUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * TraceId 过滤器 — 替代原 TraceIdInterceptor。 + *

+ * 作为 Servlet Filter 注册在 Spring Security 过滤链最前端, + * 保证整个请求生命周期(包括 Security 过滤器、JWT 验证等)的日志 + * 都携带同一个 TraceId。 + * + * @author Sumuxi + */ +@Component +public class TraceIdFilter extends OncePerRequestFilter { + + private static final String TRACE_HEADER = "X-Trace-Id"; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + try { + String traceId = request.getHeader(TRACE_HEADER); + if (traceId == null || traceId.isEmpty()) { + traceId = TraceIdUtils.generateAndSetTraceId(); + } else { + TraceIdUtils.setTraceId(traceId); + } + + response.setHeader(TRACE_HEADER, traceId); + + filterChain.doFilter(request, response); + } finally { + TraceIdUtils.clearTraceId(); + } + } +} diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java index 51fedb2e..d32f91b3 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java @@ -56,8 +56,7 @@ public abstract class DeviceAbstractQuartzJob implements Job { protected void before(JobExecutionContext context, DeviceJob deviceJob) { threadLocal.set(new Date()); - // 为设备定时任务生成TraceId,使用统一的生成逻辑 - TraceIdUtils.generateTraceId(); + TraceIdUtils.generateAndSetTraceId(); log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup()); } diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java index c615a593..399c0909 100644 --- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java +++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java @@ -56,8 +56,7 @@ public abstract class AbstractQuartzJob implements Job { protected void before(JobExecutionContext context, SysJob sysJob) { threadLocal.set(new Date()); - // 为定时任务生成TraceId,使用统一的生成逻辑 - TraceIdUtils.generateTraceId(); + TraceIdUtils.generateAndSetTraceId(); log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup()); }