# 多维度组合数据权限设计方案 PRD ## 文档基础信息 | 项目 | 内容 | | ---- | ---- | | 文档名称 | 多维度交集式数据权限标准方案 | | 适用系统 | OMS 业务系统 + RBAC 管理端 | | 关联文档 | [`rbac系统设计-060801.md`](./rbac系统设计-060801.md)、[`oms系统设计-050801.md`](./oms系统设计-050801.md) | | 权限架构 | **身份认证** → **功能权限(RBAC PermissionCode)** → **数据权限(本方案)**,三层解耦 | | 版本 | V1.2 | | 设计目标 | 在现有 RBAC 功能权限之上,为**内部用户**补齐多维数据过滤;与客户门户 **CustomerId 硬隔离** 并存、互不替代 | --- ## 导读:与 RBAC / OMS 的分工 ### 三层权限模型 ``` ┌─────────────────────────────────────────────────────────────┐ │ 身份认证(Authentication) │ │ JWT:sub(GUID) + mapId(INT) + loginType(0内部/1客户) │ │ 详见 rbac §4 │ ├─────────────────────────────────────────────────────────────┤ │ 功能权限(Authorization · 能做什么) │ │ 用户角色 → role_permission → PermissionCode 并集 P │ │ 控制:菜单、按钮、API 是否可调用 │ │ 详见 rbac §5 │ ├─────────────────────────────────────────────────────────────┤ │ 数据权限(Data Scope · 能看到哪些行) ← 本文 │ │ 五维交集过滤,仅后端注入查询条件 │ │ 控制:列表/详情/导出/统计 返回的数据行范围 │ └─────────────────────────────────────────────────────────────┘ ``` **关键原则**: 1. 有 `Track.Orders.Query` 功能权限,不代表能看全部订单;还须通过数据权限过滤。 2. 数据权限**不能**替代功能权限;二者 AND 关系:**先鉴权码、再滤数据**。 3. **客户门户(`loginType=1`)不走本文五维模型**,走 OMS 既有 **CustomerId 硬隔离**(见 oms §4.4.0、`OmsSessionHelper`)。 4. **内部用户(`loginType=0`)** 才启用本文多维数据权限;超管白名单豁免。 ### 双登录主体与数据隔离策略 | 主体 | JWT `loginType` | 数据隔离机制 | 配置入口 | | ---- | --------------- | ------------ | -------- | | **客户 / 子客户** | 1 | `CustomerId = effectiveCustomerId`(会话解析,不可绕过) | 无需数据权限配置;子客户功能范围由 RBAC 专属角色 `CustomerSub_{userId}` 控制 | | **内部用户** | 0 | 本文五维交集 + 可选 `CustomerId` 等业务维度 | RBAC 管理端「数据权限」Tab / 权限模板 | | **超级管理员** | 0 | 角色 `Code=Admin` → **跳过全部数据维度** | 内置,不可关闭 | ### 双 ID 对齐(必须与 RBAC 一致) | 标识 | 类型 | 数据权限表 / 业务表用法 | | ---- | ---- | ------------------------ | | `user.Id` | CHAR(36) GUID | **数据权限配置表** 外键 `UserId`;组织权限 `OrganizationId` | | `user.userid` | INT 自增 | JWT Claim **`mapId`**;OMS 审计字段 **`CreateUserId` / `UpdateUserId`** | | `organization.Id` | CHAR(36) GUID | 组织维度过滤;**禁止**使用自增 bigint 作为 org_id | | `t_customer.Id` | BIGINT | OMS 业务 **`CustomerId`**;销售负责范围过滤时的业务键 | > **修正(相对 V1.0)**:V1.0 中 `user_id bigint`、`org_id bigint` 与现网 RBAC 不一致,统一改为 GUID;OMS 侧负责人仍用 `CreateUserId`(INT mapId)。 --- ## 一、需求概述 ### 1.1 业务痛点 原有仅依靠 `user.OrganizationId`(单组织归属)+ 管理员无过滤(`ResolveListCustomerIdAsync` 返回 null)存在以下问题: 1. 同部门不同岗位(销售 vs 客服 vs 财务)数据可见范围无法区分; 2. 产品、运营、总部职能岗需要**跨分公司**查看指定产品线/区域数据; 3. 营收、客户隐私、成本等敏感字段无法独立管控与脱敏; 4. 不支持时间、金额、单据状态等**岗位级**动态范围限制; 5. 临时授权无时效回收;内部用户查单/查客无审计追溯; 6. 客户门户已有 CustomerId 隔离,但**运营后台**仍「登录即全量」,与合规要求不符。 ### 1.2 核心设计原则 1. **功能权限与数据权限解耦**(见导读三层模型)。 2. **多维度取交集**(内部用户): ``` 可见数据 = 组织过滤 ∩ 数据范围 ∩ 业务标签 ∩ 密级 ∩ 自定义条件 ``` 3. **密级最高优先级**:无密级权限的行直接过滤;有权限但字段级敏感则**脱敏展示**(仍返回行)。 4. **最小权限原则**:新建内部用户默认**无扩展数据权限**(仅组织默认范围,见 §2.1.4);须管理员分配或套用模板。 5. **超级管理员白名单**:角色 `Admin` 跳过五维过滤。 6. **后端唯一可信**:全部过滤在 Service/Repository 层注入;前端不做数据拦截。 7. **分模块接入**:订单、客户、充值、渠道配置等按 `DataResourceCode` 注册,统一走 `IDataScopeApplier`。 ### 1.3 五大权限控制维度(仅内部用户) | 维度 | 回答的问题 | 典型 OMS 落点 | | ---- | ---------- | ------------- | | 1. 组织 | 哪个分公司/部门的数据? | 订单扩展字段 `OrganizationId`;客户列表按销售组织 | | 2. 数据范围 | 本人 / 本组 / 本部门 / 组织树 / 全局? | `CreateUserId`、`t_customer.Salesman` | | 3. 业务标签 | 哪条产品线/区域/KPI 分区? | 销售产品、KPI 分区、客户分组 `CustomerGroup` | | 4. 数据密级 | 能否看机密/绝密字段? | 费用明细、成本、利润、客户手机号 | | 5. 自定义过滤 | 时间/金额/状态等附加条件? | 本年度订单、金额>1万、排除作废 | --- ## 二、详细功能需求 ### 2.1 维度 1:组织数据权限 #### 2.1.1 组织架构模型(对齐 RBAC `organization`) 复用 RBAC 组织树,**不另建组织主数据**: ``` 公司 → 事业部 → 分公司 → 部门 → 小组 ``` | RBAC 字段 | 说明 | | --------- | ---- | | `organization.Id` | GUID,数据权限过滤键 | | `organization.ParentId` | 树形父节点 | | `organization.Type` | `OrganizationType`:1/2/3(与 RBAC 枚举一致) | | `user.OrganizationId` | 用户**主归属**组织,作默认数据范围起点 | #### 2.1.2 组织权限范围枚举 | 编码 | 名称 | 规则说明 | | ---- | ---- | -------- | | 1 | 仅本级组织 | 仅 `OrganizationId = 授权组织`,不含下级 | | 2 | 本级 + 所有下级 | 授权组织及其子树(管理岗默认) | #### 2.1.3 业务规则 1. 平级组织默认互相隔离; 2. 支持**一人多组织**授权(多条 `user_org_perm`); 3. 支持**跨组织白名单**(总部财务、审计勾选多个分公司); 4. 用户主归属 `user.OrganizationId` 在未单独配置时,作为**默认组织范围**(本级+下级)。 #### 2.1.4 与 OMS 业务表映射 | 业务实体 | 组织字段策略 | 说明 | | -------- | ------------ | ---- | | `t_order` | **新增** `OrganizationId CHAR(36) NULL` | 创建时取自客户归属组织或操作人主组织;历史数据可批量回填 | | `t_customer` | **新增** `OrganizationId CHAR(36) NULL` | 客户归属销售组织;与 `Salesman` 联动维护 | | `t_recharge_record` | 继承 `CustomerId → t_customer.OrganizationId` | 查询时 JOIN,不必冗余 | | 渠道/系统主数据 | 默认**不受组织维限制** | 通过功能权限 + 密级控制;若需按组织隔离再扩展 | ### 2.2 维度 2:角色数据范围 #### 2.2.1 数据范围枚举 | 编码 | 名称 | SQL 语义(以订单为例) | | ---- | ---- | ---------------------- | | 1 | 个人级 | `CreateUserId = 当前 mapId` 或 `t_customer.Salesman = 当前用户账号` | | 2 | 小组级 | 同 `user.OrganizationId` 且组织 Type=小组 的全体 mapId | | 3 | 部门级 | 同部门组织子树内所有用户的 mapId | | 4 | 组织树级 | 以授权组织为根的子树内全部数据 | | 5 | 全局级 | 不追加负责人过滤(仍受组织/业务/密级/自定义约束) | #### 2.2.2 与 RBAC 角色的关系 | 配置方式 | 优先级 | 说明 | | -------- | ------ | ---- | | **角色级默认** `role_data_scope` | 低 | 角色绑定数据范围,用户继承其所有角色的**最大值**(取最宽) | | **用户级覆盖** `user_data_scope` | 高 | 显式配置则覆盖角色默认 | > 示例:用户同时有「销售专员」(个人级) 和「销售主管」(部门级) 两角色 → 有效范围为 **部门级**(取 max)。 #### 2.2.3 OMS 业务配套字段 | 字段 | 表 | 用途 | | ---- | -- | ---- | | `CreateUserId` | 各 OMS 业务表 | INT,= `user.userid`;个人级过滤 | | `Salesman` | `t_customer` | VARCHAR,可与 `user.Account` 对应;销售看自己的客户 | | `CustomerService` | `t_customer` | 客服负责范围(可选启用) | | `CustomerId` | `t_order` 等 | 先按客户归属,再按客户负责人收窄 | **内部用户查订单推荐链路**: ``` t_order → CustomerId → t_customer(Salesman / OrganizationId) → CreateUserId(下单操作人) 按 data_scope 选用 CreateUserId 或 Salesman 或组织子树 ``` ### 2.3 维度 3:业务维度权限(跨组织) #### 2.3.1 业务分类类型(OMS 落地) | biz_type 编码 | 名称 | OMS 数据源 | | ------------- | ---- | ---------- | | `product_line` | 产品线 | `t_sales_product` / 产品分类 | | `kpi_area` | KPI 分区 | `t_kpi_area` | | `customer_group` | 客户分层 | `t_customer.CustomerGroup`(0 普通,1~5 VIP) | | `warehouse` | 仓库 | `t_warehouse_address.Id` | | `region` | 区域 | 字典或 KPI 分区上级(可扩展) | #### 2.3.2 控制规则 1. 独立于组织架构,支持多选;**OR** 语义:命中任一已授权标签即可; 2. 与组织维度 **AND**:须同时满足组织范围 **且** 业务标签; 3. 用户未配置任何业务标签 = **不限制业务标签**(与 V1.0「无标签=无权限」不同,避免过度收紧;若需强制可 per-module 配置 `RequireBizTag=true`); 4. 业务表映射: | 表 | 过滤字段 | | -- | -------- | | `t_order` | `ProductCode` → 产品 → 产品线;`ConsigneeCountry` + KPI 分区 | | `t_customer` | `CustomerGroup` | | 渠道配置 | 直接按产品/分区 Id | ### 2.4 维度 4:数据密级权限(行级 + 字段级) #### 2.4.1 密级分级 | 编码 | 名称 | 典型数据 | | ---- | ---- | -------- | | 1 | 公开级 | 运单号、状态、路由 | | 2 | 内部级 | 应收费用、客户名称 | | 3 | 机密级 | 应付/成本、毛利、完整地址 | | 4 | 绝密级 | 手机号、身份证、银行信息、完整利润 | #### 2.4.2 两级控制 | 级别 | 行为 | | ---- | ---- | | **行级** | `secret_level` 高于用户授权 → **整行不返回** | | **字段级** | 行可见但字段密级超限 → **脱敏**(如 `138****1234`),接口仍 200 | #### 2.4.3 业务表字段 | 表 | 策略 | | -- | ---- | | `t_order` | 表级默认密级 2;`t_order_fee` 应付行密级 3 | | `t_customer` | 表级密级 3;Phone/Email 字段密级 4 | | `t_recharge_record` | 密级 3(含金额) | 新增通用字段(按需,非全表强制): - `SecretLevel TINYINT NOT NULL DEFAULT 2` ### 2.5 维度 5:自定义行过滤条件 #### 2.5.1 支持的条件类型 | 类型 | 示例 | 适用表 | | ---- | ---- | ------ | | 时间 | 本年度、近 3 个月、自定义区间 | `t_order.CreateTime` | | 数值 | 应收合计 > 10000 | `t_order_fee` 聚合 | | 枚举 | `OrderStatus IN (...)` | `t_order` | | 渠道 | 指定 `ProductCode` / `ChannelCode` | `t_order` | #### 2.5.2 安全实现 1. 可视化表单 → AST → SqlSugar 表达式,**禁止**管理员输入原生 SQL; 2. 按 `DataResourceCode`(如 `Order`、`Customer`、`Recharge`)区分规则; 3. 条件仅 **AND** 追加到交集结果,不改变业务接口原有筛选参数语义。 ### 2.6 特殊权限:临时数据权限 | 项 | 说明 | | -- | ---- | | 场景 | 审计、跨部门对账、临时顶岗 | | 配置 | 关联用户 GUID、全套五维副本、生效/过期时间 | | 存储 | 各 `user_*_perm` 表 `ExpireTime` 非空;或独立 `user_data_perm_grant` 快照 | | 生效 | `EffectiveTime <= NOW() < ExpireTime` 的记录参与计算;过期自动失效 | | 审计 | 标记 `IsTemporary=1`;访问日志单独索引 | ### 2.7 权限模板 预设岗位模板,一键写入角色或用户的数据权限配置: | 模板 | 组织 | 数据范围 | 业务 | 密级 | | ---- | ---- | -------- | ---- | ---- | | 普通销售 | 主归属、仅本级 | 个人级 | 指定产品线 | 1、2 | | 销售主管 | 本级+下级 | 部门级 | 全部产品线 | 1、2、3 | | 分公司经理 | 本级+下级 | 组织树级 | 全部 | 1、2、3 | | 总部产品经理 | 多组织白名单 | 全局级 | 单一产品线 | 1、2 | | 财务专员 | 本级+下级 | 组织树级 | 全部 | 含 3、4(字段脱敏策略另配) | | 客服专员 | 本级 | 个人级 | — | 1、2(客户手机号脱敏) | 支持自定义模板 CRUD;模板可绑定 RBAC **角色 Code**,新用户分配角色时自动继承。 --- ## 三、后端核心权限计算逻辑 ### 3.1 通用计算公式 记当前内部用户有效配置(已合并多角色、已过滤过期临时授权): - `OrgFilter`:组织维度 SQL 条件 - `ScopeFilter`:数据范围条件 - `BizFilter`:业务标签条件(空配置 = 恒真) - `SecretFilter`:密级条件 - `CustomFilter`:自定义条件 **最终查询条件**: ``` WHERE IsDeleted = 0 AND (Admin ? TRUE : ( OrgFilter AND ScopeFilter AND BizFilter AND SecretFilter AND CustomFilter )) AND <业务接口原有筛选> ``` **伪代码**: ```csharp async Task>> BuildDataScopeAsync( DataResourceCode resource, ICurrentUser user) { if (user.IsInRole("Admin")) return _ => true; if (OmsSessionHelper.IsCustomerLogin(user)) throw new InvalidOperationException("客户门户走 CustomerId 隔离"); var ctx = await _dataPermCache.GetOrLoadAsync(user.Id); // GUID var parts = new List>>(); parts.Add(BuildOrgFilter(ctx, resource)); parts.Add(BuildScopeFilter(ctx, user.GetId(), resource)); parts.Add(BuildBizFilter(ctx, resource)); parts.Add(BuildSecretFilter(ctx)); parts.Add(BuildCustomFilter(ctx, resource)); return parts.Aggregate((a, b) => a.And(b)); } ``` ### 3.2 超级管理员豁免 满足任一条件跳过五维过滤: 1. 用户角色包含 `Code = Admin`(RBAC 种子超管); 2. 或显式 `user.IsSuperAdmin = 1`(扩展字段,可选)。 仍受 **功能权限码** 约束(若无 `Track.Orders.Query` 仍不可调用接口)。 ### 3.3 与客户门户的并存规则 **同一套路由 `/admin/order/*`(服务内 `/order/*`)**,不按门户拆 URL;在 `OrderService` 入口按 `loginType` 分支: | loginType | 隔离方式 | | --------- | -------- | | 1 客户 | `OmsSessionHelper` → `CustomerId = effectiveCustomerId` | | 0 内部 + Admin | 不加五维 | | 0 内部 + 非 Admin | `DataScopeApplier` 五维交集 | ```mermaid flowchart TD A["POST /admin/order/list"] --> B{loginType?} B -->|1 客户| C[CustomerId 硬隔离] B -->|0 内部| D{Admin?} D -->|是| E[无五维过滤] D -->|否| F[DataScopeApplier] C --> G[查询] E --> G F --> G ``` ### 3.4 演算示例 **用户**:华南分公司销售经理(非 Admin) | 维度 | 配置 | | ---- | ---- | | 组织 | 华南分公司,本级+下级 | | 数据范围 | 组织树级 | | 业务 | 线上零售产品线 | | 密级 | 1、2、3 | | 自定义 | 2026 年、已付款 | **订单列表 SQL 语义**: ```sql WHERE t_order.IsDeleted = 0 AND t_order.OrganizationId IN (华南分公司子树 GUID 列表) AND t_order.ProductCode IN (线上零售产品 Code 列表) AND t_order.SecretLevel <= 3 AND YEAR(t_order.CreateTime) = 2026 AND t_order.OrderStatus = 'pendingPayment' -- 已付款态按业务枚举调整 -- 不含 CustomerId 全量;内部运营后台不按单客户隔离 ``` --- ## 四、数据库设计 > RBAC 库(EF)与 OMS 库(SqlSugar)可同实例不同表;**数据权限配置表建议放 RBAC 库**,与 `user`/`role`/`organization` 同库 FK。 ### 4.1 用户组织权限 `user_org_perm` | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | UserId | CHAR(36) | FK → `user.Id` | | OrganizationId | CHAR(36) | FK → `organization.Id` | | OrgScope | TINYINT | 1 仅本级 / 2 本级+下级 | | EffectiveTime | DATETIME | 生效时间,默认创建时 | | ExpireTime | DATETIME | NULL=永久 | | IsTemporary | TINYINT | 0/1 | | CreateTime | DATETIME | | ### 4.2 角色 / 用户数据范围 **`role_data_scope`** | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | RoleId | CHAR(36) | FK → `role.Id` | | DataScope | TINYINT | 1~5 | | DataResourceCode | VARCHAR(32) | 可选,空=全局默认 | **`user_data_scope`**(用户级覆盖) | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | UserId | CHAR(36) | FK → `user.Id` | | DataScope | TINYINT | 1~5 | | DataResourceCode | VARCHAR(32) | 可选 | ### 4.3 用户业务维度 `user_biz_perm` | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | UserId | CHAR(36) | | | BizType | VARCHAR(32) | product_line / kpi_area / customer_group / warehouse | | BizId | VARCHAR(64) | 业务主键(兼容 GUID 与 INT 字符串) | | ExpireTime | DATETIME | 可空 | ### 4.4 用户密级 `user_secret_perm` | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | UserId | CHAR(36) | | | SecretLevel | TINYINT | 1~4 | | DataResourceCode | VARCHAR(32) | 可选,按模块细分 | ### 4.5 用户自定义过滤 `user_filter_rule` | 字段 | 类型 | 说明 | | ---- | ---- | ---- | | Id | CHAR(36) | PK | | UserId | CHAR(36) | | | DataResourceCode | VARCHAR(32) | Order / Customer / Recharge | | FilterContent | JSON | 前端配置 AST | | FilterHash | VARCHAR(64) | 内容哈希,便于缓存失效 | | CreateTime | DATETIME | | > **不再存储 `filter_snippet` 原生 SQL 文本**;运行时由 AST 生成表达式,避免 SQL 注入与方言耦合。 ### 4.6 权限模板 `data_perm_template` / `data_perm_template_item` 模板头 + 明细行(`DimensionType` + `ItemContent` JSON),支持绑定 `RoleCode`。完整 DDL 见 **§十三** 与 `sql/install_data_permission.sql`。 **角色级五维表**(与用户对偶,供模板套用与角色继承): - `role_org_perm` · `role_data_scope` · `role_biz_perm` · `role_secret_perm` · `role_filter_rule` ### 4.7 数据权限审计 `data_perm_access_log` | 字段 | 说明 | | ---- | ---- | | UserId、DataResourceCode、ApiPath | 谁访问了什么 | | AppliedScopeJson | 本次生效的五维快照 | | QueryTime、RowCount | 性能与合规 | ### 4.8 OMS 业务表扩展(分期) | 阶段 | 表 | 新增字段 | | ---- | -- | -------- | | P1 | `t_customer` | `OrganizationId CHAR(36)` | | P1 | `t_order` | `OrganizationId CHAR(36)`、`SecretLevel TINYINT DEFAULT 2` | | P2 | `t_order_fee` | 应付行 `SecretLevel`、字段级脱敏配置 | | P2 | `t_recharge_record` | `SecretLevel` | **不强制**全表统一 `biz_type/biz_id/owner_user_id`;改用 OMS 已有字段 + JOIN 映射(见 §2.3、§2.2.3)。 --- ## 五、接口与执行流程 ### 5.1 服务组件 | 组件 | 职责 | | ---- | ---- | | `IDataPermissionRepository` | 读配置表 | | `IDataScopeContextFactory` | 合并角色+用户配置 → `DataScopeContext` | | `IDataScopeApplier` | 按 `DataResourceCode` 注入 SqlSugar `Queryable` | | `IFieldMaskingService` | 密级字段脱敏 | | `DataPermissionCache` | Redis:`data_perm:{userGuid}`,TTL 30min | ### 5.2 请求链路 1. JWT 校验 → 解析 `sub`(GUID)、`mapId`、`loginType`; 2. `[Authorize(Permission = "...")]` 功能权限校验(RBAC 完善后启用); 3. `loginType=1` → `OmsSessionHelper` CustomerId 分支; 4. `loginType=0` → 加载 `DataScopeContext` → Applier 注入条件; 5. 查询结果 → `IFieldMaskingService` 按密级脱敏; 6. 写 `data_perm_access_log`(异步,可采样)。 ### 5.3 权限变更与缓存 | 事件 | 动作 | | ---- | ---- | | 用户数据权限 CRUD | 删除 `data_perm:{userId}` | | 用户角色变更 | 同上 | | 组织树移动 | 批量失效该组织下用户缓存 | | 模板绑定角色更新 | 失效该角色所有用户 | ### 5.4 管理端 API(建议前缀 `/rbac/data-perm/*`) | 方法 | 路径 | 说明 | | ---- | ---- | ---- | | GET | `/rbac/data-perm/get-by-user` | 查询用户五维配置 | | PUT | `/rbac/data-perm/save-user` | 保存用户五维 | | GET | `/rbac/data-perm/template/list` | 模板列表 | | POST | `/rbac/data-perm/template/apply` | 模板套用到用户/角色 | | GET | `/rbac/data-perm/access-log` | 审计分页 | 权限码建议:`Rbac.DataPerm.Query`、`Rbac.DataPerm.Update`(在 `PermissionDefinitionProvider` 注册)。 ### 5.5 登录响应扩展(可选) `GET /rbac/auth/get-current-user` 增加只读摘要(不含 SQL): ```json { "dataScopeSummary": { "isAdmin": false, "orgNames": ["华南分公司"], "dataScope": 4, "secretLevels": [1, 2, 3] } } ``` 供前端展示「当前数据范围」提示,**不可替代**后端过滤。 --- ## 六、分模块接入清单(OMS) | DataResourceCode | 服务 | 主表 | 组织字段 | 范围字段 | 备注 | | ---------------- | ---- | ---- | -------- | -------- | ---- | | `Order` | `OrderService` | `t_order` | OrganizationId | CreateUserId + Customer.Salesman | 同 `/admin/order/list`,内部用户走 Applier | | `Customer` | `CustomerService` | `t_customer` | OrganizationId | Salesman / CustomerService | `/customer/list` 内部查询 | | `Recharge` | `RechargeService` | `t_recharge_record` | via Customer | CreateUserId | 审核列表 | | `ChannelProduct` | `SalesProductService` | `t_sales_product` | — | 业务标签 product_line | 配置类,密级偏低 | | `Xhs` | `Xhs*Service` | 各 t_xhs_* | — | CustomerId(已有) | 内部用户走五维;客户走 Xhs CustomerId | **客户门户模块**(`/admin/order/*`、Xhs 客户侧):**不接入**五维 Applier,保持现有 `OmsSessionHelper`。 --- ## 七、前端功能需求 ### 7.1 用户数据权限编辑(`/system/users` 或 RBAC 用户详情) - Tab:组织 / 数据范围 / 业务标签 / 密级 / 自定义过滤; - 组织树勾选:复用 `GET /rbac/organization/get-tree`; - 权限模板一键填充; - 临时权限:EffectiveTime + ExpireTime。 ### 7.2 权限模板管理 - CRUD + 绑定 RoleCode; - 预览「有效 SQL 语义」只读说明(不给 SQL 编辑)。 ### 7.3 权限审计日志 - TablePro:用户、模块、时间、AppliedScopeJson 展开; - 导出 Excel。 ### 7.4 密级与脱敏配置 - 维护 `DataResourceCode + 字段名 → SecretLevel` 映射表; - 与接口响应 DTO 字段对齐。 ### 7.5 前端约束 - `hasPermission()` 恢复真实校验(rbac §9.4)后,按钮与数据范围提示分离; - **禁止**前端按组织/密级过滤列表数据。 --- ## 八、非功能性需求 | 类别 | 要求 | | ---- | ---- | | 安全 | 后端过滤;AST 白名单运算符;字段脱敏;临时权限强制过期 | | 性能 | Redis 缓存 Context;`(OrganizationId, CreateTime)`、`(CustomerId)` 索引 | | 扩展 | 新模块注册 `DataResourceCode` + Applier 策略类 | | 易用 | 模板 + 角色继承;组织树可视化 | | 审计 | 权限变更日志 + 访问采样日志 | --- ## 九、典型岗位配置示例 ### 示例 1:普通销售(内部) - 组织:主归属销售部,仅本级 - 数据范围:个人级(Salesman = 本人账号) - 业务:负责产品线 - 密级:1、2 - 接口:`Track.Orders.Query` + 五维过滤 ### 示例 2:分公司销售经理 - 组织:本分公司,本级+下级 - 数据范围:组织树级 - 业务:全部产品线 - 密级:1、2、3 - 自定义:本年度 ### 示例 3:总部产品经理(跨组织) - 组织:多分公司白名单 - 数据范围:全局级 - 业务:单一产品线 - 密级:1、2 ### 示例 4:客户主账号(门户) - **不配置五维** - `loginType=1`,`CustomerId` 硬隔离 - 功能权限:角色 `Customer` + 分配的 PermissionCode ### 示例 5:子客户 - **不配置五维** - 订单归属主客户 `CustomerId`(推荐策略,oms §1.4) - 功能权限:角色 `CustomerSub_{userId}` ⊆ 主客户权限 ### 示例 6:超级管理员 - 角色 `Admin`:跳过五维 - 仍配置功能权限码(默认全开) --- ## 十、实施分期 | 阶段 | 内容 | 依赖 | | ---- | ---- | ---- | | **P0** | `OrderService` 同路由按 `loginType` 分支;内部非 Admin 接 `DataScopeApplier`(替换管理员无过滤) | 无路由变更 | | **P1** | 建 RBAC 数据权限表;`OrganizationId` 落库客户/订单;组织+数据范围两维;Order/Customer 接入 | RBAC 迁移 | | **P2** | 业务标签、密级、字段脱敏 | 产品/KPI 主数据 | | **P3** | 自定义过滤 AST、临时权限、审计页 | P1 稳定 | | **P4** | 权限模板与角色联动;`[Authorize(Permission)]` 全量启用 | RBAC 包升级 | --- ## 十一、与现有实现的差异对照 | 项 | 本文目标 | 当前 oms0414-back | | -- | -------- | ----------------- | | 客户门户隔离 | CustomerId 强制 | ✅ `OmsSessionHelper` 已实现 | | 内部用户查订单 | 五维过滤 | ⚠️ `ResolveListCustomerIdAsync` 管理员返回 null,**无过滤** | | 组织主数据 | 复用 RBAC `organization` | ✅ 已有,未用于数据过滤 | | 功能权限 | PermissionCode + 数据权限 | ⚠️ 细粒度 Permission 拦截待实现 | | 用户 ID | GUID 配置 + INT 审计 | ✅ 与 RBAC 一致 | --- ## 十二、交付说明 1. 本文档与 **`rbac系统设计-060801.md`**、**`oms系统设计-050801.md`** 配套使用,冲突时以**双登录主体分工**(导读)为准; 2. 建表 SQL 见 **`oms0414-back/sql/install_data_permission.sql`**、**`migrate_oms_data_permission_columns.sql`**; 3. 新业务模块在 §六 注册 `DataResourceCode` 后接入 `IDataScopeApplier`; 4. 客户门户 **禁止** 接入五维 Applier,继续用 `CustomerId` 隔离; 5. V1.0 中「所有业务表统一五字段」改为 **分期扩展 + 字段映射**,降低 OMS 全表改造风险。 --- ## 十三、表结构详设 ### 13.1 ER 关系 ```mermaid erDiagram user ||--o{ user_org_perm : has user ||--o{ user_data_scope : has user ||--o{ user_biz_perm : has user ||--o{ user_secret_perm : has user ||--o{ user_filter_rule : has role ||--o{ role_org_perm : has role ||--o{ role_data_scope : has role ||--o{ role_biz_perm : has role ||--o{ role_secret_perm : has role ||--o{ role_filter_rule : has organization ||--o{ user_org_perm : scoped data_perm_template ||--o{ data_perm_template_item : contains data_resource ||--o{ data_perm_field_mask : masks ``` ### 13.2 表清单 | 表名 | 库 | 说明 | | ---- | -- | ---- | | `user_org_perm` | RBAC | 用户组织权限 | | `role_org_perm` | RBAC | 角色组织权限 | | `user_data_scope` | RBAC | 用户数据范围(覆盖角色) | | `role_data_scope` | RBAC | 角色数据范围 | | `user_biz_perm` | RBAC | 用户业务标签 | | `role_biz_perm` | RBAC | 角色业务标签 | | `user_secret_perm` | RBAC | 用户密级 | | `role_secret_perm` | RBAC | 角色密级 | | `user_filter_rule` | RBAC | 用户自定义过滤 AST | | `role_filter_rule` | RBAC | 角色自定义过滤 AST | | `data_resource` | RBAC | 模块注册(Order/Customer…) | | `data_perm_field_mask` | RBAC | 字段脱敏规则 | | `data_perm_template` | RBAC | 权限模板头 | | `data_perm_template_item` | RBAC | 模板明细 | | `data_perm_change_log` | RBAC | 配置变更审计 | | `data_perm_access_log` | RBAC | 访问审计(采样) | | `t_customer.OrganizationId` | OMS | P1 扩展 | | `t_order.OrganizationId/SecretLevel` | OMS | P1 扩展 | ### 13.3 有效权限合并规则(运行时唯一标准) > **配置页**(`get-by-user`)展示库表原值;**运行时**(`get-effective` / 列表过滤)按下表合并。二者不可混用。 | 维度 | 角色侧 | 用户直配 | 合并算法 | 空配置默认 | | ---- | ------ | -------- | -------- | ---------- | | 组织 | 多角色 **UNION** | **UNION** 到角色结果 | 展开子树后 `OrganizationId IN (...)` | 无任一条时回退 `user.OrganizationId`(scope=2) | | 数据范围 | 多角色取 **MAX** | **按资源覆盖**角色结果 | 见 §14.1 步骤 5 | 无配置 → **Personal(1)** | | 业务标签 | 多角色 **UNION** | **UNION** | 同类型 `bizId` 并集;有过滤则 `OR` 命中 | **空=不限制** | | 密级 | **UNION** | **UNION** | `SecretLevel IN (授权集合)` | 无配置 → **[1,2]**(公开+内部) | | 自定义过滤 | 多规则 **AND** | **AND** | AST 逐条 AND | 无规则 → 恒真 | **过期**:运行时排除 `EffectiveTime > NOW()` 或 `ExpireTime <= NOW()` 的行。 **Admin**:角色 `Code=Admin` → 跳过五维,但仍走功能 PermissionCode。 **客户**:`loginType=1` → 不算五维,走 `CustomerId`(与本文无关)。 ### 13.4 枚举常量 **OrgScope** | 值 | 名称 | | -- | ---- | | 1 | SelfOnly | | 2 | SelfAndChildren | **DataScope** | 值 | 名称 | 说明 | | -- | ---- | ---- | | 1 | Personal | `CreateUserId=mapId` 或 `Salesman=Account` | | 2 | Team | 同小组组织下用户 | | 3 | Department | 同部门子树 | | 4 | OrgTree | 授权组织子树 | | 5 | Global | 不追加负责人条件 | **BizType** | 值 | OMS 映射 | | -- | -------- | | `product_line` | `t_sales_product` 分类 / ProductCode 前缀 | | `kpi_area` | `t_kpi_area.Id` | | `customer_group` | `t_customer.CustomerGroup` | | `warehouse` | `t_warehouse_address.Id` / `t_order.WarehouseId` | | `region` | 字典扩展 | **SecretLevel** | 值 | 名称 | | -- | ---- | | 1 | Public | | 2 | Internal | | 3 | Confidential | | 4 | TopSecret | **DataResourceCode**(见 `data_resource.Code`) `Order` · `Customer` · `Recharge` · `ChannelProduct` · `Xhs` **Filter AST 运算符**(白名单) `eq` · `ne` · `in` · `notIn` · `gt` · `gte` · `lt` · `lte` · `between` · `like` · `isNull` · `isNotNull` **Filter AST 示例** ```json { "logic": "and", "conditions": [ { "field": "CreateTime", "op": "between", "value": ["2026-01-01", "2026-12-31"] }, { "field": "OrderStatus", "op": "in", "value": ["pendingPayment", "pendingShip"] }, { "field": "DeclaredSum", "op": "gte", "value": 10000 } ] } ``` **模板 ItemContent 按 DimensionType** | DimensionType | JSON 结构 | | ------------- | --------- | | `org` | `{ "organizationId": "guid", "orgScope": 2 }` | | `data_scope` | `{ "dataScope": 3, "dataResourceCode": "Order" }` | | `biz` | `{ "bizType": "product_line", "bizId": "US_AIR", "bizName": "美线空运" }` | | `secret` | `{ "secretLevels": [1,2,3], "dataResourceCode": null }` | | `filter` | `{ "dataResourceCode": "Order", "filterContent": { ...AST } }` | --- ## 十四、接口与核心逻辑详设 > 路由 `/rbac/data-perm/*`;分页 `{ total, items }`;写操作单事务、软删旧行+插新行。 ### 14.0 产品决策(已确认) | # | 决策 | 结论 | | - | ---- | ---- | | 1 | 订单路由 | **不分离**;`/admin/order/*` 内按 `loginType` 分支 | | 2 | 新员工默认 dataScope | **Personal(1)** | | 3 | 改自己的数据权限 | **禁止**(403) | | 4 | 模板批量套用 | **部分失败继续**,返回 `failures[]` | | 5 | access-log 自动写入 | **P1 不做**;前端审计页已预留 | ### 14.0.1 前端页面(oms0414) | 路由 | 组件 | 说明 | | ---- | ---- | ---- | | `/system/dataPerm/users` | `views/system/dataPerm/users/index.vue` | 用户列表 + 五维配置抽屉 | | `/system/dataPerm/templates` | `views/system/dataPerm/templates/index.vue` | 模板 CRUD + 批量套用 | | `/system/dataPerm/audit` | `views/system/dataPerm/audit/index.vue` | 变更日志 / 访问日志 Tab | API 客户端:`src/api/rbac/data-perm.ts`(后端未就绪时会 404,页面已 catch)。 --- ### 14.1 三个核心类(必须先实现) | 类 | 职责 | | -- | ---- | | `EffectiveScopeBuilder` | 角色+用户+默认 → `DataScopeContext` | | `DataScopeApplier` | Context → SqlSugar 条件(`/order/list` 等) | | `FilterAstValidator` | 校验/编译 filterContent | ```csharp record DataScopeContext { bool IsAdmin; Guid UserId; int MapId; string Account; string DataResourceCode; IReadOnlySet OrganizationIds; int DataScope; // 1~5 IReadOnlyList ResolvedOwnerIds; // Team/Dept 预解析 mapId IReadOnlyDictionary BizFilters; // null=不限制 IReadOnlySet SecretLevels; IReadOnlyList CustomFilters; } ``` 缓存:`data_perm:ctx:{userId}:{resourceCode}` TTL 30min;配置变更 DEL 该 user 全部 ctx。 --- ### 14.2 运行时合并规则 | 维度 | 算法 | | ---- | ---- | | 组织 | `role_org_perm` ∪ `user_org_perm` → OrgScope 展开子树 → `IN`;全无 → `user.OrganizationId`(scope=2) | | 数据范围 | 用户专码 > 用户全局 > 角色专码 MAX > 角色全局 MAX;全无 → **Personal(1)** | | 业务 | 并集;**空=不限制** | | 密级 | 并集;空 → **[1,2]** | | 自定义 | 全部 **AND** | | 过期 | 运行时排除;`get-by-user` 编辑页仍展示过期行并标 `isExpired` | **`get-by-user`** 仅 user_* 直配;**`get-effective-by-user`** 走 Builder 合并结果。 --- ### 14.3 `EffectiveScopeBuilder.BuildAsync` 入参:`userId`, `dataResourceCode`(如 `Order`) 1. 查 user,不存在 → 404 2. 角色含 `Admin` → `{ IsAdmin=true }` 3. 组织:role∪user 行 → FilterEffective → 展开子树 → `OrganizationIds` 4. 数据范围:用户专码/全局优先,否则角色 MAX,否则 1 5. 业务:role∪user → 分组;空 → null 6. 密级:并集;空 → [1,2] 7. 过滤:role∪user 同 resource → Compile → AND 8. dataScope∈{2,3} 时预解析 `ResolvedOwnerIds`;无 `user.OrganizationId` → 业务异常 --- ### 14.4 `DataScopeApplier`(`/admin/order/*` 同路由) 在 `OrderService` 列表/详情/导出等读路径入口: 1. `loginType=1` → 仅 `CustomerId`(现有 `OmsSessionHelper`) 2. `loginType=0` + Admin → 不加五维 3. `loginType=0` + 非 Admin → `BuildAsync(currentUser, "Order")` 后: - `OrganizationId IN ctx.OrganizationIds` - `SecretLevel IN ctx.SecretLevels` - BizFilters 按注册表翻译 - dataScope:Personal → `CreateUserId=mapId OR Salesman=Account`;Team/Dept → `CreateUserId IN ResolvedOwnerIds`;OrgTree/Global → 不加 Owner - CustomFilters AND 4. DTO 映射阶段 `FieldMaskingService` 脱敏 5. **P1 不写 access-log** --- ### 14.5 HTTP 共性 - 鉴权:Bearer + PermissionCode - 可配置主体:内部用户;纯 Customer/CustomerSub → save 拒绝 - **`save-user`:`targetUserId == 操作者 userId` → 403「不能修改自己的数据权限」** - 双库:RBAC EF + OMS SqlSugar(`biz-options` 读 OMS) --- ### 14.6 接口逐步逻辑 #### `GET /meta` — `Rbac.DataPerm.Query` 代码常量枚举 + `data_resource` 表 + 每 resource 的 `filterableFields` 字典;不读当前用户权限。 #### `GET /biz-options` — Query `bizType` 必填。分支:`product_line`→`t_sales_product`;`kpi_area`→`t_kpi_area`;`customer_group`→0~5 常量;`warehouse`→`t_warehouse_address`。keyword 模糊分页。 #### `GET /get-by-user` — Query `userId` 查 user + 五张 `user_*`(IsDeleted=0,含过期);纯客户 → 400;JOIN 组织名;**不调 Builder**。 #### `GET /get-effective-by-user` — Query `userId`, `dataResourceCode` `BuildAsync` → 组织名 → `summaryText`;Admin 短路。 #### `PUT /save-user` — Query `userId` 校验(组织启用、枚举、去重、AST、**禁止 self-edit**)→ 事务:软删旧 user_* → 插新 → change_log → 失效缓存 → 返回 get-by-user。 #### `GET/PUT get-by-role / save-role` 对称读写 `role_*`;save-role 后失效该角色所有 user 的 ctx 缓存。 #### 模板 `list/get/create/update/delete` create/update 校验 item JSON Schema;内置模板不可删、不可改 code。 #### `POST /template/apply` 加载模板 → `PermBundle`;foreach targetId:失败则 **failures+=reason, continue**(部分失败继续);replace/merge 规则:org/biz/secret Union,scope Max,filter 追加 AND;调内部 save;返回 `{ successCount, failures[] }`。 #### `field-mask/list|save` 按 resource 全量覆盖脱敏规则。 #### `change-log/list` 配置变更只读分页(**P1 必做**)。 #### `access-log/list` 只读分页;**P1 无自动写入**,表结构预留。 #### `POST /invalidate-cache` 运维 DEL Redis ctx。 #### `get-current-user` 扩展 `loginType=1` → null;Admin → summary;否则 Builder 仅取 summaryText + secretLevels。 --- ### 14.7 路径速查 | 方法 | 路径 | PermissionCode | | ---- | ---- | -------------- | | GET | `/rbac/data-perm/meta` | Rbac.DataPerm.Query | | GET | `/rbac/data-perm/biz-options` | Rbac.DataPerm.Query | | GET | `/rbac/data-perm/get-by-user` | Rbac.DataPerm.Query | | GET | `/rbac/data-perm/get-effective-by-user` | Rbac.DataPerm.Query | | PUT | `/rbac/data-perm/save-user` | Rbac.DataPerm.Update | | GET | `/rbac/data-perm/get-by-role` | Rbac.DataPerm.Query | | PUT | `/rbac/data-perm/save-role` | Rbac.DataPerm.Update | | GET | `/rbac/data-perm/template/list` | Rbac.DataPerm.Template | | GET | `/rbac/data-perm/template/get` | Rbac.DataPerm.Template | | POST | `/rbac/data-perm/template/create` | Rbac.DataPerm.Template | | PUT | `/rbac/data-perm/template/update` | Rbac.DataPerm.Template | | DELETE | `/rbac/data-perm/template/delete` | Rbac.DataPerm.Template | | POST | `/rbac/data-perm/template/apply` | Rbac.DataPerm.Update | | GET | `/rbac/data-perm/field-mask/list` | Rbac.DataPerm.Query | | PUT | `/rbac/data-perm/field-mask/save` | Rbac.DataPerm.Update | | GET | `/rbac/data-perm/change-log/list` | Rbac.DataPerm.Audit | | GET | `/rbac/data-perm/access-log/list` | Rbac.DataPerm.Audit | | POST | `/rbac/data-perm/invalidate-cache` | Rbac.DataPerm.Update | ### 14.8 权限码 `Rbac.DataPerm` · `.Query` · `.Update` · `.Template` · `.Audit` ### 14.9 错误码补充 | 场景 | 消息 | | ---- | ---- | | 修改自己的数据权限 | 不能修改自己的数据权限 | | 纯客户账号 save | 客户账号不支持数据权限配置 | | template apply 单条失败 | 记入 failures[].reason,不中断批次 | ### 14.10 实施顺序 1. EffectiveScopeBuilder + 单测 2. meta / get-by-user / get-effective 3. save-user / save-role + change-log 4. Applier 接入 `/order/list`(同路由 loginType 分支) 5. template / biz-options 6. field-mask;(access-log 写入 Phase 2) ### 14.11 JSON 契约摘要 **UserDataPermDto**:userId、orgPerms[]、dataScopes[]、bizPerms[]、secretPerms[]、filterRules[](含 isExpired)。 **SaveUserDataPermInput**:同上五数组,全量覆盖;缺省数组=清空该维。 **EffectiveDataScopeDto**:isAdmin、organizationIds[]、dataScope、secretLevels[]、bizFilters[]、summaryText。 **template/apply 响应**:`{ successCount, failures: [{ targetId, reason }] }`。 --- ## 十五、SQL 执行顺序 ```bash # 1. RBAC 数据权限表 + 种子 mysql ... < sql/install_data_permission.sql # 2. OMS 业务表扩展列 mysql ... < sql/migrate_oms_data_permission_columns.sql # 3. 菜单与 Admin 权限码 mysql ... < sql/seed_data_perm_rbac_menu.sql ``` > `PermissionDefinitionProvider` 中注册 §14.9 权限码后,需重新分配角色或执行 seed 脚本。