必须手动从c.Locals取出trace_id、user_id,用http.NewRequestWithContext()构造新请求并注入header;Fiber不自动透传,复用原始*http.Request或仅设header不换context会导致透传失败。

中间件里怎么把 trace_id、user_id 塞进下游 HTTP 请求
必须手动从 c.Locals 取出上下文,再用 http.NewRequestWithContext() 构造新请求并注入 header,Fiber 不会自动透传任何跨服务字段。
常见错误是直接复用 c.Request() 的原始 *http.Request,它没带 context,下游收不到 trace 或用户信息;或者用 req.Header.Set() 但忘了传 context,导致 OpenTelemetry span 断链。
- 先确保上游中间件已把 span context 存进
c.Locals["span_ctx"](如 OtelMiddleware 所做) - 下游调用前,从
c.Locals拿出spanCtx,再用otel.GetTextMapPropagator().Inject(spanCtx, propagation.HeaderCarrier(req.Header))注入 header - 必须用
http.NewRequestWithContext(spanCtx, ...)创建请求,不能只改 header 而不换 context - 若还需透传业务字段(如
X-User-ID),得额外手动req.Header.Set("X-User-ID", userID),OpenTelemetry 不管这个
为什么不能靠全局变量或 static 缓存 user_id
因为 Fiber 是协作式协程,多个请求共用同一进程内存空间,static $user_id 或包级变量 var currentUserID string 会被不同 Fiber 交叉覆盖——A 请求刚写入,B 请求就覆写了,A 发出去的请求 header 里就是 B 的 user_id。
PHP 和 Go 的 Fiber 实现都共享这一约束:局部变量安全,全局状态危险。所谓“协程安全”只针对栈内变量,不延伸到静态/全局作用域。
- 绝对不要在中间件里写
global $currentUserID; $currentUserID = $id - 禁用
app.Use(func(c *fiber.Ctx) { c.Locals["user_id"] = extractFromToken(c) })后,在任意 handler 里用app.Get("/order", func(c *fiber.Ctx) { http.Post(..., "X-User-ID: "+c.Locals["user_id"].(string)) })—— 这看似可行,但一旦 handler 内部启动 goroutine/fiber,c.Locals就可能被下一个请求覆盖 - 真正安全的做法:每个跨服务调用前,从当前
c.Locals即时读取,并立即注入新请求,不缓存、不跨函数传递引用
Service 层如何接收并使用透传的上下文
Service 方法签名里不能出现 *fiber.Ctx,但可以接收 context.Context 和显式传入的 header map 或结构体。这是解耦的关键分界点。
比如订单创建逻辑要记录操作人,就不能让 OrderService.Create() 自己去解析 token,而应由 handler 提取后作为参数传入:
func (s *OrderService) Create(ctx context.Context, userID string, orderReq CreateOrderReq) error {
// ctx 已含 span,可用于 otel.Tracer("order").Start(ctx, "create")
// userID 是 handler 从 c.Locals 或 jwt 解析后传来的,不依赖任何框架类型
return s.db.WithContext(ctx).Create(&order).Error
}
- handler 负责从
c.Locals["user_id"]或c.Get("Authorization")解析,转成原始值 - Service 只认
string、int64、context.Context这类标准类型,不碰fiber.Ctx或http.Header - 如果 Service 内部还要发 HTTP 请求(比如查用户余额),它必须把
ctx和userID一并传给它的下游 client 方法,而不是回头找c.Locals
CORS 和 credential 场景下 header 透传容易漏掉什么
当跨域请求带 credentials: true(如 Cookie 或 Authorization 头),浏览器会拒绝响应里含 Access-Control-Allow-Origin: "*" 的响应——这会导致你注入的 X-Trace-ID 根本到不了前端,更别说透传了。
但问题不止于此:即使后端正确返回了 X-Trace-ID,前端 fetch 时若没设 credentials: "include",header 就不会出现在 response.headers 中,自然无法在下次请求中带上。
- 后端 CORS 配置必须显式设置
AllowOrigins: []string{"https://myapp.com"},不能是"*" - 必须开启
AllowCredentials: true,否则浏览器不发送 Cookie,token 解析失败 - 必须在
ExposeHeaders中列出你要透传的字段,例如ExposeHeaders: []string{"X-Trace-ID", "X-User-ID"},否则 JS 无法读取 - 前端 fetch 要配
{ credentials: "include", headers: { "X-Trace-ID": id } },缺一不可
这些配置项之间是强依赖关系,漏掉任何一个,上下文透传链就在那一环断开,且错误表现往往是静默失败——没有报错,只是 downstream 收不到值。


















