feat: add tenant-scoped rule and permission management
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
# 入口模块租户配置表迁移方案
|
||||
|
||||
> 适用范围:首页入口模块、入口模块管理页、首页可见性判定、交叉评查入口等依赖 `leaudit_entry_modules.areas` 的场景
|
||||
> 文档定位:把当前入口模块的 `areas JSONB` 从自由字符串配置升级为“租户关系表 + 主数据校验”的落地方案。
|
||||
|
||||
---
|
||||
|
||||
## 1. 结论先行
|
||||
|
||||
当前入口模块是整个“地区租户化”里最明显、最优先、最适合先落地的切入点。
|
||||
|
||||
原因有 4 个:
|
||||
|
||||
1. 问题最直观,用户已经明确感知到“新租户没法分配入口”
|
||||
2. 当前设计最脆弱,`areas JSONB` 没有主数据约束
|
||||
3. 前后端都存在固定地区假设
|
||||
4. 首页和交叉评查都依赖它做可见性判断
|
||||
|
||||
因此建议先把入口模块从:
|
||||
|
||||
1. `leaudit_entry_modules + areas JSONB`
|
||||
|
||||
升级为:
|
||||
|
||||
1. `leaudit_entry_modules`
|
||||
2. `leaudit_entry_module_tenants`
|
||||
3. `sys_tenants`
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前问题深挖
|
||||
|
||||
## 2.1 现有表结构问题
|
||||
|
||||
当前表:
|
||||
|
||||
```sql
|
||||
leaudit_entry_modules(
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
path,
|
||||
icon_path,
|
||||
areas JSONB,
|
||||
sort_order,
|
||||
is_enabled
|
||||
)
|
||||
```
|
||||
|
||||
问题:
|
||||
|
||||
1. `areas` 是自由 JSON
|
||||
2. `area` 值没有外键约束
|
||||
3. `enabled/sort_order` 与租户主数据重复
|
||||
4. 无法防止写入不存在的租户
|
||||
5. 无法直接做关系查询和索引优化
|
||||
|
||||
## 2.2 后端当前风险
|
||||
|
||||
当前 `EntryModuleAdminServiceImpl.py`:
|
||||
|
||||
1. 创建和更新时直接把前端传入 `areas` 序列化成 JSON
|
||||
2. 没有合法租户校验
|
||||
3. 没有和用户租户主数据对齐
|
||||
|
||||
当前 `homeServiceImpl.py`:
|
||||
|
||||
1. 直接按 `user_area = area_item->>'area'`
|
||||
2. 支持 `default` 兜底
|
||||
3. `super_admin` 特判 bypass
|
||||
|
||||
这意味着:
|
||||
|
||||
1. 可见性规则绑定在脏字符串上
|
||||
2. 新租户即使进库,首页也可能因为字符串不一致看不到
|
||||
|
||||
## 2.3 前端当前风险
|
||||
|
||||
当前 `EntryModuleNewClient.tsx`:
|
||||
|
||||
1. 地区候选列表硬编码为固定 5 项
|
||||
|
||||
当前 `cross-checking-access.ts`:
|
||||
|
||||
1. 用固定地区别名数组判断入口可见性
|
||||
2. `provincial_admin` 自动补 `省局`
|
||||
|
||||
这意味着:
|
||||
|
||||
1. 后端就算支持新租户,前端也无法选择
|
||||
2. 首页入口和交叉评查入口会出现不一致
|
||||
|
||||
---
|
||||
|
||||
## 3. 目标数据模型
|
||||
|
||||
## 3.1 保留主表
|
||||
|
||||
保留:
|
||||
|
||||
1. `leaudit_entry_modules`
|
||||
|
||||
用于表达模块本身:
|
||||
|
||||
1. 名称
|
||||
2. 描述
|
||||
3. 跳转路径
|
||||
4. 图标
|
||||
5. 排序
|
||||
6. 启用状态
|
||||
|
||||
## 3.2 新增关系表
|
||||
|
||||
建议新增:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS leaudit_entry_module_tenants (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
entry_module_id BIGINT NOT NULL REFERENCES leaudit_entry_modules(id),
|
||||
tenant_code VARCHAR(64) NOT NULL,
|
||||
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
visibility_scope VARCHAR(32) NOT NULL DEFAULT 'TENANT',
|
||||
ext JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMP NULL,
|
||||
UNIQUE (entry_module_id, tenant_code)
|
||||
);
|
||||
```
|
||||
|
||||
## 3.3 为什么关系表足够
|
||||
|
||||
关系表解决了当前 JSON 方案的关键缺陷:
|
||||
|
||||
1. 可按 `tenant_code` 精确筛选
|
||||
2. 可直接做唯一约束
|
||||
3. 可扩展更多可见性字段
|
||||
4. 可做索引和分页查询
|
||||
5. 能和租户主数据自然联表
|
||||
|
||||
---
|
||||
|
||||
## 4. 字段设计建议
|
||||
|
||||
## 4.1 `tenant_code`
|
||||
|
||||
必须来自 `sys_tenants.tenant_code`,不允许任意字符串。
|
||||
|
||||
## 4.2 `is_enabled`
|
||||
|
||||
表示该入口是否对该租户生效。
|
||||
|
||||
说明:
|
||||
|
||||
1. 模块总开关在 `leaudit_entry_modules.is_enabled`
|
||||
2. 租户粒度开关在 `leaudit_entry_module_tenants.is_enabled`
|
||||
|
||||
## 4.3 `sort_order`
|
||||
|
||||
表示该模块在该租户视角下的局部排序。
|
||||
|
||||
如果当前不需要租户级排序,也建议先保留,因为后续:
|
||||
|
||||
1. 不同租户首页布局可能不同
|
||||
2. 试点租户可能需要不同优先级
|
||||
|
||||
## 4.4 `visibility_scope`
|
||||
|
||||
建议预留:
|
||||
|
||||
1. `TENANT`
|
||||
2. `PUBLIC`
|
||||
3. `HEADQUARTER_ONLY`
|
||||
|
||||
当前先以 `TENANT` 为主,但保留扩展空间,避免以后再次改表。
|
||||
|
||||
---
|
||||
|
||||
## 5. 接口改造建议
|
||||
|
||||
## 5.1 管理端列表接口
|
||||
|
||||
当前:
|
||||
|
||||
1. `GET /api/v3/entry-modules?area=...`
|
||||
|
||||
建议后续改为:
|
||||
|
||||
1. `GET /api/v3/entry-modules?tenant_code=...`
|
||||
|
||||
兼容期:
|
||||
|
||||
1. `area` 仍可保留,但后端先归一成 `tenant_code`
|
||||
|
||||
## 5.2 管理端创建/更新接口
|
||||
|
||||
当前请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "交叉评查",
|
||||
"route_path": "/cross-checking",
|
||||
"areas": [
|
||||
{"area": "梅州", "enabled": true, "sort_order": 1}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
建议升级为:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "交叉评查",
|
||||
"route_path": "/cross-checking",
|
||||
"tenants": [
|
||||
{"tenant_code": "MZ", "enabled": true, "sort_order": 1}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
兼容策略:
|
||||
|
||||
1. 后端 DTO 同时接收 `areas` 和 `tenants`
|
||||
2. 兼容期内优先使用 `tenants`
|
||||
3. `areas` 走别名归一后再写关系表
|
||||
|
||||
## 5.3 首页获取入口接口
|
||||
|
||||
当前:
|
||||
|
||||
1. `HomeServiceImpl.GetEntryModules(UserId)`
|
||||
|
||||
建议内部改造为:
|
||||
|
||||
1. 先解析用户 `tenant_code`
|
||||
2. 再根据 `leaudit_entry_module_tenants` 判断可见性
|
||||
|
||||
SQL 方向应改成:
|
||||
|
||||
1. 联表 `leaudit_entry_module_tenants`
|
||||
2. 不再扫描 `jsonb_array_elements`
|
||||
|
||||
---
|
||||
|
||||
## 6. 前端改造建议
|
||||
|
||||
## 6.1 入口模块新建页
|
||||
|
||||
当前问题:
|
||||
|
||||
1. `AREA_OPTIONS` 写死
|
||||
|
||||
建议:
|
||||
|
||||
1. 页面加载时调用租户主数据接口
|
||||
2. 用 `tenant_code` 作为 value
|
||||
3. 用 `tenant_name` 作为 label
|
||||
4. 表单字段从 `selectedAreas` 改为 `selectedTenantCodes`
|
||||
|
||||
## 6.2 入口模块列表页筛选
|
||||
|
||||
建议:
|
||||
|
||||
1. 筛选条件改为租户下拉
|
||||
2. 后端接收 `tenant_code`
|
||||
3. 列表展示使用租户名称而非裸字符串
|
||||
|
||||
## 6.3 交叉评查入口判定
|
||||
|
||||
当前 `cross-checking-access.ts` 不应再:
|
||||
|
||||
1. 猜测地区别名
|
||||
2. `includes("省")`
|
||||
3. 针对 `provincial_admin` 自动补 `省局`
|
||||
|
||||
建议:
|
||||
|
||||
1. 登录态透出 `tenant_code`
|
||||
2. 首页接口直接返回“当前用户可见模块”
|
||||
3. 前端不再做租户匹配推断
|
||||
|
||||
---
|
||||
|
||||
## 7. 迁移策略
|
||||
|
||||
## 7.1 第一步:建新表,不改老表
|
||||
|
||||
先新增:
|
||||
|
||||
1. `leaudit_entry_module_tenants`
|
||||
|
||||
此时:
|
||||
|
||||
1. 旧 `areas JSONB` 继续存在
|
||||
2. 新服务层支持双写
|
||||
|
||||
## 7.2 第二步:把历史 JSON 展开入新表
|
||||
|
||||
迁移逻辑:
|
||||
|
||||
1. 读取 `leaudit_entry_modules.areas`
|
||||
2. 对每个 `area` 执行租户归一
|
||||
3. 写入 `leaudit_entry_module_tenants`
|
||||
4. 对映射失败的值输出复核清单
|
||||
|
||||
## 7.3 第三步:服务层读新表
|
||||
|
||||
顺序建议:
|
||||
|
||||
1. 管理端详情/列表先从新表组装返回
|
||||
2. 首页可见性改为读新表
|
||||
3. 交叉评查入口依赖首页接口结果,不再本地推断
|
||||
|
||||
## 7.4 第四步:前端提交新结构
|
||||
|
||||
前端提交统一改成 `tenant_code` 后:
|
||||
|
||||
1. `areas` 字段只保留兼容
|
||||
2. 新表成为唯一写入目标
|
||||
|
||||
## 7.5 第五步:下线旧 JSON
|
||||
|
||||
待所有读写都切到新表后:
|
||||
|
||||
1. `areas` 字段可以保留为快照
|
||||
2. 或在后续版本彻底废弃
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容期设计
|
||||
|
||||
## 8.1 DTO 兼容
|
||||
|
||||
建议 DTO 增加:
|
||||
|
||||
1. `tenants: list[EntryModuleTenantDTO] | None`
|
||||
|
||||
保留:
|
||||
|
||||
1. `areas: list[EntryModuleAreaDTO] | None`
|
||||
|
||||
优先级:
|
||||
|
||||
1. `tenants` 优先
|
||||
2. `areas` 仅用于历史前端兼容
|
||||
|
||||
## 8.2 VO 兼容
|
||||
|
||||
接口返回建议同时包含:
|
||||
|
||||
1. `tenants`
|
||||
2. `areas`
|
||||
|
||||
其中:
|
||||
|
||||
1. `tenants` 是标准结构
|
||||
2. `areas` 是兼容展示结构
|
||||
|
||||
避免一次性打崩旧页面。
|
||||
|
||||
## 8.3 SQL seed 兼容
|
||||
|
||||
当前:
|
||||
|
||||
1. `seed_home_entry_modules.sql`
|
||||
2. `seed_govdoc_entry_module.sql`
|
||||
|
||||
都直接写死 `areas JSONB`。
|
||||
|
||||
建议:
|
||||
|
||||
1. 先保留旧插入
|
||||
2. 新增对应关系表 seed
|
||||
3. 后续把内置入口的租户分配从 JSON 迁到关系表
|
||||
|
||||
---
|
||||
|
||||
## 9. 需要同步改的代码点
|
||||
|
||||
后端重点:
|
||||
|
||||
1. `entryModuleDto.py`
|
||||
2. `entryModuleAdminServiceImpl.py`
|
||||
3. `homeServiceImpl.py`
|
||||
4. `entryModuleController.py`
|
||||
5. 相关 VO 定义
|
||||
|
||||
前端重点:
|
||||
|
||||
1. `EntryModuleNewClient.tsx`
|
||||
2. `entry-modules` API 封装
|
||||
3. 首页入口 hooks
|
||||
4. `cross-checking-access.ts`
|
||||
|
||||
数据重点:
|
||||
|
||||
1. `schema_v2_add_evaluation_tables.sql`
|
||||
2. `seed_home_entry_modules.sql`
|
||||
3. `seed_govdoc_entry_module.sql`
|
||||
|
||||
---
|
||||
|
||||
## 10. 风险清单
|
||||
|
||||
## 10.1 首页入口可能瞬时丢失
|
||||
|
||||
如果首页先切新表,但历史数据尚未迁入新关系表,会导致所有入口不可见。
|
||||
|
||||
所以顺序必须是:
|
||||
|
||||
1. 先迁数据
|
||||
2. 再切读逻辑
|
||||
|
||||
## 10.2 新旧字段并存期可能双写不一致
|
||||
|
||||
必须规定:
|
||||
|
||||
1. 标准源为新关系表
|
||||
2. 旧 `areas` 只做兼容输出或快照
|
||||
|
||||
## 10.3 交叉评查入口可能与首页结果不一致
|
||||
|
||||
因为当前它自己做了一套前端地区推断。
|
||||
|
||||
必须同步下线这套本地逻辑。
|
||||
|
||||
## 10.4 自定义新租户仍可能被旧 seed 覆盖
|
||||
|
||||
如果后续还执行写死地区的 seed,会把新租户体系重新拉回旧模型。
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
入口模块迁移完成后,至少应满足:
|
||||
|
||||
1. 新增租户后,无需改前端常量即可在管理页被选中
|
||||
2. 管理端不能给不存在的租户分配入口
|
||||
3. 首页入口仅按 `tenant_code` 判断,不再比对中文地区字符串
|
||||
4. 交叉评查入口不再依赖固定地区别名数组
|
||||
5. 历史入口模块在旧租户下可见性与迁移前一致
|
||||
6. 新租户创建后,可以直接分配现有入口模块
|
||||
|
||||
---
|
||||
|
||||
## 12. 本文档解决什么问题
|
||||
|
||||
本文档主要解决:
|
||||
|
||||
1. 入口模块为什么是租户化第一改造点
|
||||
2. 当前 `areas JSONB` 设计具体哪里不够
|
||||
3. 关系表应该怎么建
|
||||
4. 首页和交叉评查为什么会被连带影响
|
||||
5. 迁移顺序应该怎么安排
|
||||
|
||||
建议联动阅读:
|
||||
|
||||
1. [地区租户化与自定义租户扩展改造方案.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/地区租户化与自定义租户扩展改造方案.md)
|
||||
2. [租户主数据模型设计.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/租户主数据模型设计.md)
|
||||
3. [自定义租户功能连带影响深度补充.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/自定义租户功能连带影响深度补充.md)
|
||||
Reference in New Issue
Block a user