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 133212b5..fdd7138c 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,36 +5,79 @@ import org.slf4j.MDC; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.ThreadLocalRandom; /** * 日志追踪ID工具类 - * 统一生成 TraceId 并通过 MDC 传递,使同一请求/消息链路的所有日志可关联。 + * 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom * * @author Sumuxi */ public class TraceIdUtils { + /** + * TraceId在MDC中的key + */ public static final String TRACE_ID_KEY = "traceId"; /** - * 生成一个新的 TraceId(纯生成,不写入 MDC)。 - * 使用 32 位 UUID(去横线),全局唯一且无溢出风险。 + * IdWorker实例(单例模式) */ - public static String generateTraceId() { - return IdUtils.fastSimpleUUID(); + 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; } /** - * 生成 TraceId 并写入当前线程 MDC,返回生成的值。 + * 生成TraceId + * 格式:IdWorker.nextId() * 1000 + ThreadLocalRandom.nextInt(1000) + * 参考:generateClueId() 的实现方式 + * 使用Math.abs确保TraceId为正数 + * + * @return TraceId */ - public static String generateAndSetTraceId() { - String traceId = generateTraceId(); - MDC.put(TRACE_ID_KEY, traceId); + 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)); return traceId; } /** - * 设置指定 TraceId 到 MDC + * 生成TraceId并放入MDC + * 返回字符串格式的TraceId + * + * @return TraceId字符串 + */ + public static String generateTraceIdString() { + long traceId = generateTraceId(); + return String.valueOf(traceId); + } + + /** + * 设置TraceId到MDC + * + * @param traceId TraceId */ public static void setTraceId(String traceId) { if (traceId != null && !traceId.isEmpty()) { @@ -43,7 +86,18 @@ public class TraceIdUtils { } /** - * 从 MDC 获取当前线程的 TraceId + * 设置TraceId到MDC(long类型) + * + * @param traceId TraceId + */ + public static void setTraceId(long traceId) { + MDC.put(TRACE_ID_KEY, String.valueOf(traceId)); + } + + /** + * 从MDC获取TraceId + * + * @return TraceId */ public static String getTraceId() { return MDC.get(TRACE_ID_KEY); @@ -51,19 +105,20 @@ public class TraceIdUtils { /** * 确保当前线程 MDC 中存在 TraceId。 - * 已存在则返回,不存在则生成并写入 MDC。 + * - 已存在:直接返回 + * - 不存在:生成并写入 MDC */ public static String ensureTraceId() { String traceId = getTraceId(); if (traceId == null || traceId.isEmpty()) { - traceId = generateAndSetTraceId(); + traceId = generateTraceIdString(); } return traceId; } /** * 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。 - * 用于跨线程/跨进程透传后的落地。 + * 主要用于跨线程/跨进程透传后的落地。 */ public static String ensureTraceId(String traceId) { if (traceId == null || traceId.isEmpty()) { @@ -74,15 +129,7 @@ public class TraceIdUtils { } /** - * 清除 MDC 中的 TraceId - */ - public static void clearTraceId() { - MDC.remove(TRACE_ID_KEY); - } - - /** - * 包装 Runnable:透传提交线程的 MDC 上下文到执行线程, - * 无 traceId 则补齐;执行完自动还原,避免线程复用污染。 + * 包装 Runnable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。 */ public static Runnable wrapWithMdc(Runnable task) { final Map captured = MDC.getCopyOfContextMap(); @@ -107,8 +154,7 @@ public class TraceIdUtils { } /** - * 包装 Callable:透传提交线程的 MDC 上下文到执行线程, - * 无 traceId 则补齐;执行完自动还原,避免线程复用污染。 + * 包装 Callable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。 */ public static Callable wrapWithMdc(Callable task) { final Map captured = MDC.getCopyOfContextMap(); @@ -131,4 +177,12 @@ 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 442287cb..2ade13aa 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,6 +4,7 @@ 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; @@ -29,6 +30,8 @@ public class ResourcesConfig implements WebMvcConfigurer { private RepeatSubmitInterceptor repeatSubmitInterceptor; @Resource private LanguageInterceptor languageInterceptor; + @Autowired + private TraceIdInterceptor traceIdInterceptor; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { @@ -48,6 +51,8 @@ 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 6265d04b..f0691eb9 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,7 +2,6 @@ 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; @@ -19,7 +18,6 @@ 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; /** @@ -54,12 +52,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; - /** - * TraceId 过滤器(最高优先级,保证整个请求链路 TraceId 一致) - */ - @Autowired - private TraceIdFilter traceIdFilter; - /** * 跨域过滤器 */ @@ -160,8 +152,6 @@ 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 new file mode 100644 index 00000000..b892f4ea --- /dev/null +++ b/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java @@ -0,0 +1,51 @@ +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 deleted file mode 100644 index 9eaa8eff..00000000 --- a/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/TraceIdFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -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 d32f91b3..51fedb2e 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,7 +56,8 @@ public abstract class DeviceAbstractQuartzJob implements Job { protected void before(JobExecutionContext context, DeviceJob deviceJob) { threadLocal.set(new Date()); - TraceIdUtils.generateAndSetTraceId(); + // 为设备定时任务生成TraceId,使用统一的生成逻辑 + TraceIdUtils.generateTraceId(); 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 399c0909..c615a593 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,7 +56,8 @@ public abstract class AbstractQuartzJob implements Job { protected void before(JobExecutionContext context, SysJob sysJob) { threadLocal.set(new Date()); - TraceIdUtils.generateAndSetTraceId(); + // 为定时任务生成TraceId,使用统一的生成逻辑 + TraceIdUtils.generateTraceId(); log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup()); }