feat: OpenFeign请求增加来源服务标记

- FeignChainInterceptor新增X-Source-Service和X-Request-Time请求头
- ContextInterceptor提取并记录请求来源服务、计算链路耗时
- 日志格式: [Trace] sourceService -> targetService | traceId=xxx | chainTime=xxxms | path=xxx
This commit is contained in:
zhangjf 2026-02-20 11:36:47 +08:00
parent a8450d181f
commit 61c6e573df
2 changed files with 86 additions and 7 deletions

View File

@ -5,6 +5,7 @@ import com.fundplatform.common.context.TraceContextHolder;
import com.fundplatform.common.context.UserContextHolder;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
@ -12,6 +13,8 @@ import org.springframework.stereotype.Component;
*
* <p>职责在通过 OpenFeign 发起下游 HTTP 请求时统一透传
* <ul>
* <li>X-Source-Service请求来源服务名称调用方标识</li>
* <li>X-Request-Time请求发起时间戳毫秒</li>
* <li>X-Tenant-Id租户ID</li>
* <li>X-Uid当前用户ID</li>
* <li>X-Uname当前用户名</li>
@ -21,20 +24,34 @@ import org.springframework.stereotype.Component;
@Component
public class FeignChainInterceptor implements RequestInterceptor {
public static final String HEADER_SOURCE_SERVICE = "X-Source-Service";
public static final String HEADER_REQUEST_TIME = "X-Request-Time";
public static final String HEADER_TENANT_ID = "X-Tenant-Id";
public static final String HEADER_UID = "X-Uid";
public static final String HEADER_UNAME = "X-Uname";
public static final String HEADER_TRACE_ID = "X-Trace-Id";
/**
* 当前服务名称请求来源标识
*/
@Value("${spring.application.name:unknown}")
private String serviceName;
@Override
public void apply(RequestTemplate template) {
// 多租户透传
// 1. 请求来源服务标识调用方
template.header(HEADER_SOURCE_SERVICE, serviceName);
// 2. 请求发起时间戳用于计算链路耗时
template.header(HEADER_REQUEST_TIME, String.valueOf(System.currentTimeMillis()));
// 3. 多租户透传
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
template.header(HEADER_TENANT_ID, String.valueOf(tenantId));
}
// 用户信息透传
// 4. 用户信息透传
Long uid = UserContextHolder.getUserId();
if (uid != null) {
template.header(HEADER_UID, String.valueOf(uid));
@ -44,7 +61,7 @@ public class FeignChainInterceptor implements RequestInterceptor {
template.header(HEADER_UNAME, uname);
}
// TraceId 透传如不存在则生成
// 5. TraceId 透传如不存在则生成
String traceId = TraceContextHolder.getOrCreateTraceId();
template.header(HEADER_TRACE_ID, traceId);
}

View File

@ -5,22 +5,59 @@ import com.fundplatform.common.context.TraceContextHolder;
import com.fundplatform.common.context.UserContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 租户和用户上下文拦截器
* 从HTTP Header中提取租户ID和用户ID设置到ThreadLocal中
* 从HTTP Header中提取租户ID用户ID请求来源等信息设置到ThreadLocal中
* 同时记录请求来源和链路耗时信息
*/
@Component
public class ContextInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ContextInterceptor.class);
public static final String HEADER_SOURCE_SERVICE = "X-Source-Service";
public static final String HEADER_REQUEST_TIME = "X-Request-Time";
public static final String HEADER_TENANT_ID = "X-Tenant-Id";
public static final String HEADER_USER_ID = "X-User-Id";
public static final String HEADER_TRACE_ID = "X-Trace-Id";
/**
* 请求开始时间属性Key
*/
private static final String ATTR_START_TIME = "requestStartTime";
/**
* 当前服务名称
*/
@Value("${spring.application.name:unknown}")
private String serviceName;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 记录请求开始时间
request.setAttribute(ATTR_START_TIME, System.currentTimeMillis());
// 提取请求来源服务
String sourceService = request.getHeader(HEADER_SOURCE_SERVICE);
String requestTimeStr = request.getHeader(HEADER_REQUEST_TIME);
// 计算链路耗时如果有请求发起时间
Long chainDuration = null;
if (requestTimeStr != null && !requestTimeStr.isEmpty()) {
try {
long requestTime = Long.parseLong(requestTimeStr);
chainDuration = System.currentTimeMillis() - requestTime;
} catch (NumberFormatException e) {
// 忽略无效的时间戳
}
}
// 提取租户ID
String tenantIdStr = request.getHeader(HEADER_TENANT_ID);
if (tenantIdStr != null && !tenantIdStr.isEmpty()) {
@ -42,18 +79,43 @@ public class ContextInterceptor implements HandlerInterceptor {
// 忽略无效的用户 ID
}
}
// 提取 TraceId如不存在后续会在需要时自动生成
String traceId = request.getHeader(HEADER_TRACE_ID);
if (traceId != null && !traceId.isEmpty()) {
TraceContextHolder.setTraceId(traceId);
}
// 记录请求追踪日志
if (sourceService != null && chainDuration != null) {
logger.info("[Trace] {} -> {} | traceId={} | chainTime={}ms | path={}",
sourceService, serviceName, traceId, chainDuration, request.getRequestURI());
} else if (sourceService != null) {
logger.info("[Trace] {} -> {} | traceId={} | path={}",
sourceService, serviceName, traceId, request.getRequestURI());
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 计算当前服务处理耗时
Long startTime = (Long) request.getAttribute(ATTR_START_TIME);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
String traceId = request.getHeader(HEADER_TRACE_ID);
String sourceService = request.getHeader(HEADER_SOURCE_SERVICE);
logger.debug("[Trace] {} -> {} | traceId={} | processTime={}ms | status={}",
sourceService != null ? sourceService : "external",
serviceName,
traceId,
duration,
response.getStatus());
}
// 清理 ThreadLocal防止内存泄漏
TenantContextHolder.clear();
UserContextHolder.clear();