tenantID必须从可信JWT上下文提取,禁止依赖X-Tenant-ID等不可信输入;每个租户需独占sql.DB实例并用sync.Map缓存;Redis缓存键必须硬编码tenant_id前缀;数据库连接路由函数签名须为func(tenantID string)sql.DB。

tenantID 必须从可信上下文提取,不能依赖请求头或参数
租户标识一旦来自 X-Tenant-ID 或 tenant_id query 参数,就等于把数据隔离边界交给了不可信输入。攻击者可篡改 header 直接读取他人数据。
真正可信的来源只有登录态中已验证的归属关系:JWT payload 解析后得到的 tenant_id 字段,且该 token 必须经签名校验通过。中间件里别二次解析、别做字符串转换、别 fallback 到默认值。
- 用
gin.Context.Set("tenant_id", tenantID)或context.WithValue(r.Context(), tenantKey, tenantID)注入上下文 - 后续所有 DB/Cache/Config 调用都从 ctx 取值,禁止再读
r.Header.Get("X-Tenant-ID") - 如果 JWT 里没有
tenant_id,说明认证失败,直接返回401 Unauthorized
每个租户必须独占 *sql.DB 实例,不能复用连接池
想靠一个 *sql.DB 实例 + SET search_path 或 USE database 动态切换租户,是并发雷区。database/sql 的连接池会复用物理连接,上个请求设的 schema 下个请求可能还在生效,导致数据串租。
安全做法是为每个租户初始化独立的 *sql.DB,并缓存在线程安全结构中:
立即学习“go语言免费学习笔记(深入)”;
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- 用
sync.Map缓存tenantID → *sql.DB,避免 map 并发写 panic - 首次访问时调用
sql.Open()初始化连接,DSN 中数据库名(PostgreSQL schema 名 / MySQL 库名)必须动态拼接 - 立即执行
db.Exec("SET search_path TO " + tenantID)(PostgreSQL)或确保 DSN 指向正确库(MySQL) - 必须调用
db.SetMaxOpenConns(5)和db.SetConnMaxLifetime(5 * time.Minute),否则 100 个租户 × 默认 100 连接 = 数据库拒绝连接
缓存键必须硬编码包含 tenantID,不能靠结构体 hash
Redis key 漏掉 tenant_id 是 P0 级故障点。多个租户查同名资源(如 config),缓存就会互相覆盖:“谁先写谁赢”。这不是并发问题,是设计缺陷。
更隐蔽的问题是:有些代码在 redis-go 的 Get 返回 nil 后查 DB,却没把带 tenant_id 的结果回填,导致下一个租户命中前一个租户的脏数据。
- 所有 key 必须形如
tenant:{tenant_id}:config或tenant:{tenant_id}:user:{id} - 禁用
json.Marshal(struct{...})生成 key —— 字段顺序、tag 差异都会导致键不一致 - 使用
ristretto或groupcache时,tenant_id必须是 cache key 的第一级
路由函数签名必须是 func(tenantID string) *sql.DB,不是无参闭包
闭包本身不感知租户 ID,它只捕获定义时的变量。常见错误是写成 func() *sql.DB,里面硬编码 dbMap["default"] —— 这根本不是多租户路由,只是个固定连接工厂。
真正动态路由的关键,是把租户标识作为参数传入闭包返回的函数:
- 正确签名:
func(tenantID string) *sql.DB - 闭包内部封装的是「映射表 + 健康检查 + 惰性初始化」逻辑,不是具体连接
- 每次调用都走
sync.Map.LoadOrStore(),失效连接用db.PingContext()探活后清理 - 不要在模块初始化阶段就连接所有租户库 —— 启动慢、失败难处理、浪费资源
复杂点在于连接健康状态与缓存生命周期的对齐。租户 DB 服务重启后,sync.Map 里还存着失效的 *sql.DB,后续查询会卡死或报 driver: bad connection。必须定期探测,而不是等第一次查询失败才清理。


















