# 权限、租户能力与数据范围职责边界说明 > 适用范围:`leaudit-platform` 当前 `RBAC + 租户主数据 + 多租户业务挂载` 体系 > 更新日期:2026-05-21 > 文档定位:专门解决“角色权限配置”和“租户管理里的功能开关/承载能力”看起来重复的问题,明确三者各自负责什么、不负责什么。 --- ## 1. 先看结论 当前系统里确实存在“语义重叠感”,但不应该把这三类配置看成同一层: 1. `角色权限` - 控制“人能不能操作” 2. `租户功能开关` - 控制“某租户是否开放某类业务能力” 3. `租户业务承载能力` - 控制“某租户能不能作为某类业务的数据归属方/挂载方” 4. `数据范围` - 控制“当前人最终能看到哪些租户、哪些数据” 如果只看页面字段,不看后端职责边界,就会误以为“租户页和角色权限页都在管入口模块、文档上传、知识库”。 这正是当前用户感知混乱的根因。 --- ## 2. 三层控制分别管什么 ## 2.1 角色权限:管人 角色权限回答的问题是: - 这个用户能不能进入某页面 - 这个用户能不能新建、编辑、删除、发布、导出 - 这个用户能不能调用某个接口 典型例子: - `entry_module:create` - `entry_module:update` - `documents:upload:create` - `rag:dataset:manage` - `rbac:tenants:update` 结论: - 角色权限只决定“用户有没有操作资格” - 不决定“数据应该挂在哪个租户下” - 也不决定“某个租户是否承载某类业务” --- ## 2.2 租户功能开关:管租户是否开放业务能力 租户功能开关回答的问题是: - 这个租户是否启用了入口模块能力 - 这个租户是否启用了文档上传能力 - 这个租户是否启用了知识库能力 当前主字段: - `sys_tenant_feature_flags.feature_key` - 前端对应 `feature_keys` 当前真实语义应理解为: - “该租户是否开放该业务能力” - 更偏向租户产品能力配置,不是用户授权配置 结论: - 功能开关不授予用户权限 - 它只决定“租户是否开放某业务” --- ## 2.3 租户业务承载能力:管数据能不能落到该租户 租户承载能力回答的问题是: - 新建入口模块时,这个租户能不能被选为适用租户 - 新建文档、模板、知识库时,这个租户能不能作为归属租户 当前主字段: - `can_host_entry_module` - `can_host_documents` - `can_host_rag` - `can_host_templates` 这类字段和 `feature_keys` 很像,但不是一回事: - `feature_keys` 更偏“租户是否开放该业务” - `can_host_*` 更偏“租户能否被选为该业务的数据挂载对象” 结论: - 承载能力不等于操作权限 - 也不完全等于功能开关 - 它主要服务于“业务归属”和“数据落点”控制 --- ## 2.4 数据范围:管最终可见边界 数据范围回答的问题是: - 这个用户最终能看到哪些租户的数据 - 这个用户能看全部、部门、自身、公共、关系型资源中的哪些部分 它最终会把下面几件事合并起来: 1. 用户身份 2. 角色权限 3. 用户归属租户 4. 目标资源归属租户 5. 模块策略 结论: - 数据范围是最终访问边界 - 它不是租户主数据页里的布尔开关 - 也不是角色权限页里的单个 permission 就能表达清楚的 ## 2.5 三层控制的防护性区别 这三层不是“配置位置不同的同一件事”,而是三道不同的防护闸: 1. `租户功能开关` - 属于业务准入防护 - 防的是“该租户根本不该开放这类业务,却仍被看见或被使用” 2. `角色权限` - 属于操作授权防护 - 防的是“租户虽然开放了该业务,但不是任何人都能新增、编辑、删除、发布” 3. `数据范围` - 属于数据边界防护 - 防的是“有权限的人在操作时串看、串改到不属于自己范围的数据” 可以直接这样理解: - `租户功能开关 = 模块级总闸` - `角色权限 = 人的操作闸` - `数据范围 = 数据行级边界闸` 任意一层缺失,风险都不同: 1. 只有功能开关,没有角色权限 - 结果:租户开了功能后,可能任何角色都能乱用 2. 只有角色权限,没有功能开关 - 结果:本不该承载该业务的租户,仍可能被误开放入口或误创建数据 3. 只有前两层,没有数据范围 - 结果:拥有权限的用户仍可能看到其他租户数据,形成越权或串租户泄漏 所以最终判断某个操作是否允许,不能只看一层,而应按下面顺序串起来判断: 1. 目标租户是否开放该业务 2. 当前用户是否具备该业务动作权限 3. 当前用户的数据范围是否允许访问该目标租户或目标资源 --- ## 3. 为什么当前会让人觉得重复 当前用户会觉得重复,原因不是理解错了,而是系统现状确实还没完全收口: 1. 前端租户页把: - `启用租户` - `公共租户` - `功能开关` - `承载能力` 混在同一个表单区域里 2. 业务名称上和角色权限页高度重合 3. 部分下游模块还没有把 `can_host_*` 做成强执行链路 4. `feature_keys` 已经有使用点,但并不是所有模块都用同一模式接入 因此现在最准确的判断不是“完全重复”,而是: - 设计上应该分层 - 落地上还处于过渡态 - 页面表达还不够清楚 补一句更贴近当前现状的话: - 当前系统已经有三层模型雏形 - 但还没有在所有模块做到“租户能力先拦、权限再拦、数据范围最后收口”的稳定统一执行顺序 --- ## 4. 当前代码里的真实分工 ## 4.1 已经落到真实用途的部分 当前已经能确认的真实用途: 1. `feature_keys` - 已被入口模块租户选择器消费 - `home.entry_module` 已有真实用途 2. `leaudit_entry_module_tenants` - 已作为入口模块适用租户关系表使用 3. 用户当前上下文 - 已开始统一输出 `tenant_code / tenant_name` 4. 首页入口模块 - 已按用户租户 + 模块租户关系过滤 ## 4.2 仍未完全闭环的部分 以下部分仍需继续补强: 1. `can_host_documents` 2. `can_host_rag` 3. `can_host_templates` 4. `feature_keys` 的统一消费规范 结论: - 现在租户页里的部分字段已经不是摆设 - 但也还没有做到所有字段都形成“写入即生效”的全链路闭环 --- ## 5. 最终建议的收口规则 ## 5.1 角色权限页只做“人”的授权 角色权限页只保留: - 页面访问权限 - 菜单权限 - 按钮权限 - 接口动作权限 - 数据范围策略 不要在角色权限页里表达: - 某租户是否开放业务 - 某租户是否能承载业务数据 ## 5.2 租户管理页只做“租户侧能力配置” 租户管理页应拆成 3 组: 1. 基础状态 - `启用租户` - `公共租户` 2. 功能开关 - 某租户是否开放某业务能力 3. 业务承载能力 - 某租户能否作为某业务的数据归属方 并且页面必须明确提示: - 这里不授予用户操作权限 - 用户是否能操作,仍由角色权限决定 ## 5.3 数据范围执行器单独收口 数据范围不要继续分散在各业务模块里各写一套: - 应把用户权限 - 用户租户 - 资源租户 - 公共数据策略 - 关系型特例 统一并入执行器。 --- ## 6. 推荐页面文案 ### 功能开关 说明: - 决定该租户是否开放某类业务能力 - 不直接授予用户页面或按钮操作权限 ### 业务承载能力 说明: - 决定该租户能否作为该类业务的数据归属租户 - 会影响新建/编辑时该租户是否可被选中 ### 角色权限 说明: - 控制当前用户能不能访问页面、调用接口、执行动作 - 与租户侧能力配置是两层控制 --- ## 7. 后续工程动作建议 优先级建议: 1. 前端租户页分组与文案改清楚 2. 为 `can_host_documents / can_host_rag / can_host_templates` 补下游强校验 3. 梳理 `feature_keys` 的统一消费规范 4. 统一把“租户是否可选”沉到服务层,不让前端自己猜 5. 最后再把数据范围执行器接到更多业务域 --- ## 8. 一句话原则 后续所有实现都应遵守下面这个原则: - `角色权限` 决定“谁能操作” - `租户功能开关` 决定“租户开不开这类业务” - `租户承载能力` 决定“数据能不能挂到这个租户” - `数据范围` 决定“最终能看到哪些数据” 只要这四层不再混写,后面的权限和多租户改造就不会继续失控。