4. 角色层级设计与角色继承机制
前面几章我们把 RBAC 的基础概念捋了一遍——用户、角色、权限,还有那个经典的 5 张表。但实际落地的时候,你会遇到一个棘手的问题:角色太多了怎么办?
我刚开始做权限系统那会儿,公司有 200 多个岗位,每个岗位都对应一个角色。最离谱的是,很多角色的权限有 80% 是重叠的。我和团队商量了一下,决定引入角色层级和继承机制。说白了,就是让角色能“认爹”。
4.1 为什么需要角色层级?
想象一个场景:公司有“普通员工”、“部门经理”、“总监”、“CEO”四个角色。普通员工能访问内部 wiki,经理能访问 wiki + 部门报表,总监能 wiki + 报表 + 战略数据,CEO 什么都看。如果不做继承,每个角色都得单独配置一遍权限——那简直是对人肉的浪费。
而且你想想看,如果有一天要调整“访问内部 wiki”这个权限,你得改 4 个角色配置。万一漏掉一个?嗯,事故就来了。
角色层级设计的核心目标:提取公共权限到父角色,子角色只维护增量差异,避免冗余和遗漏。
4.2 角色继承的基本概念
角色继承,本质上是一种偏序关系。子角色自动获得父角色的所有权限,同时可以拥有自己的额外权限。形式上,我们把它叫做 Role Hierarchy。
这里有三个关键点:
- 传递性:如果 A 继承 B,B 继承 C,那么 A 自动继承 C 的所有权限。
- 方向性:通常是高层级角色指向低层级角色(或者反过来,看设计习惯)。我见过有些系统反着画,但实现逻辑一样。
- 无环约束:继承关系不能成环,否则权限就无限递归了。
| 继承方式 | 特点 | 使用场景 |
|---|---|---|
| 单继承 | 每个子角色只有一个父角色 | 组织结构简单,层级固定 |
| 多继承 | 子角色可有多个父角色 | 跨部门职责(如技术经理同时继承技术和经理权限) |
| 限制深度继承 | 规定最多 n 层 | 避免过度抽象,控制复杂度 |
避坑指南:我曾经在项目里使用了多继承,结果两个父角色对同一个权限的配置冲突了——一个允许,一个拒绝。后来我们决定用“显式高于继承”的规则:子角色可以显式覆盖继承来的权限。这样冲突就有了明确的解决方案。
4.3 层级设计的三种模式
4.3.1 岗位层级型
最常见的一种。按照公司里的汇报关系来:顶层的 CEO 拥有所有权限,越往下权限越少。实现起来很简单:CEO → VP → Director → Manager → IC。我建议小公司先从这个开始,因为结构清晰,容易排查问题。
4.3.2 职能域分层型
按业务领域划分角色,然后每个领域内部再层层扩展。比如“财务相关”下面有“会计”、“财务经理”、“CFO”。这种模式适合大型企业,能避免角色层级被组织架构绑架。我记得有次给银行做项目,他们就是这种模式——会计和信贷专员完全不搭边,但各自内部有层级。
4.3.3 混合型
前两者的结合:一个角色既有组织层级的继承,又有职能域的继承。说白了就是多继承的一种典型应用。比如一个“区域财务总监”,既是“区域总监”的子角色,又是“财务总监”的子角色。这种设计能力很强,但也容易导致“继承爆炸”——权限来源太多,最后查出来一个权限是谁给的都费劲。
我的个人习惯:能不混合就不混合。如果业务必须混合,那么就严格控制多继承的角色数量,最好不超过总角色数的 10%。剩下 90% 的单继承角色,排查问题时能省一半时间。
4.4 角色继承的数据库设计
表结构上,我们有几种做法。先看最直接的继承表方案:
-- 角色主表
CREATE TABLE roles (
role_id INT PRIMARY KEY,
role_name VARCHAR(50),
description TEXT
);
-- 角色继承关系表
CREATE TABLE role_hierarchy (
parent_role_id INT NOT NULL,
child_role_id INT NOT NULL,
PRIMARY KEY (parent_role_id, child_role_id),
FOREIGN KEY (parent_role_id) REFERENCES roles(role_id),
FOREIGN KEY (child_role_id) REFERENCES roles(role_id),
CHECK (parent_role_id <> child_role_id)
);
-- 权限分配仍然用原有的 role_permissions 表
-- 子角色的权限 = 自己的权限 UNION 所有祖先角色的权限
这个设计清楚吧?但问题在于:查询某个角色的所有权限时,你需要递归地找它所有的父级角色。如果是多继承,那就是一个 DAG 的遍历。性能上你得加缓存——嗯,这也是我踩过的坑。
优化技巧:如果层级深度固定(比如不超过 4 层),你可以在应用层做递归,缓存每个角色的“有效权限列表”。如果层级很深或者动态变化频繁,那就用物化路径(materialized path)或者闭包表(closure table)。但我们这一章不展开讲闭包表,先记住有这回事就行。
4.5 权限解析:继承链上的权限合并
当一个请求来了,我们需要判断用户是否有权限。有了继承之后,逻辑变成:
- 获取用户的所有直接角色(user_roles)。
- 对每个角色,递归获取它的所有父角色(包括间接的)。
- 去重后得到该用户的所有有效角色集合。
- 用这些角色的权限列表(来自 role_permissions)做去重合并。
- (可选)如果有角色级别的拒绝权限,需要按照优先级覆盖允许权限。
为什么是第 5 步可选?因为我见过有些系统压根不支持“拒绝权限”,默认所有父角色权限都是允许的,子角色只能添加不能删除。这种设计简单,但灵活性差。我个人更推荐支持“权限类型”字段(ALLOW/DENY),让子角色能显式拒绝某个权限。
4.6 层级设计避坑清单
好,理论说完,说几个我实际遇到过的坑:
- 循环继承:有一次我同事在测试环境里写了个 SQL 把角色 A 的父角色设成了子角色 B,B 的父角色又是 A,结果查询时栈溢出。一定要在代码层或数据库约束层做无环检查。
- 过度抽象:有些系统把角色层级搞了七八层,最底层的角色只有一个权限。这种设计反而增加理解成本。我建议日常使用的角色控制在 3 层以内。
- 批量删除父角色:删除一个父角色时,所有子角色的权限都会丢失一部分。需要做好级联处理或者先提醒用户。
- 权限膨胀:如果一个子角色继承了太多父角色,它的权限集合可能会大到没法管理。我在项目中就见过一个技术主管角色,因为从 5 个父角色那里继承了一千多个权限。后来我们限制了单个角色的继承源数量不超过 3 个。
4.7 小结——我的取舍建议
角色层级与继承机制,说白了就是给权限系统加了一层抽象。抽象得好,能省下 60% 的配置工作;抽象得不好,反而会让系统变得臃肿且不可维护。
我个人推荐这么做:先画一张组织角色图,找出所有重复权限片段。然后尝试用单继承 + 最多 3 层深度来覆盖 80% 的场景。剩下 20% 的多继承需求,单独开会评审,确保它有合理的业务解释,而不是设计者的奇思妙想。
代码层面,建议在服务启动时或者角色继承关系变更时,预计算并缓存每个角色的“有效权限集合”。这样运行时就不需要递归查询数据库了。嗯,内存换时间,值。
最后一个小技巧:在你实现继承关系的“无环检测”时,可以用拓扑排序或者深度优先搜索。我习惯在添加或修改继承关系时做实时检查,而不是等用户提交后再报错——用户体验好很多。