front/docs/数据权限设计.md
zzh 2063621daf feat: 新增财务模块并优化组织管理、角色权限与我的订单
- 新增财务菜单与应收应付、充值记录、账户流水页面及路由

- 组织管理改为左右分栏,优化左侧树层级展示与选中交互

- 角色权限分配改为列表形式,支持各级菜单独立勾选

- 我的订单工具栏按钮样式与独立 loading 交互优化

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 12:15:14 +08:00

1049 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多维度组合数据权限设计方案 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<Expression<Func<T, bool>>> BuildDataScopeAsync<T>(
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<Expression<Func<T, bool>>>();
parts.Add(BuildOrgFilter<T>(ctx, resource));
parts.Add(BuildScopeFilter<T>(ctx, user.GetId<int>(), resource));
parts.Add(BuildBizFilter<T>(ctx, resource));
parts.Add(BuildSecretFilter<T>(ctx));
parts.Add(BuildCustomFilter<T>(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<Guid> OrganizationIds;
int DataScope; // 1~5
IReadOnlyList<int> ResolvedOwnerIds; // Team/Dept 预解析 mapId
IReadOnlyDictionary<string, string[]> BizFilters; // null=不限制
IReadOnlySet<int> SecretLevels;
IReadOnlyList<CompiledFilter> 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 脚本。