311 lines
16 KiB
Markdown
311 lines
16 KiB
Markdown
# FMS 权限体系、字段权限与用户个性化设计
|
||
|
||
## 0. 总体约定
|
||
|
||
本系统是 SQL 驱动的内部 ERP,权限表和模块配置表主要用于生成通用查询、写入和界面配置规则。权限采用“入口默认拒绝、其它能力默认放行、用户配置负责收紧”的策略:
|
||
|
||
- `menu/access` 和 `action/execute` 属于入口或动作权限,必须存在有效的用户授权记录;`page/access`、`report/access` 仅在页面或报表需要独立入口控制时启用,启用后同样必须存在有效授权记录;
|
||
- `module/read|create|update|delete|export` 未配置用户授权时默认放行;`s_power` 权限点或用户授权记录存在 `b_canuse = 0` 时明确禁用;
|
||
- 字段没有有效的用户字段策略时,按模块字段默认配置执行,不额外限制查询、编辑和导出;有效字段策略只能收紧权限;
|
||
- 数据范围没有配置时按 `all` 处理;有配置时先使用操作级 `override`,没有 `override` 再使用模块默认的 `*` 范围;同一层级多条范围按 OR 合并;
|
||
- 用户个性化只影响列表布局、查询收起态、顺序和宽度,不得越过字段权限、字段启用状态或数据范围;
|
||
- 数据库层不建 check 约束,字段取值及组合规则由业务层校验(见各表下方「业务层约束」)。
|
||
|
||
权限过滤、字段裁剪和数据范围条件最终必须在后端 SQL 中落实,前端隐藏菜单、按钮和字段只用于界面展示。
|
||
|
||
### 资源与权限归属
|
||
|
||
权限以业务模块为核心,不以菜单或 Vue 路由为核心:
|
||
|
||
| 资源 | 主要职责 | 是否授予模块数据权限 |
|
||
| --- | --- | --- |
|
||
| `menu` | 导航入口、菜单显示 | 否 |
|
||
| `page` | 逻辑页面访问(按需启用) | 否 |
|
||
| `module` | 读取、新增、修改、删除、导出、字段和数据范围 | 是 |
|
||
| `action` | 审核、反审核、核销等具体业务动作 | 通常绑定业务模块 |
|
||
| Vue 路由/组件 | 技术实现和页面复用 | 否 |
|
||
|
||
- 菜单或页面权限不会自动授予所使用模块的权限;
|
||
- 一个页面可以使用多个模块,一个模块可以被多个页面复用,模块权限始终按模块自身计算;
|
||
- Vue 路由和组件不是稳定的权限标识。`page` 权限使用稳定的逻辑页面编码,不使用路由路径或组件名称;
|
||
- `s_menu.b_module_id`(如果保留)只表示菜单的默认/主模块,不能表示页面使用的全部模块,也不能触发权限继承;
|
||
- 普通菜单页面通常只配置菜单入口权限。只有需要独立直接访问的页面,才额外定义 `page/access` 权限,避免客户重复勾选菜单和页面。
|
||
|
||
### 复合页面和公共页面
|
||
|
||
以海运详细页为例:
|
||
|
||
```text
|
||
海运详细页
|
||
├── sea_main
|
||
├── container_main
|
||
├── container_detail
|
||
├── finance_main
|
||
└── finance_detail
|
||
```
|
||
|
||
技术人员只需配置一次“页面使用哪些模块”的关系。页面打开后,各模块分别检查自己的 `read`、字段权限和数据范围:
|
||
|
||
- 必需模块没有读取权限时,页面不能完成正常业务,可以拒绝进入;
|
||
- 非必需模块没有读取权限时,隐藏对应页签或区域;
|
||
- 页面权限、菜单权限和模块权限互不继承;
|
||
- 同一个公共 Vue 页面被多个模块使用时,以当前页面配置和实际 `module_id` 判断权限,不以路由名称判断权限。
|
||
|
||
动作权限按业务归属配置:有明确业务主模块时使用 `action.module.<module_id>.<action_id>.execute`;真正跨模块且没有主模块的页面流程才使用 `action.page.<page_id>.<action_id>.execute`;不把动作绑定到 Vue 路由或公共组件。
|
||
|
||
### 权限计算顺序
|
||
|
||
一个页面或接口按以下顺序计算最终权限:
|
||
|
||
1. 用户账号和对应资源是否启用;
|
||
2. 菜单入口是否有有效授权;存在独立页面/报表权限时,再检查对应入口授权;
|
||
3. 当前动作是否有有效 `action/execute` 授权;
|
||
4. 当前模块操作是否被 `s_power` 或 `s_user_power.b_canuse = 0` 明确禁用,未配置时按默认放行;
|
||
5. 根据模块字段元数据和用户字段策略裁剪字段、查询和导出能力;
|
||
6. 根据数据范围的 `override`、`default` 或 `all` 生成 SQL 行条件;
|
||
7. 在以上结果基础上应用用户列表布局和查询布局偏好。
|
||
|
||
页面显示权限和接口执行权限必须使用同一套计算规则。前端可以提前隐藏无权限内容,但不能代替后端 SQL 的最终判断。
|
||
|
||
## 1. 表结构
|
||
|
||
### 1.1 s_power(权限定义表)
|
||
|
||
```sql
|
||
create table dbo.s_power (
|
||
b_id bigint not null, -- 内部主键,雪花/序列生成,不表达业务含义
|
||
b_code varchar(300) not null, -- 稳定、全局唯一的业务键,由系统生成
|
||
-- 示例:menu.cw_fee.access / module.cw_fee.read / action.module.cw_fee.audit.execute
|
||
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 类型:menu/page/module
|
||
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)
|
||
);
|
||
|
||
create index IX_s_power_resource
|
||
on dbo.s_power (b_power_type, b_owner_type, b_owner_id, b_resource_id, b_canuse);
|
||
```
|
||
|
||
业务层约束(数据库不建 check 约束,由业务层校验):
|
||
|
||
- `b_power_type` 只能取 `menu / page / report / module / action`;
|
||
- `menu / page / report` 类型:`b_owner_type`、`b_owner_id` 必须为空,`b_capability` 固定为 `access`;
|
||
- `module` 类型:`b_owner_type`、`b_owner_id` 必须为空,`b_capability` 只能取 `read / create / update / delete / export`;
|
||
- `action` 类型:`b_owner_type` 只能取 `menu / page / module`,`b_owner_id` 必填,`b_capability` 固定为 `execute`。
|
||
|
||
### 1.2 s_user_power(用户权限授权表)
|
||
|
||
```sql
|
||
create table dbo.s_user_power (
|
||
b_user_id varchar(50) not null,
|
||
b_power_id bigint not null, -- 引用 s_power.b_id(内部主键)
|
||
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);
|
||
```
|
||
|
||
### 1.3 s_user_field_permission(用户字段访问策略表)
|
||
|
||
```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(50) 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)
|
||
);
|
||
|
||
create index IX_s_user_field_permission_module
|
||
on dbo.s_user_field_permission (b_user_id, b_module_id, b_canuse, b_field);
|
||
```
|
||
|
||
业务层约束(数据库不建 check 约束,由业务层校验):
|
||
|
||
- `b_access_mode` 只能取 `hidden / view / edit`;
|
||
- `b_access_mode = 'hidden'` 时,`b_allow_query` 和 `b_allow_export` 必须同时为 `0`;
|
||
- 没有 `b_canuse = 1` 的有效记录时,该字段按模块字段默认配置执行,不视为 `hidden`。
|
||
|
||
### 1.4 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 = 'default' 时允许使用
|
||
|
||
b_scope_level varchar(10) not null default 'default',
|
||
-- default / override;default 使用 b_operation='*',override 为操作级覆盖
|
||
|
||
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,
|
||
primary key (b_user_id, b_module_id, b_operation, b_scope_level, b_scope_no)
|
||
);
|
||
|
||
create index IX_s_user_data_scope_module
|
||
on dbo.s_user_data_scope (b_user_id, b_module_id, b_operation, b_scope_level, b_scope_type);
|
||
```
|
||
|
||
数据范围计算规则:
|
||
|
||
1. 先查询当前操作的 `override` 记录;存在时只使用 `override`,忽略模块默认范围;
|
||
2. 没有 `override` 时使用 `operation = '*'` 的 `default` 记录;
|
||
3. 默认范围和操作覆盖都没有配置时,按 `all` 处理,不追加数据行过滤条件;
|
||
4. 同一层级存在多条范围记录时按 OR 合并;`b_scope_no` 只用于区分同级范围行;
|
||
5. `read`、`export`、`update`、`delete` 在 SQL 行条件中应用范围;`create` 在写入前校验新增数据是否符合范围。
|
||
|
||
### 配置流程
|
||
|
||
技术人员按以下顺序配置,避免在每个页面重复维护权限:
|
||
|
||
1. 配置模块、字段和模块默认布局;
|
||
2. 配置菜单、逻辑页面及页面使用的模块关系;
|
||
3. 由系统按菜单、页面、模块和动作定义生成 `s_power`;
|
||
4. 在用户授权界面按入口、动作、模块限制、字段限制、数据范围分组展示;
|
||
5. 客户通常只需要勾选可进入的菜单和可执行的动作,再配置少量模块禁用、敏感字段和数据范围限制。
|
||
|
||
客户不需要为每个页面重复勾选 `module.read`、`module.update` 等权限。模块操作默认放行,只有禁止某个操作时才保存 `s_user_power.b_canuse = 0`;字段和数据范围也只保存需要收紧的例外配置。
|
||
|
||
页面与模块的多对多关系属于系统配置数据,建议由独立的页面模块关系配置维护。该关系表的最终结构在《FMS新系统核心表结构设计.md》统一整理前,本文件只约定其业务语义,不在本文件重复定义核心表。
|
||
|
||
### 1.5 s_user_field_pref(用户列表布局偏好表)
|
||
|
||
```sql
|
||
create table dbo.s_user_field_pref (
|
||
b_user_id varchar(50) not null,
|
||
b_module_id varchar(50) not null,
|
||
b_field varchar(50) not null,
|
||
b_mode varchar(10) not null, -- view / edit
|
||
b_visible tinyint not null default 1,
|
||
b_xh int not null default 0,
|
||
b_width int null,
|
||
b_updatedatetime datetime2 null,
|
||
primary key (b_user_id, b_module_id, b_field, b_mode)
|
||
);
|
||
|
||
create index IX_s_user_field_pref_order
|
||
on dbo.s_user_field_pref (b_user_id, b_module_id, b_mode, b_xh, b_field);
|
||
```
|
||
|
||
### 1.6 s_user_query_field_pref(用户查询布局偏好表)
|
||
|
||
```sql
|
||
create table dbo.s_user_query_field_pref (
|
||
b_user_id varchar(50) not null,
|
||
b_module_id varchar(50) not null,
|
||
b_field varchar(50) not null,
|
||
b_quick_visible tinyint not null default 1,
|
||
b_xh int not null default 0,
|
||
b_updatedatetime datetime2 null,
|
||
primary key (b_user_id, b_module_id, b_field)
|
||
);
|
||
|
||
create index IX_s_user_query_field_pref_order
|
||
on dbo.s_user_query_field_pref (b_user_id, b_module_id, b_xh, b_field);
|
||
```
|
||
|
||
用户个性化规则:
|
||
|
||
- `s_user_field_pref` 区分查看列表和可编辑列表两套布局;
|
||
- `s_user_query_field_pref` 只影响普通查询收起态,展开态和高级查询仍使用所有有效查询字段;
|
||
- 没有个人偏好时,回落到模块默认的 `s_field_view`、`s_field_edit`、`s_field_query` 配置;
|
||
- 个人偏好只能调整可见字段的显示、顺序和宽度,不能显示隐藏字段、启用停用字段或扩大查询/导出权限;
|
||
- “恢复默认”删除当前用户、当前模块对应的个人偏好记录。
|
||
|
||
---
|
||
|
||
## 2. Demo 数据
|
||
|
||
示例沿用 `cw_fee` 费用模块。示例用户:张三(费用会计)、李四(财务经理)。
|
||
|
||
### 2.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 |
|
||
|
||
### 2.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 | 1006 | 0 | 2026-02-10 14:30: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 |
|
||
|
||
张三的 `1006` 对应 `module.cw_fee.create`,`1008` 对应 `module.cw_fee.delete`,两条记录都已停用(`b_canuse = 0`),因此张三没有新增和删除权限。模块操作权限没有用户授权记录时默认放行,所以需要显式停用记录表达禁止操作。
|
||
|
||
### 2.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 |
|
||
|
||
张三对金额字段是“可见、只读、可查询、不可导出”,对内部成本字段完全不可见;李四可以编辑和导出金额,但内部成本字段仍然只读。
|
||
|
||
未配置用户字段策略的字段按模块默认配置执行,不额外限制查询、编辑和导出;上表中的记录只表示对特定字段的收紧。
|
||
|
||
### 2.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 |
|
||
|
||
- 张三使用模块默认范围(`*` + department)读取本部门数据,update 有操作级覆盖(override + own),只能修改本人录入的费用;
|
||
- 李四使用默认范围 department_tree 处理一般操作,delete 有操作级覆盖为 all,可删全模块数据。
|