13 KiB
权限与数据可见性模型 PRD
文档目的
本文档定义 CRM 平台统一的权限与数据可见性模型,覆盖线索、商机、客户、项目等业务模块。模型规定:一个用户在任意页面上能看到哪些数据、能执行哪些操作,由三种机制组合决定。本模型为跨模块通用能力,各业务模块一致遵循。
背景
现有权限体系包含两层:功能权限(控制按钮与接口)与数据可见范围(按业务模块配置的部门级数据范围)。两层通过"取交集"组合,只能逐层收窄可见范围。
实际业务出现无法表达的场景:商机需支持"将人员加入团队成员即可查看该商机",线索公海池需支持"将人员设为负责人/协作人即可管理该池线索"。这类授权针对的是"某一条具体数据",且需要越过用户所属部门的限制——一名部门数据范围为"本部门"的销售,被拉入一条属于其他部门的商机团队成员后,应当能看到该商机,但现有"取交集"模型会将其挡在部门范围之外。
因此本模型引入第三种机制——行级授权,并明确三者的组合规则。
模型总览
用户对数据的访问由三种机制组合决定,学名对应 RBAC + ABAC + ReBAC 三层:
- 功能权限(RBAC):控制用户能否执行某个动作(点击按钮、调用接口)。跟着角色走——同一角色权限一致。配置后生效,未配置则放行。
- 数据可见范围(ABAC):按数据与用户的属性算可见,不逐条落表。跟着当前用户代入规则。包含两块:
- 页面业务语义(例:
我的线索 = owner_id = 当前用户、我的关注 = id IN 关注表); - 按角色配的部门数据范围档位(仅本人 / 本部门 / 本部门及下属 / 全部),作为组织层面的天花板。
- 页面业务语义(例:
- 行级授权(ReBAC):把用户加进团队成员 / 设为协作人时,落一条"用户 ↔ 资源"授权关系。跟着具体人 + 具体资源走,不跟角色走——换个同角色的人不会自动继承。它是加法性质的通行证,只越过 ABAC 里的部门天花板,不改页面自己的业务语义。
组合规则:
能否执行某动作 = 功能权限(已配置则校验,未配置则放行)
能否看到某条数据 = 页面业务条件
AND ( 落在数据可见范围内 OR 已被行级授权到该数据 )
数据可见性判定的核心是"数据可见范围 OR 行级授权"——满足其一即可见,而非二者同时满足。
边界澄清:ReBAC 只越过部门天花板,不改页面业务语义。例:李四被拉进某条 B 部门线索的协作人后,他在"相关 / 池视图"里能看到这条;但不会因此出现在"我的线索"页——因为那页语义是 owner_id = 李四,李四不是 owner,通行证不改这一层。
机制一:功能权限
功能权限控制"能否执行某个动作",与"能对哪些数据执行"无关,后者由机制二、机制三决定。
- 通过【权限点管理】录入权限点后生效。每个权限点包含两项:
- 前端按钮编码:控制按钮是否展示、是否可点击。
- 后端接口地址(API URL):控制接口是否可被调用。
- 未录入的按钮与接口默认放行,不做控制。需要管控的动作在权限点管理中录入对应权限点即可,无需管控的不录入。
机制二:数据可见范围
数据可见范围按业务模块(线索、商机、客户、项目,可扩展)分别配置,定义用户在该模块下组织层面能触达的数据上限。
四档范围:
| 档位 | 含义 |
|---|---|
| 仅本人 | 仅归属本人的数据 |
| 本部门 | 本人所属部门(主部门与兼职部门并集)范围内的数据 |
| 本部门及下属 | 本部门及其全部下级部门的数据 |
| 全部 | 全平台数据,不做部门过滤 |
规则:
- 按模块独立配置:同一用户在不同模块可配置不同档位,互不影响。
- 多角色取最宽:用户拥有多个角色时,某模块的最终档位取各角色在该模块配置档位中最宽的一档。未为某角色配置某模块时,该角色在此模块按"仅本人"计,不放宽任何范围。
- 性质为上限:数据可见范围仅定义组织层面的触达上限;具体页面展示哪一类数据,由页面业务条件在此上限内进一步收窄。
- 无部门时的判定:档位要求按部门过滤、但用户不属于任何部门时,判定为"一律不可见",不放行任何数据。
机制三:行级授权
行级授权针对单条具体数据,将指定用户单独授权,使其能够访问该条数据——即使该数据不在其数据可见范围内。行级授权是加法性质的"通行证",可越过机制二的天花板。
行级授权由业务动作触发产生,随动作撤销而失效:
| 触发动作 | 授权效果 |
|---|---|
| 将用户加入商机团队成员 | 该用户可访问此商机 |
| 将用户设为线索公海池负责人 | 该用户可管理此池及池内线索 |
| 将用户设为线索公海池协作人 | 该用户可管理此池及池内线索 |
| 将线索分配/指派给销售 | 被指派销售可访问该线索 |
| 用户关注某条数据 | 该用户可在"我的关注"访问该数据 |
行级授权为平台级通用能力,各业务模块共用同一套授权关系,不逐模块单独实现。授权关系按"用户 + 资源"维度存储与检索。
组合规则与示例
页面最终展示的数据由以下条件组合:
最终可见数据 =
页面业务条件 -- 页面自身定义展示哪一类数据
AND (
落在数据可见范围内 -- 机制二,组织天花板
OR 已被行级授权到该数据 -- 机制三,通行证
)
三段说明:
- 页面业务条件:由各页面业务语义定义。例:"我的线索"页限定归属本人;"线索公海"页限定状态为待领取且属于用户可触达的公海池;"我的关注"页限定用户已关注;"线索管理"页按筛选条件。
- 数据可见范围 OR 行级授权:常规数据经数据可见范围放行;被单独授权的数据经行级授权放行,越过天花板。二者取并集。
- 最外层取交集:既满足页面业务语义,又具备访问该条数据的资格。
示例一:我的线索页
销售张三,线索模块数据范围为"本部门",打开"我的线索":
归属人 = 张三
AND ( 线索所属部门 ∈ 张三所属部门 OR 张三被行级授权到该线索 )
页面业务条件已限定归属本人,天花板不额外收窄。
示例二:团队成员查看跨部门商机
销售李四,商机模块数据范围为"本部门",被加入一条属于 B 部门的商机团队成员:
(商机列表页业务条件)
AND ( 商机所属部门 ∈ 李四所属部门 -- 该商机属 B 部门,李四属 A 部门,天花板不放行
OR 李四被行级授权到该商机 ) -- 李四在团队成员中,行级授权放行
结果:李四可看到该商机。
与现有系统的关系
| 机制 | 现状 | 本次改动 |
|---|---|---|
| 功能权限(权限点管理) | 已有:录入按钮编码与 API URL,配置后生效 | 沿用,不改动 |
| 数据可见范围(按模块四档、多角色取最宽) | 已有:线索、商机、客户、项目已注册为可配置模块 | 沿用,不改动 |
| 行级授权 | 暂无,现有模型仅有部门级天花板 | 本次新增:通用行级授权关系 + 查询时作为并集通道并入 |
补充说明:
- 行级授权为平台级通用能力,商机团队成员、线索公海池负责人/协作人及后续客户、项目的同类需求共用同一套实现。
- 行级授权数据稀疏(团队成员、协作人为少量点名),按"用户 + 资源"维度建立索引,不影响列表查询性能。
- 通行证逻辑集中实现并统一注入查询,各业务页面无需分别处理,避免授权逻辑散落。
待确认事项(全部已由产品拍板,2026-08-13)
三项待确认全部已由产品拍板。以下条目仅保留结论摘要,详细依据见"已定决策"。
- (PRD-1)行级授权触发动作清单 —— ✅ 已定:
- 团队成员(商机 / 客户 / 项目)—— 逐条授权,
resource_type = opportunity / customer / project。 - 公海池负责人 / 协作人(线索)—— 池维度授权,
resource_type = pool。 - 线索侧的关注不走行级授权(详见 [F])。
- 团队成员(商机 / 客户 / 项目)—— 逐条授权,
- (PRD-2)权限边界 —— ✅ 已定:只读。通行证只放行"看得到";写权由权限点(能不能点)+ service 层(能不能写在这一条上,owner / 状态机)判定。团队成员 / 协作人因非 owner 而写不动,不分档。
- (PRD-3)撤销即失效 —— ✅ 已定:立即失效,授权行硬删。
已定决策
[D] 线索不开"按单条"直接授权通道(2026-08-13,产品确认=场景甲)
线索的可见性只来自两个来源:负责人(owner)就是本人(第一层业务过滤 owner_id = 我),或本人够得着该线索所属公海池(池维度行级授权)。业务上不存在"不改负责人、又不设整池协作人的跨部门单条协助"场景。
因此:
- 行级授权表不设
resource_type = lead的直接授权行;resource_type只记粗粒度授权对象(池 / 商机等)。 - "分配 / 指派"= 改
owner_id,走第一层业务过滤,不额外发通行证。 - 池维度授权在查询时翻译成"该池下全部线索"(间接授权),无需逐条落表。
[E] 池配置管理不发通行证(2026-08-13,产品确认=场景甲)
"能不能看 / 改某个池的配置" = 功能权限(角色有没有配这个菜单)× 数据范围(池归属部门 ∈ 用户部门集合)。行级授权(池负责人 / 协作人身份)不参与池配置的可见 / 可改判定。
依据:按 ticket 04 "一部门一池 + 成员 = 部门子树动态推导",用户负责 / 协作的池必是其部门集合内的池,数据范围天花板本来就放行它——无需通行证。不存在"跨部门当池负责人、却要改那个池配置"的场景。
因此:通行证只用于线索可见(池内线索越过部门天花板)这一件事;池配置管理里不用行级授权。
[PRD-3] 撤销授权立即失效、授权行硬删(2026-08-13,产品确认=场景甲)
移出团队成员 / 协作人名单的那一刻即失效:下一个请求的 OR EXISTS 旁路就命中不到。授权行硬删(DELETE FROM sys_row_grant WHERE ...),不做软删 + 失效标记。
依据:
- 通行证每请求实时查(
DataScopeInterceptor在prepare阶段注入,DataVisibilityContext为请求级),立即失效是零成本默认行为;做“延迟 / 保留”反而要额外加逻辑。 - 授权关系是“当前谁被授权”的事实清单,过期关系无保留价值;“谁何时被加进 / 移出协作人”由操作日志(lead_history)留痕,不靠授权表本身。
[F] 关注不走行级授权(2026-08-13,产品确认)
线索侧唯一产生行级授权的动作 = 设为公海池负责人 / 协作人(池维度)。关注不发通行证:"我的关注" = id IN 关注表 AND 部门天花板(受当前部门实时约束)。
为何不走(安全场景论证):销售 A 被人事误拉进部门 B → 点关注某条 B 部门线索 → 人事改回部门 A。
- 若关注走行级授权:那一刻落了 "A ↔ 这条线索" grant,改回部门不动 grant,A 永久越部门看到这条 —— 漏洞。
- 实际设计(关注不走 grant):改回部门 A 后,部门天花板变为 A 部门,这条 B 部门线索被天花板挡掉 —— 即使还在关注表里也看不到。可见性每请求按当前部门实时算,不因历史点过关注而永久留权。
边角:被挡掉的那条关注记录(指向已看不到的线索)留着不清——实时判定天然无害(看不到即不越权),清理反而要加逻辑;哪天又调回 B 部门自然又能看到。
术语
- 数据权限模块:可独立配置数据范围的业务模块(线索、商机、客户、项目等),存于注册表,以 code 标识。
- 数据范围档位:仅本人 / 本部门 / 本部门及下属 / 全部。
- 主部门 / 部门集合:用户唯一正式归属部门 / 主部门与兼职部门并集;数据范围按部门集合计算。
- 一律不可见:档位要求按部门过滤、但用户无任何部门时的判定结果,不放行任何数据。
- 功能权限点:权限点管理中录入的按钮编码与 API URL,配置后生效。
- 行级授权:针对单条数据将用户单独授权的通行证,可越过部门级天花板。
- RBAC / ABAC / ReBAC:三层模型的学名。RBAC=功能权限(跟角色走,管能不能点);ABAC=数据可见范围(页面业务语义 + 部门档位天花板,按属性算,不逐条落表);ReBAC=行级授权(跟具体人+具体资源走,逐条落表,只越部门天花板、不改页面语义)。ReBAC ≠ ABAC:不得用属性规则伪装逐条关系。