- .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>
580 lines
21 KiB
Markdown
580 lines
21 KiB
Markdown
# 技术设计文档: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. **新增后端 API**:PATCH /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 端点,不修改现有业务逻辑。
|
||
|
||
## 架构
|
||
|
||
### 整体架构
|
||
|
||
```mermaid
|
||
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 "后端 API(FastAPI)"
|
||
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 / ETL(Tab) | /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. ETLTasks(ETL 任务管理)
|
||
|
||
```
|
||
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
|
||
|
||
```python
|
||
# 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:
|
||
```python
|
||
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
|
||
|
||
```python
|
||
# 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:
|
||
```python
|
||
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_statements` 或 `pg_stat_activity` 查慢查询
|
||
3. 连接失败时返回 `status: "disconnected"`,其余字段为 null
|
||
|
||
#### 3. GET /api/admin/triggers/unified
|
||
|
||
```python
|
||
# 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:
|
||
```python
|
||
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 模块
|
||
|
||
```typescript
|
||
// 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`:服务状态卡片
|
||
- `GitStatusSection`:Git 状态卡片
|
||
- `SystemResourceSection`:系统资源卡片
|
||
|
||
Dashboard 页面 import 这些子组件进行聚合展示。
|
||
|
||
|
||
## 数据模型
|
||
|
||
### 新增后端 Pydantic 模型
|
||
|
||
#### UpdateTriggerConfigRequest
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
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 类型
|
||
|
||
```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 校验逻辑:
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
|
||
## 正确性属性
|
||
|
||
*属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。*
|
||
|
||
### 属性 1:DB 健康 API 已连接数据库返回完整指标
|
||
|
||
*对于任意* 处于 `connected` 状态的数据库健康响应项,其 `active_connections`、`idle_connections`、`db_size_mb`、`slow_query_count` 字段均不为 null 且类型正确(连接数为非负整数,大小为非负浮点数)。
|
||
|
||
**验证需求:2.4, 6.2**
|
||
|
||
### 属性 2:Tab 切换状态保持 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_seconds,PATCH API 应返回 HTTP 422 状态码,且响应体包含描述性错误信息。
|
||
|
||
**验证需求:5.4, 5.5**
|
||
|
||
### 属性 7:侧边栏高亮与当前路由一致
|
||
|
||
*对于任意* 有效的应用路由路径,侧边栏中被高亮(selectedKeys)的菜单项应对应该路由所属的一级模块。
|
||
|
||
**验证需求:10.3**
|
||
|
||
### 属性 8:Tab 切换与 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 API:trigger_job 不存在 | 返回 HTTP 404 + `{"detail": "任务 {id} 不存在"}` |
|
||
| PATCH API:cron_expression 格式无效 | 返回 HTTP 422 + `{"detail": "cron 表达式格式无效,需要 5 字段格式"}` |
|
||
| PATCH API:interval_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)
|
||
|
||
**属性测试覆盖**:
|
||
- 属性 2:Tab 状态保持 round-trip
|
||
- 属性 7:侧边栏高亮与路由一致
|
||
- 属性 8:Tab-URL 参数同步 round-trip
|
||
|
||
### 后端测试
|
||
|
||
**工具**:pytest + hypothesis
|
||
|
||
**属性测试配置**:
|
||
- 每个属性测试使用 `@settings(max_examples=100)`
|
||
- 标签格式同上
|
||
|
||
**单元测试覆盖**:
|
||
- PATCH API 端点存在性
|
||
- JWT 认证拦截
|
||
- DB 健康 API 端点存在性
|
||
- 统一触发器 API 端点存在性
|
||
|
||
**属性测试覆盖**:
|
||
- 属性 1:DB 健康 API 已连接数据库返回完整指标
|
||
- 属性 3:触发器统一视图数据完整性
|
||
- 属性 4:触发器配置编辑不变量
|
||
- 属性 5:触发器配置更新正确性
|
||
- 属性 6:触发器配置校验拒绝无效输入
|
||
|
||
### E2E 测试
|
||
|
||
**工具**:Playwright
|
||
|
||
**覆盖范围**(每个新页面一个测试文件):
|
||
- `dashboard.spec.ts`:Dashboard 页面渲染、4 个区块存在、跳转链接正确
|
||
- `etl-tasks.spec.ts`:ETL 任务管理 Tab 切换、各 Tab 内容渲染
|
||
- `trigger-manager.spec.ts`:触发器管理 4 个 Tab、统一视图数据、业务 Tab 编辑功能
|
||
- `navigation.spec.ts`:默认路由、重定向、菜单高亮、Tab-URL 同步
|
||
|
||
**测试批次**:每个测试文件控制在 Playwright 默认超时(30s)范围内。
|
||
|