338 lines
15 KiB
Markdown
338 lines
15 KiB
Markdown
# FMS 权限设计
|
||
|
||
> 本文是权限部分的**定稿设计**,以《FMS新系统核心表结构设计.md》的表结构为基础,替代《字段权限与用户个性化设计.md》中与本文冲突的表述(差异见第 8 节)。
|
||
> 用户个性化(`s_user_field_pref`、`s_user_query_field_pref`)不在本文范围内,后续单独处理。
|
||
|
||
## 0. 设计定位
|
||
|
||
### 0.1 系统特征(决定设计取向的前提)
|
||
|
||
| 特征 | 现状 | 对权限设计的影响 |
|
||
| --- | --- | --- |
|
||
| 部署方式 | **一客户一数据库** | 权限点只需在本库内唯一,编码不带租户前缀;不存在跨租户数据串户风险 |
|
||
| 架构 | 元数据驱动(`s_module` / `s_field` / `s_field_view` …) | 权限点必须**自动生成**,不能手工维护清单 |
|
||
| 客户可定制 | 可加模块、加字段 | 权限点需要**增量同步**机制 |
|
||
| 客户规模 | 中小货代,多数无专职 IT | 模型必须简单到客户管理员能自行配置 |
|
||
| 用户规模 | 几十 ~ 几百 | 不需要 SAP 级别的授权对象模型 |
|
||
|
||
### 0.2 结论:对应用友 / 金蝶的三层模型
|
||
|
||
**认知模型对标用友 / 金蝶**(功能权限 / 数据权限 / 字段权限 + 角色),理由不是技术最优,而是**客户和实施顾问无需学习成本**,且规模匹配。
|
||
|
||
**不采用**:
|
||
|
||
- SAP 授权对象模型(为跨国集团设计,规模不匹配,交付成本高)
|
||
- Salesforce 四层共享模型(Profile + Permission Set + OWD + Sharing,计算复杂)
|
||
- 通用权限框架 / ABAC / 运行时策略引擎(明确不做)
|
||
|
||
**借鉴两点**:
|
||
|
||
- 权限点自动生成(Salesforce 思路,利用本系统元数据驱动的优势)
|
||
- 数据范围支持选择多个组织节点(CargoWise 的 Branch 思路,覆盖"只看上海 + 深圳分公司")
|
||
|
||
---
|
||
|
||
## 1. 权限模型
|
||
|
||
### 1.1 四类权限
|
||
|
||
| 类别 | 回答的问题 | 载体 | 默认策略 |
|
||
| --- | --- | --- | --- |
|
||
| **入口** | 能不能进 | `menu` / `page` / `report` | 默认拒绝 |
|
||
| **动作** | 能不能做 | `module.<id>.<action>` | 全部默认拒绝(权限就是权限) |
|
||
| **字段** | 能看/改哪些列 | `s_user_field_permission` | 跟随模块默认,只收紧 |
|
||
| **数据范围** | 能看哪些行 | `s_user_data_scope` | 未配置视为 `all` |
|
||
|
||
### 1.2 入口 ≠ 动作
|
||
|
||
- **入口**是一维的:能不能打开(菜单可见、页面可访问)。勾了"能进费用管理"**不代表**能改数据。
|
||
- **动作**是多维的:对某个模块能做什么。
|
||
|
||
两者互不继承,符合《核心表结构》"菜单权限不自动授予模块权限"的约定。
|
||
|
||
### 1.3 权限就是权限
|
||
|
||
不区分「内置动作」与「业务动作」。查看 / 新增 / 修改 / 删除 / 导出、审核 / 核销 / 关闭 / 作废等都是同一种**动作权限**,只有「勾选」与「未勾选」两种状态。
|
||
|
||
| 状态 | 含义 |
|
||
| --- | --- |
|
||
| 勾选 | 有 `s_user_power` 授权记录(`b_canuse = 1`) |
|
||
| 未勾选 | 无授权记录 = 拒绝 |
|
||
|
||
**全部动作统一默认拒绝**。简化点:
|
||
|
||
- 一种存储:只存「授权记录」,没有「禁止记录」
|
||
- 一种默认策略:未勾选 = 拒绝
|
||
- 一种行为:勾选即授权
|
||
|
||
> 客户不能新建动作权限点——业务动作需要对应的后端代码,客户建了也无效。客户界面只提供勾选。
|
||
|
||
### 1.4 角色(可选层)
|
||
|
||
采用**主角色 + 个人例外**两层,不做角色继承角色:
|
||
|
||
```
|
||
用户 → 主角色(继承,只有一个)+ 个人例外(覆盖角色中的少数项)
|
||
```
|
||
|
||
- 新员工入职 = 选一个角色
|
||
- 个别人特殊 = 加几条例外
|
||
- 改角色 = 该角色所有人跟着变(与 SAP / 用友行为一致)
|
||
- 查询只有两层,不是无限递归
|
||
|
||
角色不是强制项:不引入角色也能用(直接给用户配),但引入后配置量大幅下降。产品内置「货代标准权限包」(操作员 / 客服 / 费用会计 / 财务经理 / 只读查询),实施时导入客户库后微调。
|
||
|
||
---
|
||
|
||
## 2. 权限点编码规范
|
||
|
||
| 类型 | 编码 | 示例 |
|
||
| --- | --- | --- |
|
||
| 菜单入口 | `menu.<module_id>.access` | `menu.cw_fee.access` |
|
||
| 页面入口 | `page.<page_id>.access` | `page.sea_detail.access` |
|
||
| 报表入口 | `report.<report_id>.access` | `report.cw_fee_summary.access` |
|
||
| 内置动作 | `module.<module_id>.<read\|create\|update\|delete\|export>` | `module.cw_fee.delete` |
|
||
| 业务动作 | `module.<module_id>.<action_id>` | `module.cw_fee.audit` |
|
||
|
||
规则:
|
||
|
||
- 全部小写,点分分隔;`<module_id>` 用 `s_module.b_id`
|
||
- **一客户一库,编码在本库内唯一即可**,不加租户前缀
|
||
- 权限点由系统生成,**不允许手工新增业务动作**
|
||
- `s_power.b_code` 是稳定业务键;`s_power.b_id` 是内部主键,业务代码只依赖 `b_code`
|
||
|
||
### 与旧模型的差异
|
||
|
||
| 项 | 旧(`b_user_power` / `s_module_power`) | 本文 |
|
||
| --- | --- | --- |
|
||
| 权限点标识 | `${module_id}.${雪花ID}`,跨环境 ID 不同,配置无法迁移 | 语义化 `b_code`,可跨环境迁移 |
|
||
| 动作编码 | `action.module.xx.yy.execute` | `module.xx.yy` |
|
||
|
||
> 现有 `stores/permissions.js` 依赖旧表(`b_user_power` / `s_module_power`),迁移到新模型后需同步改造(见第 6 节)。
|
||
|
||
---
|
||
|
||
## 3. 权限点的自动生成与升级同步
|
||
|
||
### 3.1 生成规则
|
||
|
||
| 来源 | 生成内容 |
|
||
| --- | --- |
|
||
| `s_module` 模块 | `menu.<id>.access` + 5 个内置动作 |
|
||
| `s_module` 中 `b_module_type = 'page'` 的节点 | `page.<id>.access`(仅"需要独立入口控制"的页面) |
|
||
| 报表定义 | `report.<id>.access` |
|
||
| 开发注册的业务动作 | `module.<id>.<action_id>` |
|
||
|
||
### 3.2 增量同步三原则
|
||
|
||
产品升级或客户新增模块后需要补权限点,同步逻辑必须幂等:
|
||
|
||
1. **只增不删** —— 不删除已有权限点(删了会丢客户配置)
|
||
2. **不覆盖已有配置** —— 客户主动停用的 `b_canuse = 0` 不被重置
|
||
3. **废弃置 `b_canuse = 0`** —— 不物理删除,保留历史可追溯
|
||
|
||
### 3.3 客户自定义字段的权限
|
||
|
||
客户新增字段(如"拖车费")默认跟随模块默认配置,即**全员可见可编辑**。
|
||
|
||
防泄漏建议(低优先级,可后置):
|
||
|
||
- 新增字段时,字段名命中 `cost / price / amount / profit / 成本 / 价` 等关键词 → 提示是否标记为敏感字段
|
||
- 提供「敏感字段体检」:列出金额类型但未标记敏感的字段供确认
|
||
|
||
---
|
||
|
||
## 4. 权限计算顺序
|
||
|
||
一次页面访问或接口调用按顺序计算:
|
||
|
||
1. 用户账号与对应资源是否启用
|
||
2. **入口**:菜单 / 页面 / 报表是否有有效授权(默认拒绝)
|
||
3. **动作**:当前动作是否被允许
|
||
- 内置动作:无记录即放行;`b_canuse = 0` 明确禁止
|
||
- 业务动作:必须有有效授权记录
|
||
4. **字段**:按模块字段元数据 + 用户字段策略裁剪(只收紧)
|
||
5. **数据范围**:`override` → `default` → `all`,同层多条 OR 合并,生成 SQL 行条件
|
||
6. 角色层:用户例外覆盖角色配置
|
||
|
||
> 页面显示与接口执行必须用同一套规则。前端可提前隐藏,但**最终以后端 SQL 为准**(《开发规范》第 10、23 条)。
|
||
|
||
---
|
||
|
||
## 5. 后端落实
|
||
|
||
### 5.1 统一拦截点
|
||
|
||
通用数据接口是唯一的 SQL 出入口,权限在此单点落实:
|
||
|
||
| 接口 | 对应动作 | 需落实 |
|
||
| --- | --- | --- |
|
||
| `/data/loaddata` | read | 动作校验 + 数据范围 WHERE + 字段裁剪 |
|
||
| `/data/page` | read | 同上 |
|
||
| `/data/saveobjt` | create / update / delete | 动作校验 + 字段 edit 校验 + **目标行必须落在数据范围内** |
|
||
| `/data/loaddatabysql` | — | ⚠️ 见 5.2 |
|
||
| `/data/nextid` `/nextcode` `/describe` | — | 无需 |
|
||
|
||
流程:
|
||
|
||
```
|
||
解析 table → 用 s_module.b_viewtable / b_savetable 反查 module_id
|
||
→ 确定 action(loaddata/page → read;saveobjt 按 inserts/updates/deletes 分别定)
|
||
→ ① 动作校验
|
||
→ ② 数据范围拼入 WHERE
|
||
→ ③ SELECT 列剔除 hidden 字段
|
||
→ 执行
|
||
```
|
||
|
||
权限结果按 `(user_id, module_id, action)` 缓存,避免每次查询都查权限表。
|
||
|
||
### 5.2 ⚠️ `/data/loaddatabysql` 必须收紧
|
||
|
||
该接口接受任意 SQL,**会完全绕过字段裁剪和数据范围**。不处理则第 4 节所有规则形同虚设。
|
||
|
||
处理方案(择一,建议第一种):
|
||
|
||
1. **仅限管理员 / 内部账号可用**(推荐)
|
||
2. 改为只接受白名单视图名,不接受 SQL 片段
|
||
3. 解析 SQL 后注入条件(复杂且易出漏洞,不推荐)
|
||
|
||
> 这与《开发规范》第 8 条「动态配置不等于任意 SQL」一致。
|
||
|
||
### 5.3 表名 → 模块 反查
|
||
|
||
`loaddata` / `page` 的参数只带表名,需经 `s_module.b_viewtable` / `b_savetable` 反查 `module_id`。
|
||
|
||
**待确认**:是否存在多个模块共用同一视图?若存在,表名 → 模块的映射不唯一,需要前端额外传 `moduleId` 参数。
|
||
|
||
---
|
||
|
||
## 6. 前端落实
|
||
|
||
### 6.1 `stores/permissions.js` 改造
|
||
|
||
现有骨架(并发加载 + 主体去重 + 管理员提权)保留,替换数据源与判断逻辑:
|
||
|
||
| 现状 | 改造后 |
|
||
| --- | --- |
|
||
| 拉 `s_module` / `b_user_module` / `b_user_power` / `s_module_power` | 拉 `s_module` / `s_power` / `s_user_power` / `s_user_field_permission` / `s_user_data_scope` |
|
||
| `powerSet` 存 `${module_id}.${雪花ID}` | 存语义化 `b_code` |
|
||
| `canPower(scopeCode, powerCode)`(零调用) | `can(code)` 直接传 `b_code` |
|
||
|
||
对外暴露:
|
||
|
||
```js
|
||
can('module.cw_fee.delete') // 动作判断
|
||
fieldPerm(moduleId, field) // → { mode, query, export }
|
||
scope(moduleId, action) // 数据范围
|
||
```
|
||
|
||
### 6.2 三个接线点
|
||
|
||
1. **按钮级**:提供 `v-permission` 指令 + `usePermission()`,替换掉零调用的 `canPower`
|
||
2. **字段裁剪**:`FmsModuleTable.buildColumns()` 之后过滤 —— `hidden` 移除列、`view` 置只读、`export = 0` 剔除导出列
|
||
3. **菜单过滤**:`stores/menus.js` 目前只按 `g3soft` 管理员裁剪,**未使用授权数据**,需改为按入口权限过滤
|
||
|
||
> 前端裁剪只是展示层,**不可信**。真正的过滤在后端 SQL。
|
||
|
||
---
|
||
|
||
## 7. 配置界面设计
|
||
|
||
### 7.1 核心原则
|
||
|
||
- **IT 定义"能分配什么",客户决定"分给谁"**。IT 不碰人,客户不碰元数据。
|
||
- **一个页面配完**,不分区切换、不重复选模块。
|
||
- 默认展示**已配置项**,而非全量清单(规则是"没配 = 放行",全量铺开只会增加噪音)。
|
||
|
||
### 7.2 树的主体是模块树,不是菜单树
|
||
|
||
`s_module` 本身是树(`b_parent_id` + `b_module_type`),`page` 类型节点天然承载"页面包含哪些模块":
|
||
|
||
```
|
||
海运详细页(page 节点,只配入口)
|
||
├── 海运主表(module)
|
||
├── 装箱主表(module)
|
||
└── 费用管理(module)
|
||
```
|
||
|
||
菜单 `s_menu` 只是指向模块的指针(`s_menu.b_module_id`)。**不用菜单树做权限骨架**:
|
||
|
||
- 菜单不拥有字段 / 动作 / 数据范围,挂上去语义错误
|
||
- 一个模块可能被多个菜单引用 → 同一模块重复出现、配置冲突
|
||
- 与《核心表结构》"权限以业务模块为核心"矛盾
|
||
|
||
菜单入口权限 = 模块树上的「入口」列。只有"一个模块被多个菜单引用且需分别控制"时,才启用 `page.<id>.access` 独立入口。
|
||
|
||
### 7.3 主界面形态(一屏配完一个人)
|
||
|
||
```
|
||
模块树 入口 │ 动作(默认拒绝,勾选即授权) │ 数据范围
|
||
──────────────────────────────────────────────────────────────────────────────
|
||
▾ 海运详细页[page] ☑ │ - - - - - │ - │ -
|
||
▾ 海运主表 ☑ │ ☑ ☑ ☐ ☐ ☐ │ ☑审核 ☐关闭 │ 仅本人▾
|
||
└ 字段(敏感) 金额=只读 · 内部成本=隐藏
|
||
▾ 装箱主表 ☑ │ ☑ ☐ ☐ ☐ ☐ │ - │ 本部门▾
|
||
▾ 费用管理 ☑ │ ☑ ☑ ☑ ☐ ☑ │ ☑审核 ☐反审核 │ 本部门▾
|
||
└ 字段(敏感) 金额=只读 · 内部成本=隐藏 · 毛利=隐藏
|
||
▾ 发票管理 ☑ │ ☑ ☑ ☑ ☐ ☑ │ ☑作废 │ 本部门▾
|
||
客户档案 ☐ │ ☑ ☐ ☐ ☐ ☐ │ - │ 本部门▾
|
||
```
|
||
|
||
要点:
|
||
|
||
- 第一列是**模块树**,缩进 + 展开 + 父子半选
|
||
- `page` 节点只配入口,权限列置灰(不直接对应数据表)
|
||
- 动作全在一列,全部默认拒绝,勾选即授权;业务动作与基础操作 chip 同样式
|
||
- **字段是模块行的展开子行**,且只显示**敏感字段 + 字段组**,默认折叠
|
||
- 数据范围是一列,跟着模块走("仅本人"与"禁止删除"是同频操作,不该分开放)
|
||
|
||
### 7.4 批量
|
||
|
||
- 左侧用户列表支持多选 → 底部出现「应用角色模板」「批量设置数据范围」「清空」
|
||
- 表格支持整列操作(如"所有模块禁止删除")
|
||
|
||
### 7.5 IT 专属入口(客户不可见)
|
||
|
||
| 内容 | 频率 |
|
||
| --- | --- |
|
||
| 模块 / 字段元数据 + 敏感标记 | 低 |
|
||
| 字段分组(`s_field_group`) | 低 |
|
||
| 业务动作注册(需写代码) | 低 |
|
||
| 权限点清单 + 重新生成 | 低 |
|
||
| 标准权限包维护 | 低 |
|
||
|
||
### 7.6 权限诊断(必做)
|
||
|
||
- **以某用户身份预览**
|
||
- **反查**:选中一个权限点 / 字段 → 列出谁有 → 批量调整
|
||
- **查看生效 SQL**:排查"为什么这个人看不到这条数据"
|
||
|
||
---
|
||
|
||
## 8. 与既有文档的差异(决策变更)
|
||
|
||
| # | 原表述 | 本文 | 原因 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 动作编码 `action.module.<module>.<action>.execute` | `module.<module>.<action>` | CRUD 与业务动作本质是同一概念,统一编码 |
|
||
| 2 | 权限直接授予用户,**不引入角色继承** | 可选「主角色 + 个人例外」两层 | 无角色时配置量是 `人 × 模块 × 字段` 的笛卡尔积 |
|
||
| 3 | 权限点类型含 `page` / `report`,与 `module` 并列 | 入口与动作分离;动作统一挂模块 | 入口是"能不能进",动作是"能不能做" |
|
||
| 4 | 数据范围 `custom` 填单个值 | 支持选择多个组织节点 | "只看上海 + 深圳分公司" |
|
||
| 5 | 未说明树的主语 | **模块树**是主体,菜单只是指针 | 权限以模块为核心 |
|
||
| 6 | 未涉及 | 权限点自动生成 + 增量同步三原则 | 一客户一库 + 客户可定制 |
|
||
| 7 | 动作分内置 / 业务两种,默认策略不同 | 不区分,统一为「动作」,**全部默认拒绝** | 简化模型:只存授权记录,没有禁止记录 |
|
||
|
||
> 建议:将第 1、2 条同步修订到《FMS新系统核心表结构设计.md》第 276 行及权限章节,避免两份文档冲突。
|
||
|
||
---
|
||
|
||
## 9. 实施顺序
|
||
|
||
| 阶段 | 内容 | 产出 |
|
||
| --- | --- | --- |
|
||
| P1 | 建 `s_power` / `s_user_power`(统一编码)+ 权限点自动生成 + `/data/*` 拦截只做动作校验 | 能控制"谁能进哪个模块、能做什么" |
|
||
| P2 | 字段裁剪 + 数据范围 WHERE;**收紧 `/data/loaddatabysql`** | 后端强制完整 |
|
||
| P3 | 前端 `permissions.js` 改造 + `v-permission` + 菜单过滤 + 表格字段裁剪 | 界面生效 |
|
||
| P4 | 配置界面(模块树 + 一屏配置)+ 标准权限包 + 权限诊断 | 好用 |
|
||
|
||
P1 与 P2 是同一个拦截层的两批规则,不会返工。**不建议先做 P4**——模型未定,界面做了会返工。
|