ctx.Locals是Fiber中唯一安全的请求级数据传递方式,线程安全、生命周期绑定请求、自动清空;不可用全局变量、闭包或context.WithValue替代,业务数据必须走Locals,超时控制等才用c.Context()。

ctx.Locals 是唯一安全的请求级数据传递方式
别用全局变量、闭包或 context.WithValue,Fiber 的 ctx.Locals 是专为单次请求设计的线程安全存储。它底层是 map[string]any + sync.RWMutex,且随请求结束自动清空,不会跨请求污染。
常见错误:在中间件里写 var tenantID string 然后赋值,结果下一个请求读到上一个租户的 ID —— 因为 Go 的 goroutine 复用导致变量残留。
-
ctx.Locals("user_id", 123)存,ctx.Locals("user_id")取,返回interface{},需类型断言 - 存结构体指针更高效:
ctx.Locals("user", &user),避免每次取都拷贝大对象 - 不支持嵌套 key(如
"auth.role"),要分层就用结构体字段 - 不能存 channel、mutex、goroutine 句柄等非序列化类型,否则 panic
Locals 和 c.Context() 的区别:别混用
c.Context() 返回的是 fasthttp 的原生 context.Context,它只用于超时控制、取消信号,**不是用来存业务数据的**。往里面塞 value 不仅没意义,还可能被下游库误读或覆盖。
典型翻车场景:中间件里调 c.Context().WithValue(...),然后在 handler 里试图用 c.Locals 取——根本取不到,因为这是两个完全独立的存储空间。
- 业务数据一律走
ctx.Locals - 超时/取消逻辑走
c.Context()(比如传给数据库查询) - 不要试图把
Locals数据“同步”到c.Context(),纯属多余
异步任务中 Locals 数据会丢失,必须显式透传
Goroutine 或消息队列任务启动后,原始 ctx 已结束,Locals 内容不可访问。直接把 c 传进去,轻则 panic,重则读到随机内存值。
正确做法是在启动异步前,把需要的数据抽出来,作为参数传入:
userID := c.Locals("user_id").(int)
go func(id int) {
// 在这里用 id,而不是 c.Locals
log.Printf("processing user %d", id)
}(userID)
- 切忌写
go process(c),哪怕只是读Locals - 如果要用第三方异步库(如 asynq),必须手动把租户 ID、trace ID 等关键字段塞进任务 payload
- 日志打点也得提前取好
Locals值,别在 goroutine 里调c.Locals
Locals 不是缓存,别存大量或长期数据
Locals 生命周期和请求绑定,只存活几十毫秒。存 MB 级数据进去,等于把内存压力转嫁给每个请求;存 DB 连接、文件句柄,会导致泄漏。
容易被忽略的一点:Fiber 的 Ctx 是从 sync.Pool 复用的,但 Locals map 每次请求都会新建,所以存太多东西会增加 GC 压力。
- 只存 ID、状态码、用户角色等轻量标识
- 大对象(如解析后的 JSON body)建议存一次,后续直接用,别反复
BodyParser - 绝对不要存
*sql.DB、*redis.Client—— 应该用依赖注入或全局池


















