# 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. action. module.. page. report. ``` ### 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.` | | action | 业务动作 | `action.` | | module | 数据模块操作 | `module..` | | page | 页面入口 | `page.` | | report | 报表入口 | `report.` | --- ## 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. Action 定义 ↓ action. s_module ↓ module.. Page 定义 ↓ page. Report 定义 ↓ report. ``` 因此新增模块 `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. │ └── action. ↓ 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. action. module.. page. report. ``` 其中: - `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.` 也无法保证全局唯一。 2. 字段权限被拆成多个彼此独立的 `view/edit/query/export` 权限点,不能直接表达“不可见”“可见但只读”“可见且可编辑”等实际权限状态,也容易出现 `edit=1、view=0` 这种无效组合。 新方案将“权限点身份”和“字段访问策略”分开: - `s_power` 只保存菜单、页面、报表、模块、动作等可执行能力; - `s_user_field_permission` 保存字段访问级别及查询/导出能力; - `b_id` 只做内部主键,业务侧使用不可变且全局唯一的 `b_code`; - 动作权限必须带上所属资源的命名空间,不再使用裸 `action.`。 ### 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..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.` 根据 `b_module_id` 或菜单归属补全 owner,生成 `action....execute`。无法唯一确定归属的记录不得自动合并。 3. 原字段权限按用户、模块、字段聚合:有有效 `edit` 则为 `edit`,否则有有效 `view` 则为 `view`,否则为 `hidden`;`query`、`export` 映射到附加标志,并受 `hidden` 约束。 4. 迁移完成后旧字段权限记录删除或归档,权限服务只读取新模型,避免新旧模型同时生效。 ### 21.8 数据范围配置 UI:默认范围 + 操作级覆盖 数据范围底层仍按“模块 + 操作”保存,但管理界面不要求管理员一开始就配置每个操作。采用两层配置: #### 默认配置 用户选择一个模块后,先设置一个默认数据范围: ```text 默认数据范围:仅本人 / 仅本部门 / 本部门及下属 / 全部 / 自定义 ``` 默认范围应用于该模块所有需要数据过滤的操作。底层只保存一条模块默认记录: ```text * -> 默认范围 ``` 对于不适用的操作(例如用户没有 `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) ``` 最终效果就是:张三可以查询和导出本部门费用,但只能修改本人创建的费用。这个例子也是保留操作级数据范围能力的主要原因。