Files
Neo-ZQYY/docs/specs/admin-web-restructure/design.md
Neo 70324d8542 chore: 文档与 IDE 配置整理
- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro)
- CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/
- 新增 /spec-close、/pre-change 两个工作流命令
- DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表)
- BD_Manual → BD_manual 命名统一(48 个文件)
- 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数)
- 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表)
- 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档)
- docs/database/README.md 索引更新

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

21 KiB
Raw Blame History

技术设计文档admin-web 后台重构优化

概述

本次重构将 admin-web 从 18 个页面 / 11 个一级菜单重组为 7 个一级菜单模块,核心目标:

  1. 菜单精简11 → 7 个一级菜单按功能域聚合ETL、触发器、AI 等)
  2. 新增仪表盘:登录后默认展示运行状态 Dashboard聚合 OpsPanel + DB 健康 + AI 概览
  3. 触发器统一管理聚合三套触发器数据源biz.trigger_jobs / biz.ai_trigger_jobs / scheduled_tasks
  4. 新增后端 APIPATCH /api/trigger-jobs/{id}/config编辑触发器配置、GET /api/admin/db-health数据库健康、GET /api/admin/triggers/unified触发器统一列表
  5. 渐进式迁移:新页面用目标路由开发,老页面测试通过后移入 _archived/

设计决策

  • Tab 视图而非子路由ETL 任务管理和触发器管理使用 Ant Design Tabs 组件 + URL 查询参数(?tab=xxx而非嵌套路由。理由Tab 切换不触发组件卸载,状态保持更自然。
  • 复用而非重写Dashboard 通过 import 现有 OpsPanel / AIDashboard 组件的子模块实现聚合,不重写已有逻辑。
  • 后端最小改动:仅新增 3 个 API 端点,不修改现有业务逻辑。

架构

整体架构

graph TB
    subgraph "admin-web 前端"
        Router["React Router 7<br/>路由层"]
        Layout["AppLayout<br/>Sider + Content"]
        
        subgraph "7 个一级模块"
            Dashboard["运行状态<br/>/dashboard"]
            ETL["ETL 任务管理<br/>/etl-tasks"]
            MiniApp["小程序任务管理<br/>/task-engine/*"]
            Triggers["触发器管理<br/>/triggers"]
            Tenant["租户管理员<br/>/tenant-admins"]
            Settings["系统设置<br/>/settings"]
            Logs["日志调试<br/>/logs/*"]
        end
    end
    
    subgraph "后端 APIFastAPI"
        ExistingAPI["现有 API 端点"]
        NewPATCH["PATCH /api/trigger-jobs/{id}/config"]
        NewDBHealth["GET /api/admin/db-health"]
        NewUnified["GET /api/admin/triggers/unified"]
    end
    
    subgraph "数据库"
        TriggerJobs["biz.trigger_jobs"]
        AITriggerJobs["biz.ai_trigger_jobs"]
        ScheduledTasks["scheduled_tasks"]
        FourDBs["4 个数据库健康检测"]
    end
    
    Router --> Layout
    Layout --> Dashboard & ETL & MiniApp & Triggers & Tenant & Settings & Logs
    Dashboard --> ExistingAPI & NewDBHealth
    Triggers --> ExistingAPI & NewPATCH & NewUnified
    NewUnified --> TriggerJobs & AITriggerJobs & ScheduledTasks
    NewDBHealth --> FourDBs

路由结构

/login                          → Login不变
/dashboard                      → Dashboard默认首页
/                               → Redirect → /dashboard
/etl-tasks?tab=config|manager|status  → ETLTasks合并 3 页面)
/task-engine/trigger-jobs       → TriggerJobs移动路由
/task-engine/transfer-log       → TransferLog不变
/task-engine/pending-review     → PendingReview不变
/task-engine/config             → TaskEngineConfig不变
/triggers?tab=all|biz|ai|etl    → TriggerManager
/tenant-admins                  → TenantAdmins不变
/settings/env-config            → EnvConfig移动路由
/logs/dev-trace                 → DevTrace移动路由
/logs/ai-run-logs               → AIRunLogs移动路由
/logs/db-viewer                 → DBViewer移动路由
/log-viewer                     → Redirect → /etl-tasks?tab=manager

菜单结构映射

