Files
Neo-ZQYY/docs/prd/Neo_Specs/NS4.1-tenant-admin-redesign.md
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

20 KiB
Raw Blame History

NS4.1:租户管理员页面重构 — 项目级注册体系 + 简写ID管理

优先级NS4 后续迭代,依赖 NS4 基础设施已就绪) 预估工作量:中 前置条件NS4租户管理后台基础设施、P3用户认证体系 关联页面:http://localhost:5173/tenant-adminsadmin-web 系统管理后台)


一、背景与目标

1.1 现状问题

当前 admin-web 的租户管理员页面NS4 需求 14仅支持基础 CRUD

  • 创建时手动输入 tenant_idmanaged_site_ids(无下拉选项,无名称参考)
  • 无法删除管理员记录
  • 无法管理简写IDsite_code简写ID 的创建和修改散落在数据库手动操作中
  • 缺少项目级的「连接器 → 租户 → 店铺」注册体系,租户名称无处存储

1.2 目标

  1. 建立项目级注册体系:biz.connectorsbiz.tenantsbiz.sites,统一管理连接器、租户、店铺三级关系
  2. auth.site_code_mapping 合并迁移至 biz.sites简写ID 成为店铺属性
  3. 简写ID 变更增量记录(biz.site_code_history),保护已提交但未审核的用户申请
  4. 重构租户管理员页面支持删除软删除、2 步创建流程、简写ID 管理
  5. 新增 ETL 增量同步任务:从 dwd.dim_site 同步店铺信息到业务库

二、数据模型设计

2.1 新建表

表 1biz.connectors — 连接器注册表

记录本项目接入的上游 SaaS 系统。当前仅「飞球」一个连接器,预留多连接器扩展。

