Files
workspace/code/fms/字段权限与用户个性化设计.md
T
2026-09-07 22:45:21 +08:00

991 lines
28 KiB
Markdown
Raw 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.
# FMS 权限体系设计文档
## 1. 设计目标
本权限体系用于 FMS / ERP 元数据驱动架构,核心目标:
1. 操作权限与数据权限分离。
2. 权限直接绑定菜单、动作、模块、字段、页面、报表等资源。
3. Power ID 不要求人工维护,直接由资源类型、资源 ID 和操作组成。
4. 用户直接授权,不引入角色继承。
5. 数据权限独立于菜单,不因为用户从哪个菜单进入而改变数据范围。
6. 用户个性化设置不能突破权限。
7. 权限默认拒绝(Default Deny)。
---
## 2. 权限模型总览
整个权限体系分为两条线:
```text
┌──────────────┐
│ 资源定义 │
└──────┬───────┘
│
┌──────────┼──────────┐
│ │ │
菜单/动作 模块/字段 页面/报表
│ │ │
└──────────┼──────────┘
↓
s_power
↓
s_user_power
↓
用户操作权限
┌──────────────┐
│ 数据模块 │
└──────┬───────┘
↓
s_user_data_scope
↓
s_user_data_scope
↓
数据范围
```
因此:
- `s_power`:定义「能做什么」
- `s_user_power`:定义「用户能做什么」
- `s_user_data_scope`:定义「用户在某个模块、某个操作下可以作用于哪些数据」
---
## 3. Power ID 设计
### 3.1 核心原则
`b_id` 不再使用随机 ID,也不要求人工创建类似:
```text
power_cw_fee_update
power_cw_fee_audit
power_cw_fee_amount_edit
```
而是直接使用稳定、可解析的权限编码。
统一格式:
```text
menu.<MenuID>
action.<ActionID>
module.<ModuleID>.<Operation>
page.<PageID>
report.<ReportID>
```
### 3.2 Power ID 示例
**菜单权限**
```text
menu.cw_fee
```
表示:可以进入 / 使用费用管理菜单。
**动作权限**
```text
action.cw_fee_audit
```
表示:可以执行费用审核动作。
**模块权限**
```text
module.cw_fee.read
module.cw_fee.create
module.cw_fee.update
module.cw_fee.delete
module.cw_fee.export
```
**页面权限**
```text
page.cw_fee
```
**报表权限**
```text
report.cw_fee_summary
```
---
## 4. Power 类型
| b_power_type | 用途 | ID 格式 |
| ------------ | ------------ | --------------------------------------- |
| menu | 菜单入口 | `menu.<MenuID>` |
| action | 业务动作 | `action.<ActionID>` |
| module | 数据模块操作 | `module.<ModuleID>.<Operation>` |
| page | 页面入口 | `page.<PageID>` |
| report | 报表入口 | `report.<ReportID>` |
---
## 5. 操作权限语义
### 5.1 menu
```text
menu.cw_fee
```
用于判断用户是否拥有菜单入口权限。
菜单权限只解决:
- 用户能不能进入这个菜单。
不负责:
- 数据范围
- 字段权限
- 审核权限
- 删除权限
### 5.2 action
例如:
```text
action.cw_fee_audit
action.cw_fee_unaudit
action.cw_fee_writeoff
```
统一使用:
- `b_power_type = action`
- `b_operation = execute`
动作权限解决:用户能不能执行这个具体业务动作。
### 5.3 module
例如:
```text
module.cw_fee.read
module.cw_fee.create
module.cw_fee.update
module.cw_fee.delete
module.cw_fee.export
```
模块权限解决:用户对该业务模块具有什么数据操作能力。
### 5.4 page
例如:
```text
page.cw_fee
```
用于页面访问控制。
页面权限和菜单权限可以同时存在:
```text
menu.cw_fee
page.cw_fee
```
菜单解决入口展示 / 进入菜单,页面解决具体页面访问。
### 5.5 report
例如:
```text
report.cw_fee_summary
```
用于报表访问权限。
报表权限本身不等于数据权限。例如用户拥有 `report.cw_fee_summary`,还需要根据报表对应的数据模块执行数据范围控制。
---
## 6. 核心表结构
以下表结构暂时不加入索引、唯一约束、外键约束,后续根据实际运行情况再补充。
### 6.1 s_power
权限定义表。
```sql
create table dbo.s_power (
b_id varchar(300) not null,
b_name nvarchar(200) not null,
b_i18n varchar(150) null,
b_power_type varchar(20) not null,
-- menu / action / module / page / report
b_resource varchar(128) null,
-- action / page / report 等资源 ID
b_menu_id varchar(50) null,
-- 所属菜单,主要用于 menu / action
b_module_id varchar(50) null,
-- 所属业务模块
b_operation varchar(30) not null,
-- menu/page/report: access
-- action: execute
-- module: read/create/update/delete/export
b_canuse tinyint not null default 1,
b_xh int not null default 0
);
```
`b_id` 示例:
```text
menu.cw_fee
action.cw_fee_audit
module.cw_fee.read
module.cw_fee.update
page.cw_fee
report.cw_fee_summary
```
这里的 `b_id` 就是 Power 的业务编码。
不再需要 `power_cw_fee_update`、`power_cw_fee_audit` 这种额外编码。
---
## 7. s_user_power
用户权限授权表。
```sql
create table dbo.s_user_power (
b_user_id varchar(50) not null,
b_power_id varchar(300) not null,
b_canuse tinyint not null default 1,
b_updatedatetime datetime2 null
);
```
语义:
- 存在有效记录 = 用户拥有权限
- 不存在记录 = 用户没有权限
- `b_canuse = 0` = 当前授权无效
当前设计不增加 allow / deny,即采用:
**默认拒绝,明确授权。**
---
## 8. 数据权限
操作权限与数据权限必须分开。
例如:
```text
module.cw_fee.read
```
只表示:用户具有读取费用数据的能力。
它并不表示:用户可以读取所有费用数据。
数据范围直接由 `s_user_data_scope` 控制。
---
## 9. s_user_data_scope
用户数据权限授权表。
```sql
create table dbo.s_user_data_scope (
b_user_id varchar(50) not null,
b_module_id varchar(50) not null,
b_operation varchar(30) not null,
-- * / read / create / update / delete / export;* 表示模块默认范围
b_scope_level varchar(10) not null default 'default',
-- default / override;default 使用 b_operation='*'
b_scope_no int not null default 1,
b_scope_type varchar(30) not null,
-- own / department / department_tree / all / custom
b_scope_field varchar(50) null,
b_scope_value varchar(100) null,
b_updatedatetime datetime2 null
);
```
多个同级范围同时存在时:
```text
Scope A OR Scope B OR Scope C
```
例如:
```text
张三
cw_fee
* / read / update
├── department
└── own
```
表示同一操作下多个范围按 OR 合并;具体操作没有 override 时回退到 `b_operation='*'` 的默认范围。
---
## 11. 完整示例
假设系统有:
- 模块:`cw_fee` = 费用管理
- 菜单:`cw_fee` = 费用管理
- 动作:`cw_fee_audit` = 审核、`cw_fee_unaudit` = 反审核、`cw_fee_writeoff` = 核销
- 字段:`mx_amount` = 金额
### 11.1 自动产生 Power
菜单:
```text
menu.cw_fee
```
动作:
```text
action.cw_fee_audit
action.cw_fee_unaudit
action.cw_fee_writeoff
```
模块:
```text
module.cw_fee.read
module.cw_fee.create
module.cw_fee.update
module.cw_fee.delete
module.cw_fee.export
```
---
## 12. 用户授权示例
张三拥有:
- `menu.cw_fee`
- `module.cw_fee.read`
- `module.cw_fee.update`
- `action.cw_fee_audit`
```sql
insert into dbo.s_user_power (
b_user_id,
b_power_id,
b_canuse
)
values
('zhangsan', 'menu.cw_fee', 1),
('zhangsan', 'module.cw_fee.read', 1),
('zhangsan', 'module.cw_fee.update', 1),
('zhangsan', 'action.cw_fee_audit', 1);
```
张三没有 `module.cw_fee.delete`,并且金额字段策略为 `view`,所以:
- 不能删除费用
- 不能修改金额字段
---
## 13. 数据范围示例
直接授权:
```sql
insert into dbo.s_user_data_scope (
b_user_id,
b_module_id,
b_operation,
b_scope_level,
b_scope_no,
b_scope_type,
b_scope_field
)
values (
'zhangsan',
'cw_fee',
'*',
'default',
1,
'department',
'b_department_id'
);
```
此时张三拥有 `module.cw_fee.read`,并且 read 数据范围回退为本部门。若再增加 `update + override + own`,则修改范围只限本人创建的数据。
最终不是「张三可以读取全部费用」,而是「张三可以读取自己有权限范围内的费用」。
---
## 14. 权限执行顺序
一个业务操作建议按照以下顺序判断:
```text
① 用户是否有效
↓
② 菜单 / 页面入口权限
↓
③ Action 权限
↓
④ Module 数据操作权限
↓
⑤ Field 字段权限
↓
⑥ Data Scope 数据范围
↓
⑦ 业务状态 / 业务规则
↓
⑧ 执行操作
```
例如「审核费用」:
```text
用户
↓
menu.cw_fee
↓
page.cw_fee
↓
action.cw_fee_audit
↓
module.cw_fee.update
↓
数据范围
↓
费用当前状态 = 送审
↓
执行审核
```
---
## 15. 不同权限解决不同问题
| 问题 | 权限 |
| -------------- | ------------------------------- |
| 能不能看到菜单 | `menu.*` |
| 能不能进入页面 | `page.*` |
| 能不能执行审核 | `action.*` |
| 能不能修改费用 | `module.cw_fee.update` |
| 能不能看到金额 | `s_user_field_permission.b_access_mode in ('view','edit')` |
| 能不能修改金额 | `s_user_field_permission.b_access_mode = 'edit'` |
| 能不能查询金额 | `s_user_field_permission.b_allow_query = 1` |
| 能不能导出金额 | `s_user_field_permission.b_allow_export = 1` |
| 能不能看报表 | `report.*` |
| 能看哪些数据 | `s_user_data_scope` |
---
## 16. 与元数据体系的关系
Power 不应该成为另一套独立的资源体系,而应该从现有元数据自动生成。
```text
s_menu
↓
menu.<MenuID>
Action 定义
↓
action.<ActionID>
s_module
↓
module.<ModuleID>.<Operation>
Page 定义
↓
page.<PageID>
Report 定义
↓
report.<ReportID>
```
因此新增模块 `cw_air_fee`,系统可以自动注册:
```text
module.cw_air_fee.read
module.cw_air_fee.create
module.cw_air_fee.update
module.cw_air_fee.delete
module.cw_air_fee.export
```
新增字段 `mx_tax` 后,由模块字段元数据和用户字段策略直接控制,不生成 `s_power` 权限点。
---
## 17. 权限与用户个性化的关系
用户个性化配置(`s_user_field_pref`、`s_user_query_field_pref`)只能调整:
- 显示
- 排序
- 宽度
- 查询字段默认状态
不能增加权限。
最终优先级:
```text
字段 b_canuse
↓
权限
↓
系统默认布局
↓
用户个性化
```
例如:用户字段策略为 `hidden` 时,即使 `s_user_field_pref.b_visible = 1`,也不能显示金额字段。
---
## 18. 最终推荐的核心关系
```text
┌───────────────┐
│ s_module │
└───────┬───────┘
│
├──────────────┐
↓ ↓
s_field
│
├──────────────┐
↓ ↓
s_power s_user_data_scope
│
↓
s_user_power
│
↓
b_user
```
菜单和动作:
```text
s_menu
│
├── menu.<MenuID>
│
└── action.<ActionID>
↓
s_power
```
页面和报表:
```text
page / report
↓
s_power
```
---
## 19. 最终结论
本设计的核心原则可以归纳为:
- `s_power.b_id` 是内部主键,`s_power.b_code` 是系统生成的可读业务键;
- 权限唯一性由资源类型、资源编码、owner 和能力字段共同保证;
- 字段权限直接使用 `s_user_field_permission` 的访问策略;
- 数据范围直接使用 `s_user_data_scope`,不再建立独立的范围模板表。
权限业务键示例:
```text
menu.<MenuID>
action.<ActionID>
module.<ModuleID>.<Operation>
page.<PageID>
report.<ReportID>
```
其中:
- `s_power` 是统一权限注册表。
- `s_user_power` 是用户操作权限。
- `s_user_data_scope` 是用户数据范围授权。
- 操作权限和数据权限完全分离。
- 菜单不负责数据范围。
- 用户个性化不能突破权限。
- 当前不引入角色继承。
- 后续如果增加角色,可以在 `s_user_power` 之外增加角色授权层,而无需改变 Power 编码体系。
---
## 20. Demo 数据(4 张表)
以下为新权限模型的示例数据,沿用 `cw_fee` 费用模块。示例用户:张三(费用会计)、李四(财务经理)。数据范围直接保存在 `s_user_data_scope`,不再维护独立的范围模板表。
### 20.1 s_power(权限定义)
| b_id | b_code | b_name | b_power_type | b_resource_id | b_owner_type | b_owner_id | b_capability | b_canuse | b_xh |
| ---: | --- | --- | --- | --- | --- | --- | --- | ---: | ---: |
| 1001 | menu.cw_fee.access | 费用管理菜单 | menu | cw_fee | null | null | access | 1 | 10 |
| 1002 | page.cw_fee.access | 费用页面 | page | cw_fee | null | null | access | 1 | 20 |
| 1003 | action.module.cw_fee.audit.execute | 费用审核 | action | audit | module | cw_fee | execute | 1 | 30 |
| 1004 | action.module.cw_fee.unaudit.execute | 费用反审核 | action | unaudit | module | cw_fee | execute | 1 | 40 |
| 1005 | module.cw_fee.read | 费用读取 | module | cw_fee | null | null | read | 1 | 50 |
| 1006 | module.cw_fee.create | 费用新增 | module | cw_fee | null | null | create | 1 | 60 |
| 1007 | module.cw_fee.update | 费用修改 | module | cw_fee | null | null | update | 1 | 70 |
| 1008 | module.cw_fee.delete | 费用删除 | module | cw_fee | null | null | delete | 1 | 80 |
| 1009 | module.cw_fee.export | 费用导出 | module | cw_fee | null | null | export | 1 | 90 |
| 1010 | report.cw_fee_summary.access | 费用汇总报表 | report | cw_fee_summary | null | null | access | 1 | 100 |
`b_id` 是内部主键,`b_code` 是全局唯一业务键。动作的 `b_resource_id` 是动作编码,`b_owner_type/b_owner_id` 标识所属模块;因此不同模块可以同时拥有 `audit` 动作而不冲突。
### 20.2 s_user_power(用户授权)
| b_user_id | b_power_id | b_canuse | b_updatedatetime |
| --- | ---: | ---: | --- |
| zhangsan | 1001 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1002 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1005 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1007 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1009 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1008 | 0 | 2026-02-10 14:30:00 |
| zhangsan | 1003 | 1 | 2026-02-01 09:00:00 |
| zhangsan | 1010 | 1 | 2026-02-01 09:00:00 |
| lisi | 1001 | 1 | 2026-01-15 10:00:00 |
| lisi | 1002 | 1 | 2026-01-15 10:00:00 |
| lisi | 1005 | 1 | 2026-01-15 10:00:00 |
| lisi | 1006 | 1 | 2026-01-15 10:00:00 |
| lisi | 1007 | 1 | 2026-01-15 10:00:00 |
| lisi | 1008 | 1 | 2026-01-15 10:00:00 |
| lisi | 1009 | 1 | 2026-01-15 10:00:00 |
| lisi | 1003 | 1 | 2026-01-15 10:00:00 |
| lisi | 1004 | 1 | 2026-01-15 10:00:00 |
| lisi | 1010 | 1 | 2026-01-15 10:00:00 |
张三的 `1008` 对应 `module.cw_fee.delete`,记录存在但已停用,因此仍然没有删除权限。
### 20.3 s_user_field_permission(字段访问策略)
| b_user_id | b_module_id | b_field | b_access_mode | b_allow_query | b_allow_export | b_canuse |
| --- | --- | --- | --- | ---: | ---: | ---: |
| zhangsan | cw_fee | mx_amount | view | 1 | 0 | 1 |
| zhangsan | cw_fee | mx_internal_cost | hidden | 0 | 0 | 1 |
| lisi | cw_fee | mx_amount | edit | 1 | 1 | 1 |
| lisi | cw_fee | mx_internal_cost | view | 1 | 0 | 1 |
张三对金额字段是“可见、只读、可查询、不可导出”;对内部成本字段完全不可见。李四可以编辑和导出金额,但内部成本字段仍然只读。
### 20.4 s_user_data_scope(用户数据范围授权)
| b_user_id | b_module_id | b_operation | b_scope_level | b_scope_type | b_scope_field | b_scope_value | b_scope_no | b_updatedatetime |
| --------- | ----------- | ----------- | ------------- | ---------------- | --------------- | ------------- | ---------- | ------------------- |
| zhangsan | cw_fee | * | default | department | b_department_id | null | 1 | 2026-02-01 09:00:00 |
| zhangsan | cw_fee | update | override | own | b_inputuser_id | null | 1 | 2026-02-01 09:00:00 |
| lisi | cw_fee | * | default | department_tree | b_department_id | null | 1 | 2026-01-15 10:00:00 |
| lisi | cw_fee | delete | override | all | null | null | 1 | 2026-01-15 10:00:00 |
要点:
- 张三使用模块默认范围读取本部门数据,但 update 有操作级覆盖,只能修改本人录入的费用;
- 李四使用默认范围处理一般操作,但 delete 有操作级覆盖为 `all`,经理可删全模块数据。
### 20.5 组合结果解读
| 能力 | 张三(费用会计) | 李四(财务经理) |
| ---------------- | ---------------------------------------- | -------------------------------- |
| 看菜单 / 页面 | ✅ | ✅ |
| 读取费用数据 | ✅ 本部门 | ✅ 本部门及下属部门 |
| 新增 | ❌ 无 module.create | ✅ |
| 修改 | ✅ 仅本人录入的单据 | ✅ 本部门及下属部门的单据 |
| 删除 | ❌ 授权已停用(b_canuse=0) | ✅ 全部数据 |
| 审核 / 反审核 | ✅ 可审核,❌ 不可反审核 | ✅ 都可以 |
| 金额字段 | 可见、只读、可查询、不可导出 | 可见、可编辑、可查询、可导出 |
| 内部成本字段 | 不可见 | 可见但只读 |
| 汇总报表 | ✅ | ✅(报表数据仍受 read 范围约束) |
---
## 21. 权限模型重构(当前生效方案)
### 21.1 重构原因
旧方案有两个结构性问题:
1. `s_power.b_id` 同时承担数据库主键和业务权限编码。权限编码一旦变化会影响授权记录;动作编码在不同菜单/模块下重复时,`action.<ActionID>` 也无法保证全局唯一。
2. 字段权限被拆成多个彼此独立的 `view/edit/query/export` 权限点,不能直接表达“不可见”“可见但只读”“可见且可编辑”等实际权限状态,也容易出现 `edit=1、view=0` 这种无效组合。
新方案将“权限点身份”和“字段访问策略”分开:
- `s_power` 只保存菜单、页面、报表、模块、动作等可执行能力;
- `s_user_field_permission` 保存字段访问级别及查询/导出能力;
- `b_id` 只做内部主键,业务侧使用不可变且全局唯一的 `b_code`;
- 动作权限必须带上所属资源的命名空间,不再使用裸 `action.<ActionID>`。
### 21.2 s_power:内部主键与业务编码分离
```sql
create table dbo.s_power (
b_id bigint not null, -- 内部主键,雪花/序列生成,不表达业务含义
b_code varchar(300) not null, -- 稳定、全局唯一的业务键,由系统生成
b_name nvarchar(200) not null,
b_i18n varchar(150) null,
b_power_type varchar(20) not null, -- menu/page/report/module/action
b_resource_id varchar(128) not null, -- 当前权限对应的资源编码
b_owner_type varchar(20) null, -- action 的 owner 类型
b_owner_id varchar(128) null, -- action 的 owner 编码
b_capability varchar(30) not null, -- access/execute/read/create/update/delete/export
b_canuse tinyint not null default 1,
b_xh int not null default 0,
constraint PK_s_power primary key (b_id),
constraint UQ_s_power_code unique (b_code),
constraint CK_s_power_type check (b_power_type in ('menu','page','report','module','action')),
constraint CK_s_power_shape check (
(b_power_type in ('menu','page','report')
and b_owner_type is null and b_owner_id is null
and b_capability = 'access')
or (b_power_type = 'module'
and b_owner_type is null and b_owner_id is null
and b_capability in ('read','create','update','delete','export'))
or (b_power_type = 'action'
and b_owner_type in ('menu','page','module')
and b_owner_id is not null
and b_capability = 'execute')
)
);
create index IX_s_power_resource
on dbo.s_power (b_power_type, b_owner_type, b_owner_id, b_resource_id, b_canuse);
```
`b_code` 是由结构化身份规范化生成的唯一键,不允许人工随意修改。资源编码应使用稳定的业务键(建议只允许字母、数字、`_`、`-`),更名应通过资源迁移完成,而不是直接改写已发布权限的身份。推荐格式:
```text
menu.cw_fee.access
page.cw_fee.access
report.cw_fee_summary.access
module.cw_fee.read
action.module.cw_fee.audit.execute
```
动作至少包含 `owner_type + owner_id + action_id` 三段。两个模块都可以定义 `audit`,但它们会生成不同的 `b_code`。跨多个资源的动作必须为每个资源建立独立权限点,或指定明确的系统级 owner。
### 21.3 s_user_power:引用内部主键
```sql
create table dbo.s_user_power (
b_user_id varchar(50) not null,
b_power_id bigint not null,
b_canuse tinyint not null default 1,
b_updatedatetime datetime2 null,
constraint PK_s_user_power primary key (b_user_id, b_power_id)
);
create index IX_s_user_power_user
on dbo.s_user_power (b_user_id, b_canuse, b_power_id);
```
授权、审计和关联查询使用 `b_power_id`;接口、缓存键、日志中需要可读标识时使用 `s_power.b_code`。权限名称或编码生成规则变化不会破坏数据库关联。
### 21.4 字段权限独立为访问策略
字段权限是有顺序的访问级别,不是四个互不相关的动作。新增用户字段策略表:
```sql
create table dbo.s_user_field_permission (
b_user_id varchar(50) not null,
b_module_id varchar(50) not null,
b_field varchar(128) not null,
b_access_mode varchar(10) not null, -- hidden / view / edit
b_allow_query tinyint not null default 0,
b_allow_export tinyint not null default 0,
b_canuse tinyint not null default 1,
b_updatedatetime datetime2 null,
constraint PK_s_user_field_permission
primary key (b_user_id, b_module_id, b_field),
constraint CK_s_user_field_permission_mode
check (b_access_mode in ('hidden','view','edit')),
constraint CK_s_user_field_permission_capability
check (b_access_mode <> 'hidden' or (b_allow_query = 0 and b_allow_export = 0))
);
create index IX_s_user_field_permission_module
on dbo.s_user_field_permission (b_user_id, b_module_id, b_canuse, b_field);
```
| `b_access_mode` | 返回字段 | 页面显示 | 可编辑 |
| --------------- | -------- | -------- | ------ |
| `hidden` | 否 | 否 | 否 |
| `view` | 是 | 是 | 否 |
| `edit` | 是 | 是 | 是 |
`b_allow_query` 和 `b_allow_export` 是附加能力,不能把 `hidden` 字段变成可查询或可导出字段。没有有效记录时按 `hidden` 处理。
系统元数据中的 `s_field_view`、`s_field_edit`、`s_field_query` 仍然只负责默认布局、只读、禁用和查询布局,不承担用户授权。最终字段状态按以下规则计算:
```text
用户字段策略(hidden/view/edit)
↓
模块字段元数据:b_visible、b_readonly、b_disabled(只能收紧)
↓
业务状态与业务规则
↓
最终字段状态
```
因此 `edit` 遇到 `b_readonly=1` 或 `b_disabled=1` 时降为 `view`,`b_visible=0` 时最终为 `hidden`。用户个性化不能恢复 `hidden` 字段,也不能把 `view` 变成 `edit`。
### 21.5 字段授权示例
```sql
insert into dbo.s_user_field_permission
(b_user_id, b_module_id, b_field, b_access_mode, b_allow_query, b_allow_export)
values
('zhangsan', 'cw_fee', 'mx_amount', 'view', 1, 0),
('lisi', 'cw_fee', 'mx_amount', 'edit', 1, 1);
```
写接口必须同时检查 `module.<module>.update`、字段最终为 `edit`、以及 update 数据范围。
### 21.6 权限判定顺序(重构后)
```text
① 用户有效
② menu/page/report 入口权限(如适用)
③ action 权限(如适用)
④ module 操作权限
⑤ 字段策略:hidden / view / edit + query/export
⑥ 数据范围
⑦ 元数据只读/禁用、业务状态和业务规则
⑧ 执行操作
```
入口权限、模块权限、字段策略、数据范围是 AND 关系;同一用户多个数据范围仍按 OR 合并。
### 21.7 旧数据迁移规则
1. 原 `s_power.b_id` 字符串迁移到新表的 `b_code`;新 `b_id` 重新生成,`s_user_power.b_power_id` 通过旧编码映射到新主键。
2. 原 `action.<ActionID>` 根据 `b_module_id` 或菜单归属补全 owner,生成 `action.<OwnerType>.<OwnerID>.<ActionID>.execute`。无法唯一确定归属的记录不得自动合并。
3. 原字段权限按用户、模块、字段聚合:有有效 `edit` 则为 `edit`,否则有有效 `view` 则为 `view`,否则为 `hidden`;`query`、`export` 映射到附加标志,并受 `hidden` 约束。
4. 迁移完成后旧字段权限记录删除或归档,权限服务只读取新模型,避免新旧模型同时生效。
### 21.8 数据范围配置 UI:默认范围 + 操作级覆盖
数据范围底层仍按“模块 + 操作”保存,但管理界面不要求管理员一开始就配置每个操作。采用两层配置:
#### 默认配置
用户选择一个模块后,先设置一个默认数据范围:
```text
默认数据范围:仅本人 / 仅本部门 / 本部门及下属 / 全部 / 自定义
```
默认范围应用于该模块所有需要数据过滤的操作。底层只保存一条模块默认记录:
```text
* -> 默认范围
```
对于不适用的操作(例如用户没有 `module.<module>.delete`),不生成数据范围记录也不会产生权限。
#### 操作级覆盖
在默认范围旁提供“按操作单独设置”入口。管理员展开后,可以针对具体操作覆盖默认值:
```text
查询 read :跟随默认范围 = 本部门
新增 create :跟随默认范围
修改 update :仅本人
删除 delete :无权限或不配置
导出 export :跟随默认范围 = 本部门
```
操作级覆盖只改变该操作的数据过滤条件,不会授予操作权限。用户仍然必须拥有对应的 `s_power` 权限。
#### 保存与读取规则
`s_user_data_scope` 使用来源标识和通配操作,区分默认值和覆盖值:
```sql
b_scope_level varchar(10) not null default 'default'
-- default = 来自模块默认范围
-- override = 操作级覆盖
b_operation = '*'
-- 仅 default 级别允许使用 '*'
```
同一用户、模块、操作最多保留一组生效配置:
- 没有具体操作的 `override` 记录时,运行时回退到 `b_operation='*'` 的 `default` 范围;
- 存在 `override` 记录时,只对该操作使用覆盖范围;
- 一个操作可以配置多条范围,多条范围之间仍按 `OR` 合并;
- UI 中的“无权限”表示不生成该操作的范围,同时仍以 `s_user_power` 为最终操作权限判断依据。
#### 典型示例
管理员只配置:
```text
cw_fee 默认数据范围 = 本部门
```
然后展开操作级配置,将修改覆盖为“仅本人”:
```text
s_user_data_scope
cw_fee + * + department (default)
cw_fee + update + own (override)
```
最终效果就是:张三可以查询和导出本部门费用,但只能修改本人创建的费用。这个例子也是保留操作级数据范围能力的主要原因。