第4章:角色与权限模型设计:RBAC0模型实现
权限设计,说白了就是解决「谁能干什么」的问题。
我见过不少初创团队,一开始图省事,直接在用户表里加个 role 字段,用 0、1、2 表示不同角色。结果呢?业务一扩张,权限逻辑改得想哭。嗯,今天我们就聊聊 RBAC0 模型,这是最经典、最实用的权限模型。
4.1 为什么是 RBAC0?
RBAC 全称是 Role-Based Access Control,基于角色的访问控制。RBAC0 是核心模型,它定义了三个基本元素:用户、角色、权限。
你想想看,如果每个用户都直接绑定权限,那管理起来有多恐怖?100 个用户,每个用户 10 个权限,就是 1000 条记录。如果某个权限要调整,你得遍历所有用户。我曾经接手过一个老系统,就是这种设计,改一个权限字段要跑半小时 SQL,别提多痛苦了。
RBAC0 的思路很简单:用户不直接关联权限,而是通过角色来间接关联。用户属于某个角色,角色拥有某些权限。这样,你只需要维护角色和权限的关系,用户那边自动继承。
核心公式:用户 → 角色 → 权限
用户与角色是多对多关系,角色与权限也是多对多关系。
4.2 数据库表结构设计
我们来看具体的表设计。一共需要五张核心表:
| 表名 | 说明 | 核心字段 |
|---|---|---|
| sys_user | 用户表 | user_id, username, password, status |
| sys_role | 角色表 | role_id, role_name, role_code, status |
| sys_user_role | 用户-角色关联表 | id, user_id, role_id |
| sys_menu | 菜单权限表 | menu_id, parent_id, menu_name, path, component, perms, icon, sort, visible |
| sys_role_menu | 角色-菜单关联表 | id, role_id, menu_id |
这里我重点说一下 sys_menu 表。它不只是存菜单,还存了按钮权限。你看 perms 这个字段,它存的是权限标识符,比如 system:user:add、system:user:edit。前端根据这个标识符来控制按钮的显示隐藏。
-- 菜单权限表建表语句
CREATE TABLE sys_menu (
menu_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID',
menu_name VARCHAR(50) NOT NULL COMMENT '菜单名称',
path VARCHAR(200) DEFAULT '' COMMENT '路由路径',
component VARCHAR(255) DEFAULT NULL COMMENT '组件路径',
perms VARCHAR(100) DEFAULT NULL COMMENT '权限标识',
icon VARCHAR(50) DEFAULT '#' COMMENT '菜单图标',
sort INT DEFAULT 0 COMMENT '排序',
visible TINYINT DEFAULT 1 COMMENT '是否可见(0隐藏 1显示)',
menu_type CHAR(1) DEFAULT 'M' COMMENT '菜单类型(M目录 C菜单 F按钮)',
status TINYINT DEFAULT 1 COMMENT '状态(0禁用 1启用)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (menu_id)
) COMMENT '菜单权限表';
我的习惯:menu_type 字段用 M、C、F 来区分类型。M 是目录(只有子菜单),C 是菜单(可点击跳转),F 是按钮(没有页面,只有权限标识)。这样前端渲染菜单树时,逻辑非常清晰。
4.3 按钮权限表(permission)
有些团队会把按钮权限单独拆一张表,叫 sys_permission。我个人觉得,如果项目不大,直接复用 sys_menu 表的 perms 字段就够了。但如果你做的是大型企业级应用,比如 ERP、CRM 这类,我建议还是拆开。
-- 按钮权限表(独立方案)
CREATE TABLE sys_permission (
permission_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '权限ID',
permission_name VARCHAR(50) NOT NULL COMMENT '权限名称',
permission_code VARCHAR(100) NOT NULL COMMENT '权限编码',
menu_id BIGINT DEFAULT NULL COMMENT '所属菜单ID',
status TINYINT DEFAULT 1 COMMENT '状态',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (permission_id),
UNIQUE KEY uk_code (permission_code)
) COMMENT '按钮权限表';
-- 角色-权限关联表
CREATE TABLE sys_role_permission (
id BIGINT NOT NULL AUTO_INCREMENT,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
PRIMARY KEY (id)
) COMMENT '角色权限关联表';
为什么要拆?因为有些权限不绑定菜单。比如「导出报表」这个按钮,它可能出现在多个页面,但权限逻辑是统一的。拆出来之后,角色直接关联 permission_id,维护起来更灵活。
避坑指南:我曾经遇到过一个项目,权限标识符命名混乱,有的用下划线,有的用驼峰,有的用点号。后来统一规范为 模块:功能:操作 的格式,比如 order:export:excel。建议你从一开始就定好命名规范,不然后面改起来很麻烦。
4.4 数据权限隔离方案
数据权限,说白了就是「同一张表,不同角色看到的数据不一样」。比如销售经理能看到所有销售订单,普通销售只能看到自己的订单。
数据权限的实现,一般有几种方案:
- 硬编码方案:在 SQL 查询中直接加 WHERE 条件。比如
WHERE create_by = 当前用户ID。简单粗暴,但维护成本高。 - 注解 + AOP 方案:在 Mapper 方法上加注解,通过 AOP 动态拼接 SQL。比如 @DataScope(deptAlias = "d"),自动拼接部门过滤条件。
- 数据权限规则表:设计一张数据权限规则表,存储每个角色对应的过滤条件。
我个人比较推荐第二种方案,也是目前主流框架(如若依、Spring Security)的做法。来看一个简单的数据权限注解设计:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 部门表的别名
*/
String deptAlias() default "";
/**
* 用户表的别名
*/
String userAlias() default "";
/**
* 权限字符(用于匹配角色)
*/
String permission() default "";
}
然后在 AOP 切面中,根据当前用户的角色,动态拼接 SQL。比如:
// 伪代码示例
if (用户角色 == "部门经理") {
sql += " AND d.dept_id IN (子部门ID列表)";
} else if (用户角色 == "普通员工") {
sql += " AND d.create_by = 当前用户ID";
}
数据权限的粒度:
- 全部数据:超级管理员可见
- 本部门及子部门数据:部门经理可见
- 本部门数据:部门主管可见
- 仅本人数据:普通员工可见
- 自定义数据:通过规则表灵活配置
4.5 权限校验的完整流程
一个请求进来,权限校验的流程大概是这样的:
- 用户登录,后端生成 JWT Token,存入 Redis(包含用户ID、角色信息)
- 每次请求,拦截器解析 Token,获取当前用户信息
- 访问某个接口时,通过 @PreAuthorize 注解校验权限
- 校验逻辑:从 Redis 获取该用户的权限标识列表,判断是否包含当前接口所需的权限标识
- 如果是数据权限,再通过 AOP 动态拼接 SQL 过滤条件
这里有个细节:权限标识列表不要每次都查数据库。我习惯在用户登录时,把该用户所有角色的权限标识合并后,缓存到 Redis 里。缓存 key 可以设计为 auth:perms:用户ID,过期时间设为 2 小时。这样既保证了实时性,又减轻了数据库压力。
小技巧:如果角色权限变更了,记得清除该角色下所有用户的权限缓存。我一般会在修改角色权限的接口里,加一个「清除关联用户缓存」的操作。虽然多了一步,但能避免用户权限不同步的问题。
4.6 总结
RBAC0 模型,说白了就是三层结构:用户、角色、权限。它解决了权限管理中最核心的问题——灵活性和可维护性。
菜单权限控制「能看什么」,按钮权限控制「能点什么」,数据权限控制「能看到哪些数据」。这三层加在一起,基本能覆盖 90% 的企业级权限需求。
嗯,下一章我们会聊聊流程引擎的表设计,那是 OA 系统的另一个核心模块。到时候见。