# 统一数据范围执行器设计 > 适用范围:`leaudit-platform` 当前 `RBAC + 单地区隔离 + 模块特例策略` 体系 > 文档定位:把“data_scope 已建模但未统一执行”的现状,收敛为一套后端可落地的统一执行器设计。 --- ## 1. 设计结论 当前系统真正缺的不是权限表,也不是权限点,而是一个统一的“数据范围执行层”。 现状已经具备: - `roles.data_scope` - `role_permissions.data_scope` - `role_permissions.grant_type = GRANT / DENY` - 用户主地区 `sso_users.area` - 多个模块已存在真实可运行的数据边界实现 当前系统真正缺失的是: 1. 平台层没有把“功能权限 + 数据范围 + 模块特例策略”合并成一次统一决策 2. `PermissionServiceImpl` 只返回布尔值,不返回 scope 决策 3. 文档、公文、统计、合同模板、RBAC 管理域都在各自重复写 `is_global/can_manage/area/created_by` 4. RAG、交叉评查等特殊模块没有被纳入统一决策框架,只能继续靠模块内手工判断 因此,建议新增统一执行链: 1. `PermissionDecisionService` 2. `DataScopeResolver` 3. `QueryScopeBuilder` 4. `ModulePolicyRegistry` 5. `ScopeAwarePermissionFacade` 目标不是推翻当前业务 SQL,而是先把“决策入口”和“范围解释逻辑”统一,再逐模块替换已有散落实现。 --- ## 2. 统一执行器要解决什么问题 ## 2.1 功能准入和数据范围必须拆开 一个接口是否可调用,与一个用户调用后能看到多少数据,是两层不同问题: - 功能准入:是否拥有 `permission_key` - 数据范围:即便拥有该权限,可见范围到底是 `ALL / DEPT / SELF / RELATION / PUBLIC_MIXED` 当前系统多数地方只做了第一层,第二层分散在 service 内。 ## 2.2 多角色用户必须有统一合并规则 当前库允许一个用户绑定多个角色: - 一个角色可能 `GRANT documents:list:read + SELF` - 另一个角色可能 `GRANT documents:list:read + DEPT` - 还有角色可能 `DENY documents:list:read` 没有统一合并规则,就会出现: - 有人按最大范围放行 - 有人按主角色放行 - 有人忽略 `DENY` ## 2.3 模块差异必须被显式建模 不是所有模块都适合硬套 `ALL/DEPT/SELF`: - 文档、公文、统计:典型数据范围模型 - RAG:`地区 + 公开(public)` 混合模型 - 交叉评查:任务成员关系模型 - 规则/配置域:更偏功能权限域 所以统一执行器不能只有一个“拼 where region = ?”函数,而要允许模块策略扩展。 --- ## 3. 总体架构 建议新增如下能力层。 ## 3.1 核心对象 ### 3.1.1 `ScopeContext` 表示一次权限决策的输入上下文。 建议字段: ```python @dataclass class ScopeContext: user_id: int permission_key: str module: str action: str user_area: str | None request_area: str | None = None target_user_id: int | None = None resource_id: int | None = None extra_filters: dict[str, Any] | None = None ``` 说明: - `user_area` 来自 `sso_users.area` - `request_area` 是接口显式传入的筛选地区 - `target_user_id` 适用于按用户过滤的查询 - `resource_id` 适用于详情、删除、下载这类单资源接口 - `extra_filters` 给统计、RAG、交叉评查等模块挂业务参数 ### 3.1.2 `PermissionGrant` 表示从数据库查出的单条授权记录。 ```python @dataclass class PermissionGrant: role_id: int role_key: str permission_key: str grant_type: str role_scope: str | None permission_scope: str | None condition_filter: dict[str, Any] | None priority: int is_system_role: bool ``` ### 3.1.3 `PermissionDecision` 表示平台最终产出的权限决策。 ```python @dataclass class PermissionDecision: allowed: bool deny_reason: str | None effective_scope: str | None scope_mode: str allowed_areas: list[str] allowed_user_ids: list[int] allow_public: bool conditions: dict[str, Any] matched_roles: list[str] matched_permissions: list[str] ``` 建议 `scope_mode` 取值: - `NONE`:不做数据范围控制 - `ALL` - `DEPT` - `SELF` - `RELATION` - `PUBLIC_MIXED` - `CUSTOM` ### 3.1.4 `ScopeClause` 表示最终用于拼接 SQL 的范围子句。 ```python @dataclass class ScopeClause: sql: str params: dict[str, Any] scope_mode: str description: str ``` --- ## 4. 执行链路 建议所有需要“权限 + 数据边界”的 service 统一走下面链路: ```text Controller -> ScopeAwarePermissionFacade.require() -> PermissionDecisionService.decide() -> PermissionGrantRepository.load_user_grants() -> DataScopeResolver.resolve() -> ModulePolicyRegistry.get(module).decorate() -> QueryScopeBuilder.build() -> Service 执行业务 SQL / ORM ``` 细化如下: 1. 控制器或 service 构造 `ScopeContext` 2. `PermissionDecisionService` 先确认该用户是否拥有功能权限 3. 若被 `DENY` 命中,直接拒绝 4. 若 `GRANT` 命中,则计算有效范围 5. 若模块存在特例策略,由 `ModulePolicy` 二次修饰决策 6. 由 `QueryScopeBuilder` 将决策转换为统一 SQL 子句 7. 业务 service 只负责把子句注入原查询,不再自行解释角色 --- ## 5. 数据来源与优先级规则 ## 5.1 数据来源 统一执行器的决策数据来自 4 层: 1. `sso_users.area` 2. `roles.data_scope` 3. `role_permissions.data_scope` 4. `role_permissions.grant_type / condition_filter` ## 5.2 功能准入优先级 功能准入保持现有语义,但要扩展为“带授权明细返回”: 1. 精确 `DENY` 2. 通配符 `DENY` 3. 精确 `GRANT` 4. 通配符 `GRANT` 5. 无匹配则拒绝 这部分沿用 `PermissionServiceImpl` 现有规则,不改语义。 ## 5.3 范围优先级 建议统一采用: 1. `role_permissions.data_scope` 2. `roles.data_scope` 3. 模块默认值 即: - 权限级 scope 覆盖角色级 scope - 角色级 scope 覆盖模块默认值 - 若都没有,读接口默认 `SELF`,配置接口默认 `NONE` ## 5.4 多角色合并规则 建议采用: 1. 先按 `DENY` 决定是否整体拒绝 2. 在所有有效 `GRANT` 中取“最大可见范围” 3. 若存在模块策略,则再由模块策略收敛边界 范围大小建议定义为: ```text ALL > DEPT > SELF GROUP 不再扩展,仅兼容映射 RELATION / PUBLIC_MIXED / CUSTOM 由模块策略生成 ``` 注意: - `DENY` 是功能拒绝,不只是 scope 收缩 - 不能把一个 `DENY documents:list:read` 理解成“降到 SELF” - 一旦该权限被 `DENY` 命中,应直接 `allowed = False` ## 5.5 `GROUP` 的处理 库里仍保留 `GROUP`,但当前系统没有成熟“组织组”模型。 建议: - 现阶段统一映射为 `DEPT` - 文档和代码中明确标注“仅兼容,不新增使用” - 后续如果真要启用组织组,再单独扩展 --- ## 6. 范围语义规范 ## 6.1 `ALL` 含义: - 可见该模块全部数据 - 可按任意地区或任意用户进一步筛选 适用: - 省级管理员 - 超级管理员 - 某些总部配置权限 ## 6.2 `DEPT` 本项目中 `DEPT` 语义应明确定义为: - 同主地区 `area` 不是传统部门树。 含义: - 只能看与自身 `user_area` 相同地区的数据 - 如接口显式传入 `request_area`,则必须等于 `user_area` ## 6.3 `SELF` 含义: - 仅可看与自己直接归属的数据 优先字段通常是: - `created_by` - `owner_id` - `user_id` 必须按模块字段映射规范执行,不能每个模块再自行猜测。 ## 6.4 `RELATION` 适用于交叉评查等成员关系模型。 含义: - 可访问与当前用户存在显式关系绑定的数据 例如: - `task_member.user_id = current_user_id` - `proposal.created_by = current_user_id` - `document` 属于当前参与任务 ## 6.5 `PUBLIC_MIXED` 适用于 RAG。 含义: - 本地区资源可见 - 公共资源可见 - 省级公共资源可见 本质条件通常是: ```sql resource.area IN (:user_area, '省级', '') OR resource.is_public = TRUE ``` --- ## 7. 通用字段映射规范 统一执行器要避免“模块自己猜字段名”,必须先定义字段映射表。 ## 7.1 推荐映射对象 ```python @dataclass class ScopeFieldMapping: area_field: str | None = None creator_field: str | None = None owner_field: str | None = None user_field: str | None = None ``` ## 7.2 模块字段规范 ### 文档模块 - area 字段:`d.region` - self 字段:`f.created_by` 或主文档归属用户 说明: - 当前文档真实归属更多落在文件记录 `created_by` - 统一接入时要固定“列表/详情/附件/结果”的归属判断口径 ### 公文模块 - area 字段:`d.region` - self 字段:`f.created_by` ### 使用统计模块 按口径不同分两类: - 用户口径 area:`u.area` - 文档口径 area:`d.region` - self 字段:`f.created_by` 或 `u.id` ### 合同模板模块 - area 字段:`t.region` - self 字段:`t.created_by` ### RBAC 用户管理 - area 字段:`u.area` - self 字段:通常不用 ### RAG 数据集/应用 - area 字段:`dataset.area` / `app.area` - self 字段:按创建者字段补充 - public 字段:`is_public` ### 交叉评查 - 不优先走 `area_field` - 主入口走关系字段: - `task_member.user_id` - `proposal.created_by` - `vote.user_id` --- ## 8. 模块策略设计 统一执行器不能只靠 `ALL/DEPT/SELF`,必须允许模块策略。 ## 8.1 `ModulePolicy` 接口 ```python class ModulePolicy(Protocol): def decorate(self, ctx: ScopeContext, decision: PermissionDecision) -> PermissionDecision: ... def build_clause(self, ctx: ScopeContext, decision: PermissionDecision) -> ScopeClause | None: ... ``` 说明: - `decorate()` 负责把通用 scope 转成模块实际策略 - `build_clause()` 负责拼接模块专属条件 ## 8.2 DocumentPolicy 适用模块: - 文档列表 - 文档详情 - 文档状态 - 评查点聚合 - 附件追加 - 文档确认 建议策略: - `ALL`:不加地区限制,但允许 `request_area` - `DEPT`:固定 `d.region = user_area` - `SELF`:固定 `f.created_by = current_user_id` - 单资源接口必须先验证资源是否在 scope 内,再做更新/删除 ## 8.3 GovdocPolicy 建议策略: - 与文档模块同一套 area/self 语义 - `run/result/report/download` 等派生接口必须继承 document scope - 不能只校验 `run_id` 是否存在,必须回溯到所属 `document_id` ## 8.4 UsageStatsPolicy 建议策略: - `overview/trends/by-areas`:支持 `ALL/DEPT` - `by-users/by-departments/details`:支持 `ALL/DEPT/SELF` - `SELF` 时只允许看到自己的登录、上传、评查明细 - `areaScope=document` 与 `areaScope=user` 由模块策略切换字段映射 ## 8.5 RagPolicy 建议策略: - 读取类接口:`PUBLIC_MIXED` - 管理类接口:`ALL/DEPT/SELF` + 资源属地校验 拆分为两类: 1. `rag:app:read` / `rag:chat:use` / `rag:dataset:read` - 允许 `本地区 + 省级 + 公共` 2. `rag:dataset:manage/create/update/delete` - 不再按 `UserRole` 白名单 - 统一按 `permission + scope` 决定 ## 8.6 CrossReviewPolicy 建议策略: - 主模型使用 `RELATION` - `task:read` 不是 area 过滤,而是成员关系过滤 - `document:complete` 不是角色白名单,而是“拥有权限 + 是任务参与者/负责方” - `proposal:read/export` 必须通过文档所属任务关系回溯校验 结论: - 交叉评查接入统一执行器 - 但不强制落到普通 `ALL/DEPT/SELF` - 它是统一框架下的特例策略,不是例外代码 ## 8.7 RbacAdminPolicy 建议策略: - 角色、用户、组织树、路由、授权查询等接口优先按 `u.area` - 全局管理员看全部 - 地区管理员仅管理本地区用户 - 角色对象本身通常不按地区分表,但“角色绑定到哪些用户”仍受用户 area 边界约束 --- ## 9. QueryScopeBuilder 设计 ## 9.1 目标 把 `PermissionDecision` 转成业务可注入的条件,而不是让业务自己重新解释。 ## 9.2 建议接口 ```python class QueryScopeBuilder: def build_by_mapping( self, decision: PermissionDecision, mapping: ScopeFieldMapping, current_user_id: int, user_area: str | None, request_area: str | None = None, ) -> ScopeClause: ... ``` ## 9.3 通用构造规则 ### `ALL` ```sql 1 = 1 ``` 若带 `request_area`: ```sql COALESCE(area_field, '') = :request_area ``` ### `DEPT` ```sql COALESCE(area_field, '') = :scope_area ``` 并且: - 若 `request_area` 非空且不等于 `user_area`,直接拒绝 ### `SELF` 优先级: 1. `creator_field` 2. `owner_field` 3. `user_field` 生成: ```sql creator_field = :current_user_id ``` ### `PUBLIC_MIXED` ```sql ( COALESCE(area_field, '') IN :visible_areas OR public_field = TRUE ) ``` ### `RELATION` 由模块策略返回完整 SQL 子句,不走通用拼接。 --- ## 10. 权限决策缓存建议 当前 `PermissionServiceImpl` 已有 60 秒权限集合缓存。 建议统一执行器增加两层缓存: 1. 用户授权明细缓存 2. 用户能力快照缓存 建议缓存内容: - grant / deny 结果 - 每个 `permission_key` 对应的有效 scope - 用户 `area` - 用户角色列表 注意: - 缓存里只存决策原料,不缓存最终 SQL - 角色变更、权限变更、用户地区变更后,应主动失效 --- ## 11. 建议代码落点 建议新增目录: ```text fastapi_modules/fastapi_leaudit/services/permission_scope/ decision_models.py permissionDecisionService.py dataScopeResolver.py queryScopeBuilder.py modulePolicies/ base.py documentPolicy.py govdocPolicy.py usageStatsPolicy.py ragPolicy.py crossReviewPolicy.py rbacAdminPolicy.py ``` 建议改造现有: - `services/impl/permissionServiceImpl.py` - 保留布尔接口 - 新增返回授权明细的方法 - `services/permissionService.py` - 增加 `GetPermissionDecision` - `services/impl/documentServiceImpl.py` - `services/impl/govdocServiceImpl.py` - `services/impl/usageStatsServiceImpl.py` - `services/impl/ragDatasetServiceImpl.py` - `services/impl/ragChatServiceImpl.py` - `services/impl/rbacAdminServiceImpl.py` - `services/impl/contractTemplateServiceImpl.py` --- ## 12. 推荐接入顺序 ## 12.1 第一批 - RAG - 文档 - 公文 - 使用统计 原因: - 这几块最明显存在范围逻辑分散或双轨冲突 - 风险最高,收益也最大 ## 12.2 第二批 - RBAC 管理域 - 合同模板 - 首页入口模块 ## 12.3 第三批 - 交叉评查接入统一决策框架 - 前端联动改为只认权限与服务端返回能力 --- ## 13. 与现有代码的映射关系 当前散落逻辑,建议按下述替换: - `documentServiceImpl._getCurrentUserContext` - 替换为 `PermissionDecisionService + ScopeContext` - `documentServiceImpl._buildDocumentScopeFilters` - 替换为 `QueryScopeBuilder + DocumentPolicy` - `govdocServiceImpl` 内 area/created_by 过滤 - 替换为 `GovdocPolicy` - `usageStatsServiceImpl._build_user_scope_condition` - 替换为 `UsageStatsPolicy` - `contractTemplateServiceImpl` 内 `is_global/can_manage` - 替换为 `ScopeDecision` - `rbacAdminServiceImpl` 内 `bool_or(role_key IN (...))` - 替换为统一管理域能力决策 - `ragDatasetServiceImpl` / `ragChatServiceImpl` 内 `UserRole` 白名单 - 替换为 `RagPolicy` --- ## 14. 验收标准 统一执行器设计落地后,应满足以下标准: 1. 新增一个 permission 时,可以同时配置 data_scope,并被平台自动执行 2. 文档、公文、统计、RAG 不再自行硬编码角色解释范围 3. `DENY`、多角色合并、地区限制有统一规则 4. 详情、删除、下载、导出接口与列表接口共享同一 scope 决策 5. 前端不再需要猜测“省级管理员/市级管理员能看到哪些数据” --- ## 15. 最终建议 这次改造不应该继续扩写更多 `is_global/can_manage` 工具函数,而应直接建立统一执行器。 如果继续在各 service 内复制现有模式,后续问题会持续扩大: - 数据范围无法统一验收 - 新角色接入成本持续上升 - 前后端边界会长期不一致 - RAG、交叉评查这类特例模块会越来越难治理 统一数据范围执行器是本轮权限架构改造的核心平台能力,优先级应为最高。