不能用全局变量或闭包存租户/用户数据,因多请求共享同一变量会导致 tenantID 被覆盖,引发跨租户数据泄露;Echo 的 context 是请求独享的唯一安全载体,应通过强类型 key 的 c.Set() 和封装取值函数传递租户信息。

为什么不能用全局变量或闭包存租户/用户数据
全局变量或中间件闭包里存 tenantDB、userID 等值,看似省事,但会直接导致并发安全问题:多个请求共享同一变量,A 请求刚写入的 tenantID 可能被 B 请求覆盖,后续 handler 拿到的是错的租户上下文,轻则查错库,重则跨租户数据泄露。
Echo 的 echo.Context 是每个请求独享的,生命周期与请求一致,是唯一安全的传递载体。
- 别在中间件外定义
var currentUser *User,然后在AuthMiddleware里赋值 - 别用闭包捕获外部变量(如
func(tenantCache map[string]*sql.DB) echo.MiddlewareFunc)——缓存可共享,但当前请求的租户实例必须绑定到 context - 所有中间件执行时拿到的
c都是新实例,c.Set()写入的数据只对当前请求链有效
怎么用 c.Set() 和强类型 key 安全传值
c.Set("user_id", 123) 虽能用,但拼错 key、类型断言失败、IDE 无法跳转——线上跑着跑着就 panic。真正可维护的做法是定义私有 key 类型 + 封装取值函数。
示例:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
type userCtxKey string
const userIDKey userCtxKey = "user_id"
func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// ... 从 JWT 或 header 解析 userID
uid := uint64(123)
c.Set(string(userIDKey), uid)
return next(c)
}
}
func GetUserID(c echo.Context) (uint64, bool) {
v, ok := c.Get(string(userIDKey)).(uint64)
return v, ok
}
- key 类型是未导出的
string别名,避免外部误用 -
GetUserID()封装了类型断言和ok判断,handler 里直接调用即可,不用重复写断言 - 所有业务 handler 都应通过该函数取值,而不是
c.Get("user_id")
路由分组间共享 context 数据是否需要额外操作
不需要。Echo 的 echo.Context 在整个请求生命周期内贯穿所有中间件和最终 handler,无论你用了 e.Group("/api/v1") 还是嵌套 usersGroup.Group("/admin"),只要中间件注册在请求链上,c.Set() 写入的数据就能被下游任意 handler 读到。
关键约束只有一条:中间件必须在目标 handler 执行前注册并运行。
- 如果在
/api/v1/users分组里注册了AuthMiddleware,那么该分组下所有路由(如GET /list、POST /create)都能拿到c.Get("user_id") - 但如果把
AuthMiddleware错挂到/api/v1/products分组,那 users 路由就拿不到——不是 context 断了,是根本没执行那个中间件 - 别指望靠路由参数(如
/t/:tenant_id)自动透传;必须在中间件里显式提取、校验、c.Set()
哪些数据不该塞进 context
context 不是垃圾桶。塞太多东西会拖慢 GC、增加内存占用,还容易引发循环引用。
- 禁止存大对象:比如整个
*sql.DB实例(应存连接池指针)、原始图片字节切片、未压缩 JSON 字符串 - 禁止存需手动释放的资源:如
*os.File、net.Conn——context 生命周期结束不等于资源自动关闭 - 禁止存可变结构体指针:比如
c.Set("user", &u),后续 handler 修改u.Name会影响其他中间件看到的值,极难调试 - 推荐只存:基础类型(
int64、string)、不可变结构体值、小指针(如*sql.DB、*redis.Client)
最常被忽略的一点:context 里的值不会自动序列化进日志或 trace,如果要打到日志里,必须显式调用 GetUserID(c) 并注入 log 字段——否则你以为传过去了,其实日志里全是空。


















