4. RBAC 数据库设计:用户、角色、权限与关联表
聊完了 RBAC 的模型概念,咱们得落地了。说白了,就是怎么设计几张数据库表,把用户、角色、权限这几个东西存起来,还能高效地查。这事我做过挺多次,每次重构权限系统,核心表结构其实都大差不差,但细节上稍不注意,后面就可能踩坑。
4.1 核心表:三张基础表
最基本的 RBAC 设计,需要三张实体表,外加两张关联表。我先说说这三张实体表。
- 用户表(user):存谁在用系统。字段太简单?嗯,但状态字段你得多留个心眼。
- 角色表(role):存有哪些岗位或身份。比如“管理员”、“运营编辑”、“普通用户”。
- 权限表(permission):存具体的操作许可。比如“创建文章”、“删除用户”、“查看报表”。
我见过一些设计,把权限和菜单混在一起,这其实不好。权限最好只抽象为“操作”,而不是“页面url”。页面只是操作的载体。你想想看,一个页面可能有多个权限点(增、删、改),所以权限表最好只描述“能做什么”。
4.2 用户表(user)
我个人习惯,用户表里不直接放角色字段。角色应该通过关联表去映射,这样一个人就能有多个角色了。
role_id 字段,只能存一个角色。后来业务要求“一个人既是审核员又是管理员”,结果被迫改表结构,改了一大堆业务代码。所以,一开始就设计成多对多,后面会省心很多。| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| username | varchar(50) | 登录名,唯一 |
| password | varchar(255) | 加密后的密码 |
| status | tinyint | 0-禁用,1-启用 |
| created_at | datetime | 创建时间 |
4.3 角色表(role)
角色是权限的集合。一个角色对应一整套操作能力。表结构很简单,但 role_code 字段我建议加上,因为代码里经常要用这个 code 做逻辑判断,而不是靠 id 或 name。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| role_code | varchar(50) | 角色编码,唯一,如 ADMIN |
| role_name | varchar(50) | 角色名称,如 管理员 |
| description | varchar(200) | 角色描述 |
role_code like 'OPS%' 就查出来了。要是直接用 id,你还得去另一张表里查关联,麻烦不少。4.4 权限表(permission)
权限表的设计,这里有个分歧。有人喜欢把权限做成树形结构(比如菜单权限、按钮权限分开),但基础 RBAC 里,我倾向于先扁平化。每个权限就是一个“原子操作”。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| perm_code | varchar(50) | 权限编码,如 article:create |
| perm_name | varchar(50) | 权限名称,如 创建文章 |
| module | varchar(50) | 所属模块,如 article |
4.5 关联表:打通用户与权限
实体表之间是多对多的关系。一个用户可以有多个角色,一个角色可以有多个权限。所以,需要两张关联表来桥接它们。
- 用户-角色关联表(user_role)
- 角色-权限关联表(role_permission)
为什么会这样设计?因为用户和权限之间如果直接关联,系统会变得非常灵活,但也会非常复杂。RBAC 的核心思想就是通过“角色”这一中间层来管理,这样日常维护时,你只需要给角色加减权限,用户会自动继承。
| 关联表 | 字段 | 示例 |
|---|---|---|
| user_role | user_id, role_id | 用户 1001 关联角色 2(管理员) |
| role_permission | role_id, perm_id | 角色 2 关联权限 5、6、7(增删改) |
UNIQUE KEY uk_user_role (user_id, role_id),防止重复关联。这是基本功,但很多人会忘。4.6 完整的 SQL 实现示例
下面给你一个完整的 DDL 示例。你可以直接拿来用,或者根据需求微调。嗯,这里要注意字符集和引擎的选择,我个人习惯用 InnoDB + utf8mb4,兼容性好,支持事务。
-- 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`status` tinyint NOT NULL DEFAULT '1',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_code` varchar(50) NOT NULL,
`role_name` varchar(50) NOT NULL,
`description` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 权限表
CREATE TABLE `permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`perm_code` varchar(50) NOT NULL,
`perm_name` varchar(50) NOT NULL,
`module` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_perm_code` (`perm_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户-角色关联表
CREATE TABLE `user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色-权限关联表
CREATE TABLE `role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`perm_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_perm` (`role_id`, `perm_id`),
KEY `idx_perm_id` (`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.7 一个小扩展:租户隔离
如果你是做 SaaS 系统的,可能会遇到多租户问题。这时候,每张表里再加一个 tenant_id 字段,所有查询都带上这个条件,就能做到数据隔离。我的做法是:把 tenant_id 作为联合索引的第一个字段,因为查询时基本都会带上。
举个例子,用户表可能变成这样:
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`tenant_id` bigint NOT NULL,
`username` varchar(50) NOT NULL,
-- 其他字段略
UNIQUE KEY `uk_tenant_username` (`tenant_id`, `username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
好了,表结构讲到这里。这是 RBAC 最经典、最实用的数据库设计。你可以在它的基础上自由扩展,比如加上数据权限字段、部门字段等等。基础打牢了,后面玩 ABAC 时才会更顺手。