# 5.8 多租户隔离 :::tip 概述 多租户是硬隔离:9 张租户级表的 tenant_id 过滤与盖章由全局钩子强制执行,业务代码零感知、不可绕过。租户状态与过期由 Tenant 中间件每请求校验(60s 缓存)。 ::: ## 数据模型 ```text goframe_tenant:code(T+4位ID,Account 生成)、status(1 正常/2 禁用)、 expire_time(nil=永不过期)、max_user_num 等 9 张租户级表(带 tenant_id 列,hooks.tenantTables 登记): goframe_user / role / dept / level / position / article / category / notice / link 平台级(无租户过滤):其余 16 张表 ``` ## 读侧隔离(不可绕过) ```text dao.Ctx(hooks.ApplyCtx):非平台上下文自动追加 WHERE tenant_id IN (0, 当前租户) ← 0 = 平台共享行 超管(ctx tenant_id=0):不加过滤,全量可见 写侧禁止手写该条件——dao.Ctx 是唯一查询入口 ``` ## 写侧归属(insertHook 盖章规则) ```text 租户用户插入未显式指定 tenant_id → 自动填自身(防逃逸) 超管未显式指定 → 填 tenant.defaultId(默认 1) 显式指定具体值 → 从显式值 显式 0 → 创建平台共享行(如全局字典/参数) 业务取归属租户统一 consts.GetTenantID(ctx)(超管归默认租户); CtxTenantID(ctx) 原始值仅供租户设施使用。 ``` ## 请求级校验(Tenant 中间件) ```text 位于 JWT 之后: * 公开路径放行 * ctx tenant_id=0(平台/超管)直接放行 * 其余加载租户摘要(缓存 easygoadmin:tenant:info:{id},TTL 60s): - status != 1 → code=1006 租户已禁用 - expire_time 早于现在 → code=1005 租户已过期 LoadTenant 语义:租户不存在 → 按禁用处理(失效关闭); 数据库基础错误 → 返回 nil,中间件放行(避免存储抖动导致全站不可用) ``` ## 租户状态机 ```text Account 开户 │ ▼ ┌─────────┐ Status(2) ┌─────────┐ │ 1 正常 │─────────────▶│ 2 禁用 │ └─────────┘ └─────────┘ │ │ │ expire_time 到期 │ Status(1) ▼ │ 过期(1005 拒绝访问)◀──────────┘ (expire_time nil = 永不过期) ``` 状态/过期变更后 tenant:info:{id} 缓存由 tenant 模块 AfterUpdate clearCache 钩子清理。 ## 租户开户(tenant.Account,POST /api/tenant/account) ```text 创建租户 + 初始管理员:事务内 1) 建租户(code 按 T+ID 规则生成) 2) 建管理员用户(bcrypt 事务外预计算) 3) 分配角色 max_user_num 上限校验(1007)在 user.Add 生效(checkTenantUserMax) 事务纪律:事务内不做 HTTP/文件 IO,bcrypt 等 CPU 计算事务外预计算 ``` 配套接口:`GET /api/tenant/users/:tenantId` 查询租户用户(sys:tenant:users)。 ## 安全审计要点 ::: warning 租户红线 1. 业务代码禁止 `g.DB()` 直查业务表 2. 业务代码禁止手写 del_flag / tenant_id 条件 3. **新增租户级表必须登记 hooks.tenantTables(漏登=跨租户泄漏)** 4. 唯一预检范围必须等于唯一索引范围(含 tenant_id 索引须 Group:"tenant_id") 5. 平台级全量可见是设计预期,管理端点须收紧 permission ::: ## 排障 | 症状 | 原因 | |------|------| | 租户看到别家数据 | tenantTables 漏登记该表;或业务绕过 dao.Ctx | | 租户看不到平台共享行 | 业务手写了 `tenant_id = ?`(应交给 dao.Ctx 的 IN(0,x)) | | 禁用租户仍能访问 | 等 60s 缓存过期,或 DEL `easygoadmin:tenant:info:{id}` | | 新增报租户用户数上限 | 1007——超过 max_user_num | | 超管新增数据归属不对 | 显式指定 tenantId 或调 tenant.defaultId 配置 |