CREATE TABLE biz.connectors (
    id            SERIAL        PRIMARY KEY,
    connector_key VARCHAR(50)   NOT NULL UNIQUE,  -- 连接器标识(如 'feiqiu'
    display_name  VARCHAR(100)  NOT NULL,          -- 显示名称(如 '飞球'
    is_active     BOOLEAN       NOT NULL DEFAULT true,
    created_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE biz.connectors IS '连接器注册表:记录本项目接入的上游 SaaS 系统';

初始数据:

INSERT INTO biz.connectors (connector_key, display_name)
VALUES ('feiqiu', '飞球');

表 2biz.tenants — 租户注册表

记录每个连接器下的租户信息。tenant_id 来自上游系统(飞球的 tenant_id)。

CREATE TABLE biz.tenants (
    id            SERIAL        PRIMARY KEY,
    connector_id  INTEGER       NOT NULL REFERENCES biz.connectors(id),
    tenant_id     BIGINT        NOT NULL,          -- 上游系统的租户 ID
    tenant_name   VARCHAR(200),                    -- 租户名称(可从上游同步或手动填写)
    is_active     BOOLEAN       NOT NULL DEFAULT true,
    created_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    UNIQUE (connector_id, tenant_id)               -- 同一连接器下 tenant_id 唯一
);

COMMENT ON TABLE biz.tenants IS '租户注册表连接器下的租户tenant_id 来自上游系统';

初始数据(从 ETL 库 dwd.dim_site 提取当前唯一租户):

INSERT INTO biz.tenants (connector_id, tenant_id, tenant_name)
VALUES (1, 2790683160709957, '朗朗桌球');
-- tenant_name 暂用店铺名,后续可由管理员修改或从上游同步

表 3biz.sites — 店铺注册表(合并 auth.site_code_mapping

auth.site_code_mapping 的功能合并到此表,增加 tenant_id 外键关联。 site_code 为当前生效的简写ID6 位3+3 格式)。

CREATE TABLE biz.sites (
    id            SERIAL        PRIMARY KEY,
    tenant_id     INTEGER       NOT NULL REFERENCES biz.tenants(id),
    site_id       BIGINT        NOT NULL UNIQUE,   -- 上游系统的店铺 ID
    site_name     VARCHAR(200),                    -- 店铺名称(从 dwd.dim_site 同步)
    site_code     VARCHAR(6)    UNIQUE,            -- 当前生效的简写ID如 'LLQ001'
    site_label    VARCHAR(50),                     -- 店铺标签(从 dwd.dim_site 同步)
    is_active     BOOLEAN       NOT NULL DEFAULT true,
    created_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE biz.sites IS '店铺注册表:合并原 auth.site_code_mapping增加租户关联和简写ID管理';
COMMENT ON COLUMN biz.sites.site_code IS '当前生效的简写ID6位字符3+3格式全局唯一';

初始数据(从 auth.site_code_mapping 迁移):

-- 仅迁移真实数据(排除测试数据 tenant_id IS NULL
INSERT INTO biz.sites (tenant_id, site_id, site_name, site_code)
SELECT t.id, scm.site_id, scm.site_name, scm.site_code
FROM auth.site_code_mapping scm
JOIN biz.tenants t ON t.tenant_id = scm.tenant_id
WHERE scm.tenant_id IS NOT NULL;

表 4biz.site_code_history — 简写ID 变更历史表

增量记录所有使用过的简写ID用于保护已提交但未审核的用户申请。

CREATE TABLE biz.site_code_history (
    id            SERIAL        PRIMARY KEY,
    site_id       BIGINT        NOT NULL,          -- 关联 biz.sites.site_id
    site_code     VARCHAR(6)    NOT NULL,           -- 历史简写ID
    is_current    BOOLEAN       NOT NULL DEFAULT false, -- 是否为当前生效
    created_at    TIMESTAMPTZ   NOT NULL DEFAULT NOW(), -- 该 code 生效时间
    retired_at    TIMESTAMPTZ,                      -- 该 code 失效时间NULL=当前生效)
    UNIQUE (site_code)                              -- 简写ID 全局唯一(含历史)
);

COMMENT ON TABLE biz.site_code_history IS '简写ID变更历史增量记录所有使用过的简写ID';
COMMENT ON COLUMN biz.site_code_history.is_current IS 'true=当前生效的简写ID每个 site_id 最多一条 is_current=true';

2.2 表关系

biz.connectors (1)
  └── biz.tenants (N)        -- 一个连接器下多个租户
        └── biz.sites (N)    -- 一个租户下多个店铺
              └── biz.site_code_history (N)  -- 一个店铺的简写ID变更历史

auth.tenant_admins.tenant_id → biz.tenants.tenant_id逻辑关联不加 FK
auth.user_applications.site_code → biz.site_code_history.site_code逻辑关联

2.3 废弃表处理

auth.site_code_mapping 在数据迁移完成并验证后标记为废弃:

  1. 迁移期间保留原表,新旧并行
  2. 后端代码切换到 biz.sites 读取
  3. 验证无误后,原表重命名为 auth._archived_site_code_mapping

2.4 dwd.dim_site 提升为项目级

当前 dwd.dim_site 位于 ETL 连接器级别(apps/etl/connectors/feiqiu/)。 本次不做物理迁移(表仍在 dwd schema但在语义上将其视为项目级维度表。 后续如有多连接器场景,再考虑拆分为项目级 dim schema。


三、功能详细设计

3.1 租户管理员列表页

现有功能保留

  • 分页 + 关键词搜索
  • 编辑(显示名称、管辖门店、账号状态)
  • 重置密码

新增功能

3.1.1 删除管理员(软删除)
  • 操作:点击「删除」按钮 → 二次确认弹窗 → 确认后设置 is_active = false
  • 列表默认只显示 is_active = true 的记录
  • 可选:增加「显示已禁用」开关,查看所有记录
  • 已禁用的记录不可再次删除,但可以重新启用
3.1.2 创建管理员2 步流程)

第 1 步:创建账号

  • 选择租户:下拉选择 biz.tenants 中的租户(显示 tenant_name,值为 tenant_id
  • 输入用户名
  • 输入初始密码
  • 输入显示名称
  • 选择管辖门店:根据所选租户,加载该租户下所有店铺(biz.sites),多选

第 2 步设置简写ID

  • 展示所选租户下所有店铺列表
  • 每个店铺显示店铺名称、当前简写ID如有
  • 可为每个店铺设置/修改简写ID
  • 简写ID 格式6 位字符数字3+3 模式,如 LLQ001
  • 校验:全局唯一(含历史记录中的 code

第 2 步可跳过简写ID 后续可在编辑时设置)

3.1.3 编辑管理员
  • 不可修改所属租户(tenant_id 只读)
  • 可修改用户名、密码重置、显示名称、管辖门店、简写ID
  • 简写ID 编辑入口在编辑弹窗中增加「管理简写ID」区域展示该租户下所有店铺及其当前 code

3.2 简写ID 管理逻辑

3.2.1 设置/修改简写ID

用户在管理后台修改某店铺的简写IDold_code → new_code
  → 校验 new_code 格式6位3+3
  → 校验 new_code 全局唯一biz.sites.site_code + biz.site_code_history.site_code
  → 事务内执行:
     1. 将 old_code 在 site_code_history 中标记 is_current=false, retired_at=NOW()
     2. 插入 new_code 到 site_code_historyis_current=true
     3. 更新 biz.sites.site_code = new_code
     4. 清理无引用的历史记录(见 3.2.2

3.2.2 历史记录清理

每次修改简写ID 时,检查被替换的旧 code 是否有关联的用户申请:

-- 检查旧 code 是否有未审核的申请引用
SELECT COUNT(*) FROM auth.user_applications
WHERE site_code = :old_code AND status = 'pending';
  • 如果有未审核申请引用 → 保留历史记录(is_current=false,但不删除)
  • 如果无任何申请引用 → 从 biz.site_code_history 中删除该条记录

目的:防止用户已用旧 code 提交申请但尚未审核时,映射关系丢失

3.2.3 简写ID 格式规范

  • 总长度6 位
  • 格式3 位字母/数字 + 3 位数字(如 LLQ001ABC123
  • 大小写:统一存储为大写
  • 全局唯一:同一时刻不允许两个店铺使用相同 code含历史未清理的 code

3.3 租户/店铺信息展示

3.3.1 租户下拉选项

创建管理员时,租户下拉数据来源:

GET /api/admin/tenants → biz.tenants (is_active=true)
返回:[{ id, tenantId, tenantName, connectorName }]

3.3.2 店铺列表

选择租户后,加载该租户下所有店铺:

GET /api/admin/tenants/{tenant_id}/sites → biz.sites (tenant_id=?, is_active=true)
返回:[{ id, siteId, siteName, siteCode, siteLabel }]

四、接口设计

4.1 新增接口

接口 方法 路径 说明
租户列表 GET /api/admin/tenants 所有活跃租户(含连接器名称)
租户下店铺列表 GET /api/admin/tenants/{tenant_id}/sites 指定租户下所有店铺(含当前 site_code
删除管理员 DELETE /api/admin/tenant-admins/{id} 软删除is_active=false
设置简写ID PUT /api/admin/sites/{site_id}/site-code 设置/修改店铺简写ID
简写ID历史 GET /api/admin/sites/{site_id}/site-code-history 查看某店铺的简写ID变更历史

4.2 修改接口

接口 变更内容
POST /api/admin/tenant-admins 创建时 tenant_id 改为从 biz.tenants 选择;managed_site_idsbiz.sites 选择
PATCH /api/admin/tenant-admins/{id} 增加 username 可修改(需校验唯一性)
GET /api/admin/tenant-admins 默认只返回 is_active=true;增加 include_inactive 参数

五、ETL 同步任务

5.1 店铺信息增量同步

新增 ETL 任务:从 dwd.dim_siteETL 库)增量同步到 biz.sites(业务库)。

同步逻辑

1. 读取 dwd.dim_site WHERE scd2_is_current = 1
2. 对比 biz.sites 中已有记录:
   - 新增店铺site_id 不存在)→ INSERTsite_code 留空,待管理员设置)
   - 店铺名称变更 → UPDATE site_name
   - 店铺标签变更 → UPDATE site_label
3. 不删除已有记录(即使上游标记为关闭)

触发方式

  • 手动触发:管理后台按钮或 CLI 命令
  • 定时触发:随 ETL 日常调度DWD 层完成后)

租户信息

  • biz.tenants 中的 tenant_name 暂不自动同步(上游无 tenant_name 字段)
  • 首次由迁移脚本写入,后续由管理员在管理后台手动修改

六、数据迁移计划

6.1 迁移步骤

1. 创建新表biz.connectors → biz.tenants → biz.sites → biz.site_code_history
2. 写入种子数据connectors飞球、tenants从 dim_site 提取)
3. 迁移 auth.site_code_mapping → biz.sites仅真实数据排除 tenant_id IS NULL 的测试数据)
4. 为已有 site_code 创建 site_code_history 记录is_current=true
5. 运行 ETL 同步任务,补充 biz.sites 中缺失的店铺dim_site 中有但 site_code_mapping 中没有的)
6. 后端代码切换:所有读取 auth.site_code_mapping 的地方改为读取 biz.sites
7. 验证:对比新旧表数据一致性
8. 废弃原表:重命名为 auth._archived_site_code_mapping

6.2 回滚策略

  • 迁移期间保留原表不动
  • 如需回滚:删除 biz.sites/tenants/connectors/site_code_history恢复后端代码指向 auth.site_code_mapping

七、前端页面设计

7.1 页面布局

┌─────────────────────────────────────────────────────┐
│  租户管理员                                          │
│  [搜索框]  [显示已禁用 ☐]  [+ 创建管理员]            │
├─────────────────────────────────────────────────────┤
│  用户名 │ 显示名称 │ 租户 │ 管辖门店 │ 状态 │ 操作   │
│  admin1 │ 张三     │ 朗朗 │ 朗朗桌球 │ 启用 │ 编辑   │
│         │          │      │          │      │ 重置   │
│         │          │      │          │      │ 简写ID │
│         │          │      │          │      │ 删除   │
└─────────────────────────────────────────────────────┘

7.2 创建弹窗2 步)