序号 一级菜单 子项/Tab 路由
1 运行状态 /dashboard
2 ETL 任务管理 任务配置 / 任务管理 / ETL 状态Tab /etl-tasks?tab=config|manager|status
3 小程序任务管理 定时任务 / 转移日志 / 待审核任务 / 参数管理 /task-engine/*
4 触发器管理 全部 / 业务 / AI / ETLTab /triggers?tab=all|biz|ai|etl
5 租户管理员 /tenant-admins
6 系统设置 环境配置 / 触发器配置(跳转) /settings/*
7 日志调试 DevTrace / AI 调用明细 / 数据库查看器 /logs/*

组件与接口

新增前端页面组件

1. Dashboard运行状态仪表盘

src/pages/Dashboard.tsx

聚合展示系统运行状况,由 4 个区块组成:

  • OpsPanel 区块:复用现有 OpsPanel 组件中的服务状态、Git 状态、系统资源子模块(需将 OpsPanel 拆分为可独立使用的子组件)
  • DB 健康监控区块:新组件 DbHealthCard,调用 GET /api/admin/db-health,展示 4 个数据库的连接池 / 大小 / 慢查询
  • AI 运行总览区块:复用现有 AIDashboard 组件的统计卡片、趋势图、预算进度
  • AI 调度摘要区块:展示今日触发数、成功率、最近错误,提供"查看详情"跳转到 /triggers?tab=ai

跳转链接:

  • "ETL 状态详情" → /etl-tasks?tab=status
  • "触发器详情" → /triggers?tab=all
  • "AI 调度详情" → /triggers?tab=ai

2. ETLTasksETL 任务管理)

src/pages/ETLTasks.tsx

使用 Ant Design Tabs 组件合并 3 个现有页面:

  • Tab "config":渲染 <TaskConfig />(现有组件)
  • Tab "manager":渲染 <TaskManager />(现有组件)
  • Tab "status":渲染 <ETLStatus />(现有组件)

Tab 切换通过 URL 查询参数 ?tab=config|manager|status 同步,支持浏览器前进/后退。使用 useSearchParams 读写当前 Tab。各 Tab 内容使用 destroyInactiveTabPane={false} 保持状态不丢失。

3. TriggerManager触发器统一管理

src/pages/TriggerManager.tsx

4 个 Tab

  • 全部 Tab(只读):调用 GET /api/admin/triggers/unified,展示统一字段表格(名称、类型标签、触发条件、状态、上次/下次执行、最近错误)
  • 业务 Tab:复用现有 TriggerJobs 组件 + 新增编辑功能(调用 PATCH /api/trigger-jobs/{id}/config
  • AI Tab:复用现有 AIOperations 组件的手动操作功能 + AITriggerJobs 的调度状态
  • ETL Tab:展示 scheduled_tasks 数据(调用现有调度 API

4. DbHealthCard数据库健康卡片

src/components/DbHealthCard.tsx

独立组件,接收 GET /api/admin/db-health 返回数据,为每个数据库渲染一张卡片:

  • 连接池状态(活跃/空闲连接数,进度条)
  • 数据库大小MB
  • 慢查询数量(最近 1 小时)
  • 连接失败时显示"未连接"状态标签

新增后端 API

1. PATCH /api/trigger-jobs/{id}/config

# apps/backend/app/routers/trigger_jobs.py 新增端点

@router.patch("/{job_id}/config", response_model=TriggerJobItem)
async def update_trigger_config(
    job_id: int,
    body: UpdateTriggerConfigRequest,
    user: CurrentUser = Depends(get_current_user),
) -> TriggerJobItem:
    """编辑触发器的 cron_expression 或 interval_seconds。"""

请求体 Schema

class UpdateTriggerConfigRequest(BaseModel):
    cron_expression: str | None = None
    interval_seconds: int | None = None

逻辑:

  1. 查询 trigger_job 是否存在
  2. 校验 cron_expression 格式5 字段 cron 语法)
  3. 校验 interval_seconds >= 1
  4. 更新 trigger_config JSON 中对应字段
  5. 调用 _calculate_next_run() 重新计算 next_run_at
  6. 返回更新后的完整 trigger_job

2. GET /api/admin/db-health

# apps/backend/app/routers/admin_db_health.py 新增路由模块

@router.get("", response_model=list[DbHealthItem])
async def get_db_health(
    user: CurrentUser = Depends(get_current_user),
) -> list[DbHealthItem]:
    """返回 4 个数据库的健康状态。"""

响应 Schema

class DbHealthItem(BaseModel):
    db_name: str                          # etl_feiqiu / test_etl_feiqiu / zqyy_app / test_zqyy_app
    status: str                           # connected / disconnected
    active_connections: int | None = None
    idle_connections: int | None = None
    db_size_mb: float | None = None
    slow_query_count: int | None = None   # 最近 1 小时内 > 1s 的查询数

逻辑:

  1. 遍历 4 个数据库 DSN 配置
  2. 对每个库尝试连接并执行诊断 SQL
    • pg_stat_activity 查连接池状态
    • pg_database_size() 查数据库大小
    • pg_stat_statementspg_stat_activity 查慢查询
  3. 连接失败时返回 status: "disconnected",其余字段为 null

3. GET /api/admin/triggers/unified

# apps/backend/app/routers/admin_triggers.py 新增路由模块

@router.get("", response_model=list[UnifiedTriggerItem])
async def get_unified_triggers(
    user: CurrentUser = Depends(get_current_user),
) -> list[UnifiedTriggerItem]:
    """聚合三张表的触发器数据。"""

响应 Schema

class UnifiedTriggerItem(BaseModel):
    id: int
    name: str
    source: str                           # biz / ai / etl
    trigger_condition: str                # cron / interval / event
    status: str                           # running / idle / error / disabled
    last_run_at: str | None = None
    next_run_at: str | None = None
    last_error: str | None = None

逻辑:

  1. 查询 biz.trigger_jobs,映射 source="biz"
  2. 查询 biz.ai_trigger_jobs(最近 100 条),映射 source="ai"
  3. 查询 scheduled_tasks,映射 source="etl"
  4. 统一字段格式后合并返回

新增前端 API 模块

// src/api/dbHealth.ts
export interface DbHealthItem {
  db_name: string;
  status: 'connected' | 'disconnected';
  active_connections: number | null;
  idle_connections: number | null;
  db_size_mb: number | null;
  slow_query_count: number | null;
}

export async function fetchDbHealth(): Promise<DbHealthItem[]> {
  const { data } = await apiClient.get<DbHealthItem[]>('/admin/db-health');
  return data;
}

// src/api/triggers.ts
export interface UnifiedTriggerItem {
  id: number;
  name: string;
  source: 'biz' | 'ai' | 'etl';
  trigger_condition: string;
  status: string;
  last_run_at: string | null;
  next_run_at: string | null;
  last_error: string | null;
}

export async function fetchUnifiedTriggers(): Promise<UnifiedTriggerItem[]> {
  const { data } = await apiClient.get<UnifiedTriggerItem[]>('/admin/triggers/unified');
  return data;
}

// src/api/triggerJobs.ts 新增
export interface UpdateTriggerConfigReq {
  cron_expression?: string;
  interval_seconds?: number;
}

export async function updateTriggerConfig(
  jobId: number, body: UpdateTriggerConfigReq
): Promise<TriggerJob> {
  const { data } = await apiClient.patch<TriggerJob>(`/trigger-jobs/${jobId}/config`, body);
  return data;
}

修改的现有组件

App.tsx 路由重构

  • NAV_ITEMS 从 11 个一级项重组为 7 个
  • Routes 更新为新路由结构
  • 添加 //dashboard 重定向
  • 添加 /log-viewer/etl-tasks?tab=manager 重定向
  • 登录成功后导航到 /dashboard

OpsPanel 组件拆分

将 OpsPanel.tsx 拆分为可独立使用的子组件:

  • ServiceStatusSection:服务状态卡片
  • GitStatusSectionGit 状态卡片
  • SystemResourceSection:系统资源卡片

Dashboard 页面 import 这些子组件进行聚合展示。

数据模型

新增后端 Pydantic 模型

UpdateTriggerConfigRequest

class UpdateTriggerConfigRequest(BaseModel):
    """触发器配置编辑请求(部分更新)"""
    cron_expression: str | None = None      # 5 字段 cron 表达式
    interval_seconds: int | None = None     # 间隔秒数,>= 1

    @model_validator(mode='after')
    def at_least_one_field(self) -> Self:
        if self.cron_expression is None and self.interval_seconds is None:
            raise ValueError('至少提供 cron_expression 或 interval_seconds 之一')
        return self

DbHealthItem

class DbHealthItem(BaseModel):
    """单个数据库健康状态"""
    db_name: str
    status: Literal['connected', 'disconnected']
    active_connections: int | None = None
    idle_connections: int | None = None
    db_size_mb: float | None = None
    slow_query_count: int | None = None

UnifiedTriggerItem

class UnifiedTriggerItem(BaseModel):
    """统一触发器视图项"""
    id: int
    name: str
    source: Literal['biz', 'ai', 'etl']
    trigger_condition: str
    status: str
    last_run_at: str | None = None
    next_run_at: str | None = None
    last_error: str | None = None

新增前端 TypeScript 类型

// 数据库健康
interface DbHealthItem {
  db_name: string;
  status: 'connected' | 'disconnected';
  active_connections: number | null;
  idle_connections: number | null;
  db_size_mb: number | null;
  slow_query_count: number | null;
}

// 统一触发器
interface UnifiedTriggerItem {
  id: number;
  name: string;
  source: 'biz' | 'ai' | 'etl';
  trigger_condition: string;
  status: string;
  last_run_at: string | null;
  next_run_at: string | null;
  last_error: string | null;
}

// 触发器配置编辑
interface UpdateTriggerConfigReq {
  cron_expression?: string;
  interval_seconds?: number;
}

数据库变更

本次重构不新增数据库表。所有数据来自现有表:

用途
biz.trigger_jobs zqyy_app 4 个业务触发器
biz.ai_trigger_jobs zqyy_app AI 事件链触发器
scheduled_tasks zqyy_app ETL 调度任务
pg_stat_activity 各库 连接池状态
pg_database 各库 数据库大小

Cron 表达式校验规则

PATCH API 中的 cron_expression 校验逻辑:

import re

CRON_FIELD_PATTERNS = [
    r'(\*|[0-5]?\d)',                    # minute: 0-59
    r'(\*|[01]?\d|2[0-3])',              # hour: 0-23
    r'(\*|[1-9]|[12]\d|3[01])',          # day of month: 1-31
    r'(\*|[1-9]|1[0-2])',               # month: 1-12
    r'(\*|[0-6])',                        # day of week: 0-6
]

def validate_cron_expression(expr: str) -> bool:
    """校验 5 字段 cron 表达式基本格式。"""
    parts = expr.strip().split()
    if len(parts) != 5:
        return False
    for part, pattern in zip(parts, CRON_FIELD_PATTERNS):
        if not re.fullmatch(pattern, part):
            return False
    return True

正确性属性

属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。

属性 1DB 健康 API 已连接数据库返回完整指标

对于任意 处于 connected 状态的数据库健康响应项,其 active_connectionsidle_connectionsdb_size_mbslow_query_count 字段均不为 null 且类型正确(连接数为非负整数,大小为非负浮点数)。

验证需求2.4, 6.2

属性 2Tab 切换状态保持 round-trip

对于任意 ETL 任务管理或触发器管理的 Tab 视图,在某个 Tab 中设置筛选条件后切换到另一个 Tab 再切回来,原 Tab 的筛选条件应保持不变。

验证需求3.5

属性 3触发器统一视图数据完整性与字段完整性

对于任意 一组来自 biz.trigger_jobs、biz.ai_trigger_jobs、scheduled_tasks 三个数据源的触发器数据,统一视图 API 返回的记录总数应等于三个数据源记录数之和,且每条记录都包含 name、source、trigger_condition、status、last_run_at、next_run_at、last_error 字段。

验证需求4.1, 4.2

属性 4触发器配置编辑不变量

对于任意 通过 PATCH /api/trigger-jobs/{id}/config 的成功更新请求trigger_job 中除 trigger_config 内的 cron_expression / interval_seconds 和 next_run_at 外的所有字段id、job_type、job_name、trigger_condition、status、description、created_at应保持不变。

验证需求4.7

属性 5触发器配置更新正确性

对于任意 有效的 cron_expression符合 5 字段 cron 语法)或有效的 interval_seconds>= 1通过 PATCH API 更新后,返回的 trigger_job 中 trigger_config 对应字段值应等于请求值,且 next_run_at 应被重新计算为非 null 值。

验证需求5.2, 5.3, 5.7

属性 6触发器配置校验拒绝无效输入

对于任意 不符合 5 字段 cron 语法的字符串作为 cron_expression或任意小于 1 的整数作为 interval_secondsPATCH API 应返回 HTTP 422 状态码,且响应体包含描述性错误信息。

验证需求5.4, 5.5

属性 7侧边栏高亮与当前路由一致

对于任意 有效的应用路由路径侧边栏中被高亮selectedKeys的菜单项应对应该路由所属的一级模块。

验证需求10.3

属性 8Tab 切换与 URL 查询参数同步 round-trip

对于任意 Tab 视图页面ETL 任务管理、触发器管理),设置 URL 查询参数 ?tab=X 后渲染页面,当前激活的 Tab 应为 X反之点击 Tab Y 后URL 查询参数应更新为 ?tab=Y

验证需求10.4

错误处理

前端错误处理

场景 处理方式
API 请求失败(网络错误) Ant Design message.error() 提示,保持当前页面状态
API 返回 401 现有 axios 拦截器自动处理:尝试 refresh token → 失败则跳转 /login
API 返回 422校验错误 在编辑表单中展示具体错误信息(如"cron 表达式格式无效"
API 返回 500 message.error() 提示通用错误信息
组件渲染异常 现有 ErrorBoundary 组件捕获,展示降级 UI
DB 健康 API 超时 DbHealthCard 展示"加载超时"状态,提供重试按钮
触发器统一 API 部分数据源失败 后端返回可用数据源的数据,前端正常展示已获取的数据

后端错误处理

场景 处理方式
PATCH APItrigger_job 不存在 返回 HTTP 404 + {"detail": "任务 {id} 不存在"}
PATCH APIcron_expression 格式无效 返回 HTTP 422 + {"detail": "cron 表达式格式无效,需要 5 字段格式"}
PATCH APIinterval_seconds < 1 返回 HTTP 422 + {"detail": "interval_seconds 必须 >= 1"}
PATCH API请求体为空 返回 HTTP 422 + {"detail": "至少提供 cron_expression 或 interval_seconds 之一"}
DB 健康 API某库连接失败 该库返回 status: "disconnected",其余指标为 null不影响其他库
DB 健康 API所有库连接失败 返回 4 个 disconnected 项HTTP 200不返回 500
统一触发器 API某数据源查询失败 记录日志,返回其他数据源的数据,失败的数据源不包含在结果中
JWT 认证失败 返回 HTTP 401现有中间件处理

测试策略

双重测试方法

本项目采用单元测试 + 属性测试的双重策略:

  • 单元测试Vitest:验证具体示例、边界条件、错误场景
  • 属性测试fast-check:验证跨所有输入的通用属性
  • 两者互补:单元测试捕获具体 bug属性测试验证通用正确性

前端测试

工具Vitest + @testing-library/react + fast-check

属性测试配置

  • 每个属性测试最少运行 100 次迭代
  • 每个属性测试必须用注释引用设计文档中的属性编号
  • 标签格式:Feature: admin-web-restructure, Property {number}: {property_text}

单元测试覆盖

  • 菜单结构验证7 个一级菜单项、子项正确性)
  • 路由重定向(/ → /dashboard、/log-viewer → /etl-tasks
  • Dashboard 区块渲染
  • Tab 组件默认 Tab 选择
  • DbHealthCard 各状态渲染connected / disconnected

属性测试覆盖

  • 属性 2Tab 状态保持 round-trip
  • 属性 7侧边栏高亮与路由一致
  • 属性 8Tab-URL 参数同步 round-trip

后端测试

工具pytest + hypothesis

属性测试配置

  • 每个属性测试使用 @settings(max_examples=100)
  • 标签格式同上

单元测试覆盖

  • PATCH API 端点存在性
  • JWT 认证拦截
  • DB 健康 API 端点存在性
  • 统一触发器 API 端点存在性

属性测试覆盖

  • 属性 1DB 健康 API 已连接数据库返回完整指标
  • 属性 3触发器统一视图数据完整性
  • 属性 4触发器配置编辑不变量
  • 属性 5触发器配置更新正确性
  • 属性 6触发器配置校验拒绝无效输入

E2E 测试

工具Playwright

覆盖范围(每个新页面一个测试文件):

  • dashboard.spec.tsDashboard 页面渲染、4 个区块存在、跳转链接正确
  • etl-tasks.spec.tsETL 任务管理 Tab 切换、各 Tab 内容渲染
  • trigger-manager.spec.ts:触发器管理 4 个 Tab、统一视图数据、业务 Tab 编辑功能
  • navigation.spec.ts默认路由、重定向、菜单高亮、Tab-URL 同步

测试批次:每个测试文件控制在 Playwright 默认超时30s范围内。