Revert "20260326:traceId链路追踪"

This reverts commit 24c1120105.
This commit is contained in:
George 2026-03-29 11:40:07 +08:00
parent 24c1120105
commit acc9768619
7 changed files with 139 additions and 85 deletions

View File

@ -5,36 +5,79 @@ import org.slf4j.MDC;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* 日志追踪ID工具类 * 日志追踪ID工具类
* 统一生成 TraceId 并通过 MDC 传递,使同一请求/消息链路的所有日志可关联。 * 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom
* *
* @author Sumuxi * @author Sumuxi
*/ */
public class TraceIdUtils { public class TraceIdUtils {
/**
* TraceId在MDC中的key
*/
public static final String TRACE_ID_KEY = "traceId"; public static final String TRACE_ID_KEY = "traceId";
/** /**
* 生成一个新的 TraceId(纯生成,不写入 MDC)。 * IdWorker实例(单例模式)
* 使用 32 位 UUID(去横线),全局唯一且无溢出风险。
*/ */
public static String generateTraceId() { private static volatile IdUtils idWorker;
return IdUtils.fastSimpleUUID();
/**
* 获取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() { public static long generateTraceId() {
String traceId = generateTraceId(); long id = getIdWorker().nextId();
MDC.put(TRACE_ID_KEY, traceId); // 使用绝对值确保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; 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) { public static void setTraceId(String traceId) {
if (traceId != null && !traceId.isEmpty()) { 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() { public static String getTraceId() {
return MDC.get(TRACE_ID_KEY); return MDC.get(TRACE_ID_KEY);
@ -51,19 +105,20 @@ public class TraceIdUtils {
/** /**
* 确保当前线程 MDC 中存在 TraceId。 * 确保当前线程 MDC 中存在 TraceId。
* 已存在则返回,不存在则生成并写入 MDC。 * - 已存在:直接返回
* - 不存在:生成并写入 MDC
*/ */
public static String ensureTraceId() { public static String ensureTraceId() {
String traceId = getTraceId(); String traceId = getTraceId();
if (traceId == null || traceId.isEmpty()) { if (traceId == null || traceId.isEmpty()) {
traceId = generateAndSetTraceId(); traceId = generateTraceIdString();
} }
return traceId; return traceId;
} }
/** /**
* 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。 * 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。
* 用于跨线程/跨进程透传后的落地。 * 主要用于跨线程/跨进程透传后的落地。
*/ */
public static String ensureTraceId(String traceId) { public static String ensureTraceId(String traceId) {
if (traceId == null || traceId.isEmpty()) { if (traceId == null || traceId.isEmpty()) {
@ -74,15 +129,7 @@ public class TraceIdUtils {
} }
/** /**
* 清除 MDC 中的 TraceId * 包装 Runnable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
*/
public static void clearTraceId() {
MDC.remove(TRACE_ID_KEY);
}
/**
* 包装 Runnable:透传提交线程的 MDC 上下文到执行线程,
* 无 traceId 则补齐;执行完自动还原,避免线程复用污染。
*/ */
public static Runnable wrapWithMdc(Runnable task) { public static Runnable wrapWithMdc(Runnable task) {
final Map<String, String> captured = MDC.getCopyOfContextMap(); final Map<String, String> captured = MDC.getCopyOfContextMap();
@ -107,8 +154,7 @@ public class TraceIdUtils {
} }
/** /**
* 包装 Callable:透传提交线程的 MDC 上下文到执行线程, * 包装 Callable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
* 无 traceId 则补齐;执行完自动还原,避免线程复用污染。
*/ */
public static <T> Callable<T> wrapWithMdc(Callable<T> task) { public static <T> Callable<T> wrapWithMdc(Callable<T> task) {
final Map<String, String> captured = MDC.getCopyOfContextMap(); final Map<String, String> captured = MDC.getCopyOfContextMap();
@ -131,4 +177,12 @@ public class TraceIdUtils {
} }
}; };
} }
/**
* 清除MDC中的TraceId
*/
public static void clearTraceId() {
MDC.remove(TRACE_ID_KEY);
}
} }

View File

@ -4,6 +4,7 @@ import com.fastbee.common.config.RuoYiConfig;
import com.fastbee.common.constant.Constants; import com.fastbee.common.constant.Constants;
import com.fastbee.framework.interceptor.LanguageInterceptor; import com.fastbee.framework.interceptor.LanguageInterceptor;
import com.fastbee.framework.interceptor.RepeatSubmitInterceptor; import com.fastbee.framework.interceptor.RepeatSubmitInterceptor;
import com.fastbee.framework.interceptor.TraceIdInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -29,6 +30,8 @@ public class ResourcesConfig implements WebMvcConfigurer {
private RepeatSubmitInterceptor repeatSubmitInterceptor; private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Resource @Resource
private LanguageInterceptor languageInterceptor; private LanguageInterceptor languageInterceptor;
@Autowired
private TraceIdInterceptor traceIdInterceptor;
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
@ -48,6 +51,8 @@ public class ResourcesConfig implements WebMvcConfigurer {
*/ */
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// TraceId拦截器放在最前面,确保所有请求都有TraceId
registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**").order(0);
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
//这里配置国际化拦截器的白名单 //这里配置国际化拦截器的白名单
registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs", registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs",

View File

@ -2,7 +2,6 @@ package com.fastbee.framework.config;
import com.fastbee.framework.config.properties.PermitAllUrlProperties; import com.fastbee.framework.config.properties.PermitAllUrlProperties;
import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter; 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.AuthenticationEntryPointImpl;
import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl; import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired; 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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
/** /**
@ -54,12 +52,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter; private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* TraceId 过滤器(最高优先级,保证整个请求链路 TraceId 一致)
*/
@Autowired
private TraceIdFilter traceIdFilter;
/** /**
* 跨域过滤器 * 跨域过滤器
*/ */
@ -160,8 +152,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 添加CORS filter // 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
// TraceId filter 放在 Security 过滤链最前面,确保所有日志都有同一个 TraceId
httpSecurity.addFilterBefore(traceIdFilter, SecurityContextPersistenceFilter.class);
} }
/** /**

View File

@ -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();
}
}

View File

@ -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。
* <p>
* 作为 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();
}
}
}

View File

@ -56,7 +56,8 @@ public abstract class DeviceAbstractQuartzJob implements Job {
protected void before(JobExecutionContext context, DeviceJob deviceJob) { protected void before(JobExecutionContext context, DeviceJob deviceJob) {
threadLocal.set(new Date()); threadLocal.set(new Date());
TraceIdUtils.generateAndSetTraceId(); // 为设备定时任务生成TraceId,使用统一的生成逻辑
TraceIdUtils.generateTraceId();
log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup()); log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup());
} }

View File

@ -56,7 +56,8 @@ public abstract class AbstractQuartzJob implements Job {
protected void before(JobExecutionContext context, SysJob sysJob) { protected void before(JobExecutionContext context, SysJob sysJob) {
threadLocal.set(new Date()); threadLocal.set(new Date());
TraceIdUtils.generateAndSetTraceId(); // 为定时任务生成TraceId,使用统一的生成逻辑
TraceIdUtils.generateTraceId();
log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup()); log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup());
} }