步骤 1/2创建账号
┌──────────────────────────────┐
│  租户:    [▼ 朗朗桌球     ] │
│  用户名:  [              ] │
│  初始密码:[              ] │
│  显示名称:[              ] │
│  管辖门店:[☑ 朗朗桌球    ] │
│                              │
│        [下一步]  [取消]      │
└──────────────────────────────┘

步骤 2/2设置简写ID
┌──────────────────────────────┐
│  店铺          当前简写ID     │
│  朗朗桌球      [LLQ001    ]  │
│                              │
│  格式6位3字母+3数字     │
│                              │
│   [跳过]  [完成创建]  [上一步]│
└──────────────────────────────┘

7.3 简写ID 管理弹窗

管理简写ID — 朗朗桌球(租户)
┌──────────────────────────────────────┐
│  店铺          当前ID    操作         │
│  朗朗桌球      LLQ001   [修改]       │
│                                      │
│  修改简写ID                         │
│  新ID[      ]  [保存]  [取消]      │
│                                      │
│  变更历史:                           │
│  LLQ001  当前生效  2026-03-22        │
│  LL001   已失效    2026-02-25 (保留)  │
│                                      │
│                          [关闭]      │
└──────────────────────────────────────┘

八、影响范围

8.1 后端

文件 变更类型 说明
app/routers/admin_tenant_admins.py 修改 增加 DELETE 端点、修改 CREATE/EDIT 逻辑
app/schemas/admin_tenant_admins.py 修改 新增/修改请求响应 Schema
app/routers/admin_registry.py 新建 租户/店铺/简写ID 管理接口
app/schemas/admin_registry.py 新建 注册体系 Schema

