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

9.7 KiB
Raw Blame History

入口模块租户配置表迁移方案

适用范围:首页入口模块、入口模块管理页、首页可见性判定、交叉评查入口等依赖 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 现有表结构问题

当前表:

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 新增关系表

建议新增:

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 管理端创建/更新接口

当前请求体:

{
  "name": "交叉评查",
  "route_path": "/cross-checking",
  "areas": [
    {"area": "梅州", "enabled": true, "sort_order": 1}
  ]
}

建议升级为:

{
  "name": "交叉评查",
  "route_path": "/cross-checking",
  "tenants": [
    {"tenant_code": "MZ", "enabled": true, "sort_order": 1}
  ]
}

兼容策略:

  1. 后端 DTO 同时接收 areastenants
  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
  2. 租户主数据模型设计.md
  3. 自定义租户功能连带影响深度补充.md