# 权限与数据可见性模型 PRD ## 文档目的 本文档定义 CRM 平台统一的权限与数据可见性模型,覆盖线索、商机、客户、项目等业务模块。模型规定:一个用户在任意页面上能看到哪些数据、能执行哪些操作,由三种机制组合决定。本模型为跨模块通用能力,各业务模块一致遵循。 ## 背景 现有权限体系包含两层:功能权限(控制按钮与接口)与数据可见范围(按业务模块配置的部门级数据范围)。两层通过"取交集"组合,只能逐层收窄可见范围。 实际业务出现无法表达的场景:商机需支持"将人员加入团队成员即可查看该商机",线索公海池需支持"将人员设为负责人/协作人即可管理该池线索"。这类授权针对的是"某一条具体数据",且需要越过用户所属部门的限制——一名部门数据范围为"本部门"的销售,被拉入一条属于其他部门的商机团队成员后,应当能看到该商机,但现有"取交集"模型会将其挡在部门范围之外。 因此本模型引入第三种机制——行级授权,并明确三者的组合规则。 ## 模型总览 用户对数据的访问由三种机制组合决定,学名对应 **RBAC + ABAC + ReBAC** 三层: 1. **功能权限(RBAC)**:控制用户能否执行某个动作(点击按钮、调用接口)。**跟着角色走**——同一角色权限一致。配置后生效,未配置则放行。 2. **数据可见范围(ABAC)**:按数据与用户的属性算可见,不逐条落表。**跟着当前用户代入规则**。包含两块: - **页面业务语义**(例:`我的线索 = owner_id = 当前用户`、`我的关注 = id IN 关注表`); - **按角色配的部门数据范围档位**(仅本人 / 本部门 / 本部门及下属 / 全部),作为组织层面的天花板。 3. **行级授权(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) > 三项待确认全部已由产品拍板。以下条目仅保留结论摘要,详细依据见"已定决策"。 1. **(PRD-1)行级授权触发动作清单** —— ✅ 已定: - **团队成员**(商机 / 客户 / 项目)—— 逐条授权,`resource_type = opportunity / customer / project`。 - **公海池负责人 / 协作人**(线索)—— 池维度授权,`resource_type = pool`。 - **线索侧的关注**不走行级授权(详见 [F])。 2. **(PRD-2)权限边界** —— ✅ 已定:**只读**。通行证只放行"看得到";写权由权限点(能不能点)+ service 层(能不能写在这一条上,owner / 状态机)判定。团队成员 / 协作人因非 owner 而写不动,不分档。 3. **(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:不得用属性规则伪装逐条关系。