8.2 前端admin-web

文件 变更类型 说明
src/pages/TenantAdmins/index.tsx 重构 2 步创建、删除、简写ID 管理
src/api/tenantAdmins.ts 修改 新增 API 调用
src/api/registry.ts 新建 租户/店铺列表 API

8.3 数据库

操作 对象 说明
新建 biz.connectors 连接器注册表
新建 biz.tenants 租户注册表
新建 biz.sites 店铺注册表(合并 site_code_mapping
新建 biz.site_code_history 简写ID 变更历史
废弃 auth.site_code_mapping 迁移完成后废弃

8.4 ETL

变更 说明
新增同步任务 dwd.dim_sitebiz.sites 增量同步

8.5 小程序端

变更 说明
用户申请时的 site_code 查询 auth.site_code_mapping 切换到 biz.sites + biz.site_code_history

九、约束与边界条件

  1. 一个租户只有一个管理员(软限制,不加 DB 约束)
  2. 简写ID 全局唯一(含历史记录),通过 UNIQUE 约束保证
  3. 删除管理员为软删除(is_active = false),不物理删除
  4. 编辑时不可修改所属租户
  5. 简写ID 格式6 位3+3 模式,统一大写存储
  6. 历史简写ID 仅在无未审核申请引用时才可清理
  7. ETL 同步不删除已有店铺记录(即使上游关闭)
  8. biz.tenants.tenant_name 暂不自动同步,由管理员手动维护

十、不做的事情(明确排除)

  1. 不做多连接器支持的完整实现(仅预留 biz.connectors 表结构)
  2. 不做 dwd.dim_site 的物理迁移(保留在 dwd schema
  3. 不做租户管理员的自助注册功能
  4. 不做店铺管理员的管理(本 PRD 仅涉及租户管理员)
  5. 不做 auth.site_code_mapping 的立即删除(迁移后保留为 _archived
  6. 不做简写ID 的自动生成(由管理员手动设置)

十一、数据库现状参考

ETL 库 dwd.dim_site(当前数据)

site_id tenant_id shop_name site_label
2790685415443269 2790683160709957 朗朗桌球 A

业务库 auth.site_code_mapping(当前数据)

id site_code site_id site_name tenant_id
1 LL001 2790685415443269 朗朗桌球 2790683160709957
1448 PT952 857189 测试球房_PT952 NULL
1470 PT118 819193 测试球房_PT118 NULL
1471 PT607 899675 测试球房_PT607 NULL

仅 id=1 为真实数据,其余为属性测试生成的测试数据(tenant_id IS NULL

业务库 auth.tenant_admins(现有结构)

字段 类型 说明
id BIGSERIAL 主键
username VARCHAR(50) 登录用户名UNIQUE
password_hash VARCHAR(255) bcrypt 哈希
display_name VARCHAR(100) 显示名称
tenant_id BIGINT 所属租户
managed_site_ids BIGINT[] 管辖门店 ID 列表
is_active BOOLEAN 账号状态
created_by BIGINT 创建者
created_at TIMESTAMPTZ 创建时间
last_login_at TIMESTAMPTZ 最后登录时间

tenant_admins 表结构不变,仅后端逻辑调整(创建时从 biz.tenants 选择租户)


十二、参考文档

文档 路径 用途
NS4 原始 PRD docs/prd/Neo_Specs/NS4-tenant-admin-web.md 租户管理后台完整需求
P3 认证体系 docs/prd/specs/P3-miniapp-auth-system.md site_code / user_applications 设计
BD 手册-认证表 docs/database/BD_Manual_auth_tables.md auth schema 表结构
BD 手册-业务表 docs/database/BD_Manual_biz_tables.md biz schema 表结构