Files
leaudit-platform-backend/docs/权限与地区隔离/入口模块租户配置表迁移方案.md

462 lines
9.7 KiB
Markdown
Raw Permalink 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.
# 入口模块租户配置表迁移方案
> 适用范围:首页入口模块、入口模块管理页、首页可见性判定、交叉评查入口等依赖 `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)