树形权限架构采用原型继承思想,以委托查找和动态继承路径为核心,节点仅存本级声明,运行时沿父级向上聚合判断,天然支持无限嵌套且避免冗余存储。

直接用原型继承思想设计树形权限架构,关键不是模仿JavaScript的prototype链,而是抓住“委托查找”和“动态继承路径”这两个本质——权限不固化在节点上,而是在运行时沿父级逐层向上委托、聚合、判断。这种设计天然适配无限嵌套,且避免冗余存储。
一、权限节点建模:每个节点只存“本级声明”,不存继承结果
把权限节点看作一个轻量对象,只定义它自己明确拥有的能力,以及它直接归属的上级节点(即“原型”)。不预计算所有祖先权限,也不在数据库里存全量继承集。
-
字段精简:每个权限项只需
id、name、type(如 menu/button)、parent_id(指向直接父权限,可为空) -
无冗余继承字段:不存
all_ancestors或inherited_perms等派生字段,这些全部延迟计算 -
示例数据:
{"id": 5, "name": "导出报表", "type": "button", "parent_id": 3}
{"id": 3, "name": "报表管理", "type": "menu", "parent_id": 1}
{"id": 1, "name": "数据看板", "type": "module", "parent_id": null}
二、运行时委托机制:像访问对象属性一样查权限
当判断用户是否拥有某权限(如 ID=5)时,不查静态表,而是启动一次“原型链查找”:
- 先检查该节点自身是否被显式授权(例如角色配置中直接勾选了 ID=5)
- 若未显式授权,则自动委托给它的
parent_id(ID=3),再查 ID=3 是否被授权 - 继续向上委托,直到找到一个被授权的祖先,或到达根节点(
parent_id为 null)仍未命中,则拒绝 - 整个过程是深度优先、单向向上的,类似 JavaScript 中
obj.method()找不到就往__proto__找
三、角色与权限解耦:角色只挂“起点”,继承由树结构驱动
角色不再绑定一堆扁平权限 ID,而是只指定一组“起始权限节点”。后续所有继承关系,由树本身的父子链自动展开。
- 角色配置变简单:比如“编辑员”角色只需声明拥有 ID=3(报表管理),自动获得 ID=5(导出报表)和 ID=1(数据看板)的访问权
- 增删节点不影响角色配置:在 ID=3 下新增子权限 ID=8(打印报表),所有已拥有 ID=3 的角色立即生效,无需重新配置
- 支持多原型路径:一个节点可有多个父节点(如 ID=7 同时属于 ID=2 和 ID=4),查找时并行委托,任一路径命中即通过
四、前端树组件同步实现“半选态”与懒加载
前端树形菜单不只是展示,它要实时反映原型继承状态:
- 半选逻辑自然浮现:某父节点部分子项被选中 → 它本身不被显式授权,但其某个子节点被授权 → 父节点呈现“半选”态,这正是委托未完全覆盖的直观体现
- 懒加载不破坏继承语义:只展开一级时,后端仅返回当前层级子节点;用户点开某节点再请求它的子节点;每次渲染都基于本地已知的父子关系做委托判断,无需一次性加载整棵树
- 选中状态即授权快照:用户勾选一个节点,代表对该节点做“显式授权”;取消勾选,只是撤回该层声明,不影响其祖先或后代的继承路径










