租户数据隔离必须在每个关键链路显式校验和透传tenant_id,不可依赖中间件自动绑定上下文;异步任务、缓存、向量库等均需从干净isolatedCtx提取tenant_id,Redis Key须带租户前缀,Weaviate必须启用Tenant-aware Collections。

租户数据隔离不能靠“信任中间件自动绑定上下文”,必须在每个关键链路显式校验和透传 tenant_id,否则异步任务、缓存读写、向量查询都会越界。
租户上下文必须从请求头强制提取并覆盖 goroutine 上下文
默认的 context.WithValue() 不会跨 goroutine 传播租户标识,Webhook 或定时任务启动的新协程极易继承父 context 中残留的 tenant_id,导致数据混用。
- 永远不要依赖中间件中一次
context.WithValue(c.Request.Context(), "tenant_id", ...)就一劳永逸 - 所有异步逻辑(如
go processTask(...))前,必须显式构造干净的上下文:isolatedCtx := context.WithValue(context.Background(), "tenant_id", tenantID) - 数据库、缓存、LLM 调用层需统一从该 isolatedCtx 中提取
tenant_id,禁止 fallback 到全局变量或闭包捕获值
Redis 缓存 Key 必须带租户命名空间前缀
直接使用 prompt:12345 这类无租户标识的 key,会导致不同租户对同一 Prompt ID 的缓存互相污染——A 租户更新后,B 租户下次读到的就是脏数据。
- 正确格式是
tenant:org-456:prompt:12345,其中org-456来自当前请求解析出的租户标识 - 避免手拼字符串,应在 ORM 或 Cache 客户端层统一注册
CacheKeyPrefixer中间件,自动注入前缀 - Redis ACL 也需同步配置,仅允许应用账号访问以
tenant:开头的 key 前缀,这是第二道防线
Weaviate 向量库必须启用 Tenant-aware Collections
仅靠 API Key 鉴权无法实现租户级向量隔离。Weaviate 默认单 Collection 共享所有 Embedding,A 租户写入的向量会被 B 租户的相似性搜索命中。
- 必须创建带
tenant参数的 Collection,例如:client.schema.createClass(&class{Class: "Document", MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}) - 每次向量操作(
createObject/nearText)都需显式指定tenant: "org-456" - 禁用
multi-tenancy的集群不可用于多租户场景,无论上层怎么拦截请求都无效
最容易被忽略的是:网络层隔离(如 Kubernetes NetworkPolicy)和数据层隔离(如 RLS、tenant-aware collections)之间存在盲区——中间件只管“进来的请求”,但挡不住“内部服务直连数据库/Redis/Weaviate”的绕过行为。所有下游组件调用必须走封装后的 Client,且 Client 内部强制校验上下文中的 tenant_id。


















