多租户 RBAC 模型设计方案
·
基于 Go 技术栈的 多租户 RBAC 模型设计方案,涵盖完整的数据库设计、组织层级、数据范围控制以及审计与扩展体系。方案采用“共享数据库、tenant_id 隔离”,配合 Gin、GORM、PostgreSQL、Wire、Viper,确保统一隔离、高性能和易用性。
一、总体设计思路
1. 多租户策略
- 共享数据库 + tenant_id 行级隔离
所有业务表包含tenant_id列,查询时通过 GORM 全局作用域自动添加WHERE tenant_id = ?条件。 - 租户上下文传递
由 Gin 中间件从 JWT 中解析租户 ID 并注入context.Context,GORM 通过 Context 回调动态获取租户 ID,保证每次查询天然隔离。 - 高效性保障:为所有涉及租户的查询建立
(tenant_id, ...)联合索引或赋予tenant_id单独索引,确保最优计划。
2. 技术选型分布
| 技术 | 用途 |
|---|---|
| Golang | 后端语言 |
| Gin | HTTP 框架,中间件链 |
| GORM | ORM,自动租户过滤、钩子 |
| PostgreSQL | 主数据库,支持数组、JSONB、物化路径/闭包表 |
| Wire | 依赖注入,组织 Service/Repo 层次 |
| Viper | 配置管理(数据库、JWT、租户) |
二、数据库模型(ER 与核心表)
1. 租户与用户
-- 租户表
CREATE TABLE tenants (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
code VARCHAR(50) NOT NULL UNIQUE,
status SMALLINT DEFAULT 1, -- 1:启用 0:禁用
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP
);
-- 用户表
CREATE TABLE users (
id BIGSERIAL,
tenant_id BIGINT NOT NULL,
username VARCHAR(64) NOT NULL,
password_hash VARCHAR(256) NOT NULL,
email VARCHAR(128),
phone VARCHAR(20),
real_name VARCHAR(64),
avatar VARCHAR(256),
status SMALLINT DEFAULT 1,
org_id BIGINT, -- 主部门(组织)
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT uq_tenant_user UNIQUE (tenant_id, username)
);
CREATE INDEX idx_users_tenant ON users(tenant_id);
2. 组织架构(支持复杂层级)
采用 邻接表 + 物化路径 + 闭包表 混合设计,兼顾查询性能与维护成本。
-- 部门/组织表
CREATE TABLE organizations (
id BIGSERIAL,
tenant_id BIGINT NOT NULL,
parent_id BIGINT,
name VARCHAR(100) NOT NULL,
code VARCHAR(50) NOT NULL,
path VARCHAR(500) NOT NULL, -- 物化路径 '/1/3/8/'
sort INT DEFAULT 0,
leader_id BIGINT, -- 负责人 user_id
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT uq_tenant_org_code UNIQUE (tenant_id, code),
CONSTRAINT fk_org_parent FOREIGN KEY (parent_id) REFERENCES organizations(id)
);
CREATE INDEX idx_org_tenant_parent ON organizations(tenant_id, parent_id);
CREATE INDEX idx_org_path ON organizations(tenant_id, path varchar_pattern_ops); -- 支持 like 查询
-- 闭包表(高效子树查询)
CREATE TABLE organization_closures (
ancestor_id BIGINT NOT NULL,
descendant_id BIGINT NOT NULL,
depth INT NOT NULL, -- 层级深度 0 表示自身
tenant_id BIGINT NOT NULL,
PRIMARY KEY (ancestor_id, descendant_id)
);
CREATE INDEX idx_closure_descendant ON organization_closures(tenant_id, descendant_id);
CREATE INDEX idx_closure_ancestor ON organization_closures(tenant_id, ancestor_id);
维护策略:在组织移动/新增时,通过事务更新
path与closure表,确保一致性。
3. 用户 - 组织关系(多组织兼职)
CREATE TABLE user_organizations (
user_id BIGINT NOT NULL,
org_id BIGINT NOT NULL,
is_primary BOOLEAN DEFAULT false, -- 主部门标识
PRIMARY KEY (user_id, org_id)
);
业务约束:同一用户仅允许一个 is_primary = true。
4. RBAC 权限(角色 - 资源)
为简化且扩展性强,采用 资源树(菜单/按钮/API) + 角色关联,用资源唯一标识符作为权限点。
-- 全局资源表(无 tenant_id,共享给所有租户)
CREATE TABLE resources (
id BIGSERIAL PRIMARY KEY,
parent_id BIGINT,
code VARCHAR(100) NOT NULL UNIQUE, -- 唯一权限标识,如 user:view, user:create
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('menu','button','api')),
path VARCHAR(256), -- 菜单路由
icon VARCHAR(128),
sort INT DEFAULT 0,
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP
);
CREATE INDEX idx_res_parent ON resources(parent_id);
-- 租户角色表
CREATE TABLE roles (
id BIGSERIAL,
tenant_id BIGINT NOT NULL,
name VARCHAR(64) NOT NULL,
code VARCHAR(64) NOT NULL,
description VARCHAR(256),
status SMALLINT DEFAULT 1,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP,
PRIMARY KEY (id),
CONSTRAINT uq_tenant_role_code UNIQUE (tenant_id, code)
);
-- 角色 - 资源关联
CREATE TABLE role_resources (
role_id BIGINT NOT NULL,
resource_id BIGINT NOT NULL,
PRIMARY KEY (role_id, resource_id)
);
-- 用户 - 角色关联
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
);
5. 数据范围控制(精细数据权限)
数据范围独立于功能权限,可绑定到角色,也可分配给具体用户。
-- 数据范围类型(使用枚举更规范,这里用 CHECK 简化)
CREATE TYPE scope_type AS ENUM ('ALL', 'DEPT', 'DEPT_AND_CHILD', 'CUSTOM', 'SELF');
-- 角色数据范围
CREATE TABLE role_data_scopes (
id BIGSERIAL PRIMARY KEY,
role_id BIGINT NOT NULL,
scope scope_type NOT NULL,
org_ids BIGINT[], -- CUSTOM 时有效,其他为空
created_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_rd_scope_role ON role_data_scopes(role_id);
-- 用户数据范围(可选,用于额外授权)
CREATE TABLE user_data_scopes (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
scope scope_type NOT NULL,
org_ids BIGINT[],
created_at TIMESTAMP DEFAULT now()
);
CREATE INDEX idx_uds_scope_user ON user_data_scopes(user_id);
6. 审计与安全
CREATE TABLE audit_logs (
id BIGSERIAL,
tenant_id BIGINT NOT NULL,
user_id BIGINT,
action VARCHAR(50) NOT NULL, -- 如:CREATE, UPDATE, DELETE
resource VARCHAR(100) NOT NULL, -- 操作资源类型,如 'User', 'Order'
resource_id VARCHAR(64),
old_value JSONB,
new_value JSONB,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP DEFAULT now()
) PARTITION BY RANGE (created_at); -- 大数据量时按月分区
CREATE INDEX idx_audit_tenant_time ON audit_logs(tenant_id, created_at);
old_value/new_value用 JSONB 记录变更前后差异。- 密码使用 bcrypt 哈希存储于
users.password_hash。
三、核心机制实现
1. 统一多租户隔离
- Gin 中间件:解析 JWT → 提取
tenant_id,user_id→ 存入c.Set("tenant_id", tid) - GORM 全局回调:
db.Callback().Query().Before("gorm:query").Register("tenant_filter", func(db *gorm.DB) { if tid, ok := db.Statement.Context.Value("tenant_id").(int64); ok { db.Where("tenant_id = ?", tid) } }) // 同理应用于 Create / Update / Delete - 这样业务代码无需显式携带
tenant_id,从源头杜绝数据越权。
2. 复杂组织层级查询
利用 闭包表 高效获取子树/所有祖先:
- 查询某部门所有子部门(含自身):
SELECT descendant_id FROM organization_closures WHERE tenant_id = ? AND ancestor_id = ?; - 在物化路径
path上建立索引,直接WHERE path LIKE '/1/3/%'也可作为辅助手段。
3. 精细数据范围计算
合并规则(优先级:ALL > 其他):
- 获取用户所有角色对应的
role_data_scopes。 - 加上
user_data_scopes(代表额外授权)。 - 若任一为
ALL→ 数据全可见。 - 否则逐条解析:
DEPT→ 使用user_organizations中is_primary的org_id。DEPT_AND_CHILD→ 获取主部门及其所有子代org_id列表(通过闭包表)。CUSTOM→ 直接使用org_ids数组。SELF→ 记录一个标记,业务查询时转为created_by = current_user_id。
- 将所有
org_id取并集(含SELF标记)。 - 业务 SQL 生成条件:
-- 假设结果集包含 org_ids []int64, hasSelf bool WHERE (org_id IN (org_ids) OR (hasSelf AND created_by = ?))org_id IN利用索引,高效过滤。
4. 权限校验流程
- 根据用户 ID 与租户,联合查询
user_roles+role_resources,得到该用户可访问的所有resource.code集合。 - 中间件拦截请求,比对当前接口的权限标识(如
user:view)是否在集合中。 - 通过后再进行数据范围过滤,实现功能权限 + 数据权限双重控制。
5. 扩展性功能模块
- 动态菜单/按钮:通过
resources表轻松增删,角色分配后即刻生效。 - 数据范围扩展:增加新的
scope_type值(如AREA),并实现对应的解析逻辑。 - 多应用支持:可在
resources加app_code字段,区分不同子系统。
四、性能与数据库规范
| 优化点 | 措施 |
|---|---|
| 租户隔离查询 | 所有表 tenant_id 作为索引前缀,大量查询用覆盖索引 |
| 组织子树查询 | 闭包表替代递归 CTE,查询 O(1) 级延迟 |
| 权限码快速匹配 | resource.code 唯一索引,集权限校验可通过缓存(Redis)存储用户权限码 |
| 审计日志 | 按时间分区,定期归档历史数据 |
| 数组索引 | org_ids BIGINT[] 采用 GIN 索引,加速 @> 包含查询 |
| JSONB 差异 | 审计日志新旧值存储,无需额外表结构 |
五、代码分层与 Wire 依赖注入
cmd/
main.go # 启动入口,wire 生成依赖
config/
config.yaml # 数据库、JWT等配置,由 viper 读取
internal/
middleware/
tenant.go # 租户注入
auth.go # JWT 解析 + 权限校验
handler/ # Gin Handler
service/ # 业务逻辑
repository/ # GORM 数据访问
model/ # 表结构模型
wire/
wire.go # wire 绑定
wire_gen.go # 自动生成
通过 Wire 组装 DB -> Repo -> Service -> Handler,清晰可测试。
六、总结
该方案在同一个 PostgreSQL 数据库内,通过严格的 tenant_id 隔离、闭包表驱动的组织层级、角色+数据范围的双重权限控制,以及 GORM 自动注入、Gin 中间件、Wire 依赖注入,构建了一套完整、安全、高性能的多租户 RBAC 系统。所有表结构和索引均贴合业务语义,易于理解、扩展与维护。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)