17 KiB
节点权限设计说明(支持两棵树 + 子节点操作 + 全局 tree 规则)
本说明基于当前的权限表设计约束:
-
有两棵树:
standards和projects。 -
只有 Allow(允许)权限,没有 Deny,也没有继承。
-
使用一张表
node_permission管理权限,支持 6 类主体:- 用户:
user - 用户组:
group - 角色:
role - 所有人:
everyone - 父节点的 owner:
parent_owner - 子树根节点的 owner:
root_owner
- 用户:
一、node_permission 表字段定义
CREATE TABLE node_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
-- 树维度:
-- 'standards' : 只对 standards 生效
-- 'projects' : 只对 projects 生效
-- NULL : 两棵树都生效(全局规则)
tree VARCHAR(32) NULL,
-- 节点维度:
-- NULL : 默认权限(作用范围由 tree 决定)
-- > 0 : 某个具体节点的专属权限
node_id BIGINT NULL,
-- 主体类型:
-- user : 用户(user.id)
-- group : 用户组(group.id)
-- role : 角色(如 super/sAdmin/pAdmin)
-- everyone : 所有人
-- parent_owner : 父节点的 owner
-- root_owner : 当前子树根节点的 owner
principal_type ENUM(
'user',
'group',
'role',
'everyone',
'parent_owner',
'root_owner'
) NOT NULL,
-- 主体标识:
-- user/group : 对应 id 的字符串
-- role : 角色 code
-- everyone / parent_owner / root_owner : 固定为 NULL
principal_key VARCHAR(64) NULL,
-- 节点自身权限
can_list_children TINYINT(1) NOT NULL DEFAULT 0,
can_add_child_folder TINYINT(1) NOT NULL DEFAULT 0,
can_add_child_doc TINYINT(1) NOT NULL DEFAULT 0,
can_delete_node TINYINT(1) NOT NULL DEFAULT 0,
can_edit_node TINYINT(1) NOT NULL DEFAULT 0,
-- 文档内容权限
can_read_doc TINYINT(1) NOT NULL DEFAULT 0,
can_edit_doc TINYINT(1) NOT NULL DEFAULT 0,
can_delete_doc TINYINT(1) NOT NULL DEFAULT 0,
-- 子节点操作权限
can_delete_child_node TINYINT(1) NOT NULL DEFAULT 0,
can_edit_child_node TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- 同一 (tree, node_id, principal_type, principal_key) 只允许一条
UNIQUE KEY uk_tree_node_principal (tree, node_id, principal_type, principal_key),
KEY idx_node (node_id),
KEY idx_principal (principal_type, principal_key)
);
二、node_permission 关键字段语义
这里只说明与鉴权相关的字段
-
节点维度
node_id:NULL:表示“默认权限”,作用范围由tree决定:tree = 'standards':只对 standards 全部节点生效;tree = 'projects':只对 projects 全部节点生效;tree IS NULL:对两棵树全部节点生效。
> 0:表示“某个具体节点的专属权限”,此时tree应等于该节点所属树。
-
主体(Principal)维度
principal_type:'user' | 'group' | 'role' | 'everyone' | 'parent_owner' | 'root_owner'principal_key:根据principal_type确定其含义user:对应用户表user.id的字符串形式(如'12')group:对应用户组表group.id的字符串形式role:角色编码(如'super'、'sAdmin'、'pAdmin')everyone:固定为NULLparent_owner:固定为NULLroot_owner:固定为NULL
-
节点自身权限字段
can_list_children:是否可展开节点、列出子节点。can_add_child_folder:是否可在该节点下新增文件夹(node_type='folder')。can_add_child_doc:是否可在该节点下新增文档(node_type='doc')。can_delete_node:是否可删除该节点本身。can_edit_node:是否可修改该节点本身的元数据(名称、排序、owner 等)。
-
文档内容权限字段
can_read_doc:是否可读取文档内容。can_edit_doc:是否可编辑文档内容。can_delete_doc:是否可删除文档节点。
-
对子节点操作权限字段
can_delete_child_node:是否有权删除“该节点的直接子节点”。can_edit_child_node:是否有权修改“该节点的直接子节点”的元数据(如 name、owner)。
唯一约束建议:
(tree, node_id, principal_type, principal_key)唯一。这样:- 对于某棵树上的某个节点 + 某个主体,只会有一条“节点专属”权限记录;
- 对于某棵树上的某个主体,只会有一条“默认”规则;
tree IS NULL AND node_id IS NULL的记录则是“两棵树共用的唯一全局默认”。
三、主体集合(Principals)的构造
鉴权的第一步是,把“当前用户在权限系统里是谁”表达成一个主体集合,集合中的每个元素是一个 (principal_type, principal_key)。
设:
- 当前用户 id 为
U; - 当前操作的节点 id 为
N; - 当前节点所属的树为
T('standards'或'projects',可以从nodes表的字段tree_type获得 。
3.1 主体集合的组成
主体集合 P 至少包含以下几类元素:
-
用户本身(user)
- 元素:
('user', U.toString())
- 元素:
-
用户所属的所有组(group)
- 从
groupUser中查询:WHERE user_id = U得到所有group_id。 - 每个组生成一个主体:
('group', groupId.toString())。
- 从
-
用户的角色(role)
users表有一个role字段(值为'super'/'sAdmin'/'pAdmin'),可获取到角色。- 生成主体:
('role', roleCode)。
-
everyone(所有人)
- 固定加入:
('everyone', NULL)。
- 固定加入:
-
parent_owner(父节点 owner)
- 先在
nodes表中拿到当前节点的父节点 id:parentId。 - 若
parentId大于 0,则再从node表读出该父节点的owner字段:- 若
owner == U,说明当前用户是父节点的 owner,则加入:('parent_owner', NULL)。 - 否则不加入
parent_owner。
- 若
- 先在
-
root_owner(子树根节点 owner)
- 定义一个函数
isSubtreeRootOwner(N, U):- 输入:当前节点
N、当前用户U; - 算法:根据节点的
code(nodes表的字段code形如001003002008,表示该节点的祖先节点的code为001、001003、001003002)从数据库select出所有祖先节点的owner的id,判断当前用户U是否其中一员 - 输出:true或false。
- 输入:当前节点
- 在主体构造过程中:
- 若
isSubtreeRootOwner(N, U)返回true,说明当前用户是该子树根节点的owner,则加入:('root_owner', NULL); - 否则不加入
root_owner。
- 若
- 定义一个函数
最终,P 是一组主体元素(可能包含 user / group / role / everyone / parent_owner / root_owner)。
四、权限记录的分类:默认 vs 节点专属
在 node_permission 中,根据 tree 与 node_id 组合,可以区分三类规则:
-
全局默认规则(对两棵树都生效)
- 条件:
tree IS NULL AND node_id IS NULL。 - 语义:对 standards 和 projects 的所有节点都生效。
- 条件:
-
按树的默认规则
- 条件:
tree = T AND node_id IS NULL。 - 语义:只对树
T的所有节点生效。
- 条件:
-
节点专属规则
- 条件:
tree = T AND node_id = N。 - 语义:只对树
T上的节点N生效,用来覆盖默认设置。
- 条件:
说明:当存在
node_id > 0的规则时,不使用tree IS NULL以及node_id IS NULL的记录,这两个规则仅用于默认规则。
优先级规则(同一主体维度):
对给定树 T、节点 N、主体 (principal_type, principal_key),从最具体到最泛化依次为:
- 节点专属规则:
tree = T AND node_id = N; - 树级默认规则:
tree = T AND node_id IS NULL; - 全局默认规则:
tree IS NULL AND node_id IS NULL; - 如果以上三类都不存在,则该主体对该节点“没有任何配置”,视为该主体不给任何权限。
五、典型场景下的 INSERT 示例
1. 全局默认 everyone:所有人只能展开树结构
对 两棵树所有节点 生效(tree IS NULL AND node_id IS NULL):
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
NULL, NULL, 'everyone', NULL,
1,
0, 0,
0, 0,
0, 0, 0,
0, 0
);
2. 全局默认 parent_owner:父节点 owner 管理直接子节点
对两棵树所有节点生效,表达“父节点 owner 默认可以管理直接子节点”和“看子列表”:
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
NULL, NULL, 'parent_owner', NULL,
1,
1, 1,
0, 0,
0, 0, 0,
1, 1 -- 修改直接子节点
);
3. 全局默认 root_owner:子树根节点 owner 管理整棵子树
比如项目负责人(子树根的 owner)对整棵子树有较大权限:
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
NULL, NULL, 'root_owner', NULL,
1,
1, 1,
1, 1,
1, 1, 1,
1, 1
);
- standards 专用默认:技术标准管理员(角色 sAdmin)全权管理 standards
只对 standards 生效(projects 不受影响):
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
'standards', NULL, 'role', 'sAdmin',
1,
1, 1,
1, 1,
1, 1, 1,
1, 1
);
5. projects 专用默认:项目管理员(角色 pAdmin)管理 projects
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
'projects', NULL, 'role', 'pAdmin',
1,
1, 1,
1, 1,
1, 1, 1,
1, 1
);
6. 超级管理员专用默认:
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
NULL, NULL, 'role', 'super',
1,
1, 1,
1, 1,
1, 1, 1,
1, 1
);
7. 某个敏感节点对 everyone 做节点专属覆盖
假设节点 id = 123,是一个敏感目录,希望 everyone 在这个节点上完全没有权限(连展开都不行),但其它节点仍按默认规则:
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
'standards', 123, 'everyone', NULL,
0,
0, 0,
0, 0,
0, 0, 0,
0, 0
);
由于这是 tree='standards' AND node_id=123 AND principal_type='everyone' 的节点专属记录,会覆盖对全局默认 everyone 的配置。
8. 针对某个用户在单节点上授予完全权限
例如用户 id = 42,在节点 500 上拥有完全权限(不依赖角色/owner):
INSERT INTO node_permission (
tree, node_id, principal_type, principal_key,
can_list_children,
can_add_child_folder, can_add_child_doc,
can_delete_node, can_edit_node,
can_read_doc, can_edit_doc, can_delete_doc,
can_delete_child_node, can_edit_child_node
) VALUES (
'projects', 500, 'user', '42',
1,
1, 1,
1, 1,
1, 1, 1,
1, 1
);
六、计算“对某个节点的有效权限”的通用算法
我们定义一个抽象的“有效权限对象”,包含所有布尔权限字段:
can_list_childrencan_add_child_foldercan_add_child_doccan_delete_nodecan_edit_nodecan_read_doccan_edit_doccan_delete_doccan_delete_child_nodecan_edit_child_node
记这个对象为 R,初始时这十个值全部为 false。
6.1 输入
计算函数需要的输入信息:
- 用户 id:
U - 节点 id:
N - 树标识:
T ∈ {'standards', 'projects'}
6.2 步骤 1:构造主体集合 P
按照第二节“主体集合的构造”的规则,从 U、N、node/groupUser/user 表中计算出主体集合 P。
结果是若干个 (principal_type, principal_key) 的集合,例如:
('user', '12')('group', '3')('role', 'sAdmin')('everyone', NULL)(如果有一个全局everyone的配置以及对N节点的everyone配置,则只保留N节点自己的everyone配置)('parent_owner', NULL)(如果满足条件)('root_owner', NULL)(如果满足条件)
6.3 步骤 2:读取节点专属权限记录
在 node_permission 表中,查询当前树 + 当前节点的所有权限记录:
- 条件:
tree = T AND node_id = N
结果是一组行,每行包含:
principal_typeprincipal_key- 十个
can_*布尔字段
这组记录可以在内存中按 (principal_type, principal_key) 组织成一个映射结构,后面用来快速查找:
- 键:某个主体
(principal_type, principal_key) - 值:对应的一行权限记录。
6.4 步骤 3:读取默认权限记录(树级 + 全局)
在 node_permission 表中,查询当前树相关的所有默认权限记录:
- 条件:
(tree = T OR tree IS NULL) AND node_id IS NULL
这些记录中:
tree = T的记录是“树级默认规则”;tree IS NULL的记录是“全局默认规则”。
在内存中同样按 (principal_type, principal_key, tree) 或类似结构组织,以便后续对同一主体区分“树级默认”和“全局默认”。
6.5 步骤 4:按主体合并权限(带 tree 优先级)
对主体集合中的每一个主体 p = (ptype, pkey) 执行如下逻辑:
-
查找节点专属记录
- 在“节点专属映射”中查找键
(ptype, pkey)对应的记录。 - 若存在,则记为
row_node。
- 在“节点专属映射”中查找键
-
查找树级默认记录
- 在“默认映射”中查找满足:
principal_type = ptypeprincipal_key = pkeytree = T
- 若存在,则记为
row_tree_default。
- 在“默认映射”中查找满足:
-
查找全局默认记录
- 在“默认映射”中查:
principal_type = ptypeprincipal_key = pkeytree IS NULL
- 若存在,则记为
row_global_default。
- 在“默认映射”中查:
-
确定权限
- 如果
row_node存在用户U的记录:使用row_node中用户U的特定权限配置,其它配置全部忽略; - 否则将
row_node、row_tree_default、row_global_default中的所有权限做并集,即只要其中一个赋予了权限,就有该权限,相应地给权限对象R赋值。
- 如果
七、针对具体操作的鉴权规则
7.1 展开节点 / 查看子节点列表
操作: 展开节点 N,列出其直接子节点。
- 判断方法:
- 根据
U, N, T计算一次有效权限R。 - 若
R.can_list_children == true,则允许展开;否则禁止。
- 根据
7.2 在节点下创建子文件夹 / 子文档
操作: 在节点 N 下创建子节点:
- 创建文件夹:新子节点
node_type = 'folder'。 - 创建文档:新子节点
node_type = 'doc'。
判断方法:
- 先对父节点
N计算有效权限R。 - 创建文件夹时:
- 允许条件:
R.can_add_child_folder == true。
- 允许条件:
- 创建文档时:
- 允许条件:
R.can_add_child_doc == true。
- 允许条件:
7.3 修改节点自身(重命名、改 owner 等)
操作: 修改节点 N 本身的元数据(名称、owner、排序等)。
判断方法:
- 对节点
N计算有效权限R。 - 允许条件:
R.can_edit_node == true。
7.4 删除节点自身
操作: 删除节点 N 本身。
判断方法:
- 对节点
N计算有效权限R。 - 允许条件:
R.can_delete_node == true。
7.5 文档内容的读取 / 编辑 / 删除
假设节点 N 是一个文档节点(node_type = 'doc')。
-
读取文档内容:
- 对节点
N计算有效权限R。 - 允许条件:
R.can_read_doc == true。
- 对节点
-
编辑文档内容:
- 对节点
N计算有效权限R。 - 允许条件:
R.can_edit_doc == true。
- 对节点
-
删除文档文档节点:
- 对节点
N计算有效权限R。 - 允许条件:
R.can_delete_doc == true。
- 对节点
7.6 删除子节点
操作: 在父节点 P 下,删除一个直接子节点 C(C.parent = P)。
- 允许条件:父节点
P上的can_delete_child_node== true;
7.7 修改子节点(name / owner 等)
操作: 在父节点 P 下,修改直接子节点 C 的元数据(名称、owner 等)。
- 允许条件:父节点
P上的can_edit_child_node== true;