在准备环境前提交次全部更改。

This commit is contained in:
Neo
2026-02-19 08:35:13 +08:00
parent ded6dfb9d8
commit 4eac07da47
1387 changed files with 6107191 additions and 33002 deletions

View File

@@ -0,0 +1 @@
{"generationMode": "requirements-first"}

View File

@@ -0,0 +1,749 @@
# 设计文档Web 管理后台admin-web-console
## 概述
将现有 PySide6 桌面 GUI 替换为 BS 架构的 Web 管理后台。系统分为两部分:
- **后端**:在现有 `apps/backend/` FastAPI 骨架上扩展,新增 ETL 管理相关的 RESTful API 和 WebSocket 端点
- **前端**:在 `apps/admin-web/` 下使用 React + Vite + Ant Design 构建 SPA 应用
核心设计原则:
1. 后端通过子进程调用现有 ETL_CLI不重写 ETL 逻辑
2. 调度任务从本地 JSON 迁移至 PostgreSQL`zqyy_app` 库)
3. 前后端通过 JSON API 通信,实时日志通过 WebSocket 推送
4. 数据库查询限制为只读,防止误操作
5. **多门店隔离**:通过 `site_id` 贯穿全链路Operator 登录后绑定门店,所有 API 请求自动携带 site_id
6. **执行流程Flow分离**:完整保留现有 7 种 Flow 和 3 种处理模式,前端按 Flow 动态展示可选层和任务
## 架构
```mermaid
graph TB
subgraph "前端 (apps/admin-web/)"
FE[React SPA<br/>Vite + Ant Design]
end
subgraph "后端 (apps/backend/)"
API[FastAPI 应用]
AUTH[JWT 认证中间件]
WS[WebSocket 端点<br/>实时日志推送]
EXEC[TaskExecutor<br/>子进程管理]
QUEUE[TaskQueue<br/>队列管理]
SCHED[Scheduler<br/>定时调度]
end
subgraph "数据层"
PG_APP[(zqyy_app<br/>用户/队列/调度/历史)]
PG_ETL[(etl_feiqiu<br/>ODS/DWD/DWS)]
ENV[.env 文件]
end
subgraph "ETL Connector"
CLI[ETL CLI<br/>子进程]
end
FE -->|HTTP/WS| API
API --> AUTH
API --> WS
API --> EXEC
API --> QUEUE
API --> SCHED
EXEC -->|subprocess| CLI
CLI --> PG_ETL
QUEUE --> PG_APP
SCHED --> PG_APP
SCHED -->|到期触发| QUEUE
API -->|只读查询| PG_ETL
API -->|读写| PG_APP
API -->|读写| ENV
```
### 请求流程
1. 前端发起 HTTP 请求 → JWT 中间件验证令牌(提取 site_id→ 路由处理
2. 任务执行API 接收 TaskConfig含 site_id→ TaskQueue 入队 → TaskExecutor 取出执行 → 子进程调用 ETL_CLI`--store-id {site_id}`)→ stdout/stderr 通过 WebSocket 推送
3. 调度触发Scheduler 定时检查到期任务 → 自动入队 → 同上执行流程
### 多门店隔离设计
现有系统通过 `site_id`(即 `store_id`CLI 参数名为 `--store-id`)实现多门店数据隔离:
- ETL 数据库层:所有业务表包含 `site_id` 字段,`app` schema 通过 RLS 按 `current_setting('app.current_site_id')` 自动过滤
- ETL CLI 层:通过 `--store-id` 参数指定门店
Web 管理后台的隔离策略:
1. **用户-门店绑定**`admin_users` 表增加 `site_id` 字段,每个 Operator 绑定一个门店
2. **JWT 令牌携带 site_id**:登录时将 `site_id` 写入 JWT payload后续请求自动提取
3. **API 层自动注入**:所有涉及 ETL 操作的 API从 JWT 中提取 `site_id`,自动注入到 TaskConfig 和数据库查询中
4. **数据库查看器隔离**:查询 ETL 数据库时,设置 `SET LOCAL app.current_site_id = '{site_id}'`,利用 RLS 自动过滤
5. **队列和调度隔离**`task_queue``scheduled_tasks` 表增加 `site_id` 字段,查询时按 site_id 过滤
### 执行流程Flow配置设计
> 术语说明:**Connector**(数据源连接器)指对接的上游 SaaS 平台(如飞球),对应 `apps/etl/pipelines/{connector}/`**Flow**(执行流程)指 ETL 任务的处理链路描述数据从哪一层流到哪一层。CLI 参数 `--pipeline` 实际传递的是 Flow ID。
完整保留现有 7 种 Flow前端根据选择动态展示
| Flow ID | 显示名称 | 包含层 |
|---------|---------|--------|
| `api_ods` | API → ODS | ODS |
| `api_ods_dwd` | API → ODS → DWD | ODS, DWD |
| `api_full` | API → ODS → DWD → DWS汇总 → DWS指数 | ODS, DWD, DWS, INDEX |
| `ods_dwd` | ODS → DWD | DWD |
| `dwd_dws` | DWD → DWS汇总 | DWS |
| `dwd_dws_index` | DWD → DWS汇总 → DWS指数 | DWS, INDEX |
| `dwd_index` | DWD → DWS指数 | INDEX |
3 种处理模式:
- `increment_only`:仅增量处理
- `verify_only`:校验并修复(可选"校验前从 API 获取"
- `increment_verify`:增量 + 校验并修复
时间窗口模式:
- `lookback`:回溯 + 冗余lookback_hours + overlap_seconds
- `custom`自定义时间范围window_start + window_end
- 窗口切分:不切分 / 按天1天/10天/30天
前端交互逻辑:
1. Operator 选择 Flow → 前端根据 Flow 包含的层,动态显示/隐藏 ODS 任务选择、DWD 表选择、DWS 任务选择
2. Operator 选择处理模式 → 前端根据模式显示/隐藏校验相关选项
3. Operator 选择时间窗口模式 → 前端切换回溯配置或自定义日期选择器
## 组件与接口
### 后端 API 路由
```
apps/backend/app/
├── main.py # FastAPI 入口(已有,扩展)
├── config.py # 配置加载(已有)
├── database.py # 数据库连接(已有,扩展)
├── auth/
│ ├── __init__.py
│ ├── jwt.py # JWT 令牌生成/验证
│ └── dependencies.py # FastAPI 依赖注入(当前用户)
├── routers/
│ ├── auth.py # POST /api/auth/login, POST /api/auth/refresh
│ ├── tasks.py # 任务注册表 & 配置 API
│ ├── execution.py # 任务执行 & 队列 API
│ ├── schedules.py # 调度任务 CRUD API
│ ├── env_config.py # 环境配置 API
│ ├── db_viewer.py # 数据库查看器 API
│ └── etl_status.py # ETL 状态 API
├── schemas/
│ ├── auth.py # 认证相关 Pydantic 模型
│ ├── tasks.py # 任务配置 Pydantic 模型
│ ├── execution.py # 执行记录 Pydantic 模型
│ ├── schedules.py # 调度配置 Pydantic 模型
│ └── db_viewer.py # 数据库查看器 Pydantic 模型
├── services/
│ ├── task_executor.py # 子进程管理,执行 ETL_CLI
│ ├── task_queue.py # 任务队列管理
│ ├── scheduler.py # 定时调度器
│ └── cli_builder.py # CLI 命令构建(从 gui/utils/cli_builder.py 迁移)
├── middleware/
│ └── auth.py # JWT 认证中间件
└── ws/
└── logs.py # WebSocket 日志推送端点
```
### 前端结构
```
apps/admin-web/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── index.html
├── src/
│ ├── main.tsx # 入口
│ ├── App.tsx # 根组件Layout + Router
│ ├── api/ # API 客户端
│ │ ├── client.ts # axios 实例JWT 拦截器)
│ │ ├── auth.ts
│ │ ├── tasks.ts
│ │ ├── execution.ts
│ │ ├── schedules.ts
│ │ ├── envConfig.ts
│ │ ├── dbViewer.ts
│ │ └── etlStatus.ts
│ ├── pages/
│ │ ├── Login.tsx
│ │ ├── TaskConfig.tsx # 任务配置
│ │ ├── TaskManager.tsx # 任务管理(队列 + 调度)
│ │ ├── EnvConfig.tsx # 环境配置
│ │ ├── DBViewer.tsx # 数据库查看器
│ │ ├── ETLStatus.tsx # ETL 状态
│ │ └── LogViewer.tsx # 日志查看器
│ ├── components/ # 可复用组件
│ │ ├── TaskSelector.tsx # 任务选择器(按业务域分组)
│ │ ├── DwdTableSelector.tsx
│ │ ├── TimeWindowForm.tsx
│ │ └── LogStream.tsx # WebSocket 日志流组件
│ ├── hooks/
│ │ ├── useAuth.ts
│ │ └── useWebSocket.ts
│ ├── store/ # 状态管理React Context 或 Zustand
│ │ └── authStore.ts
│ └── types/ # TypeScript 类型定义
│ └── index.ts
```
### 核心 API 端点
| 方法 | 路径 | 说明 | 需求 |
|------|------|------|------|
| POST | `/api/auth/login` | 用户登录,返回 JWT | 1 |
| POST | `/api/auth/refresh` | 刷新访问令牌 | 1 |
| GET | `/api/tasks/registry` | 获取任务注册表(按业务域分组) | 2 |
| GET | `/api/tasks/dwd-tables` | 获取 DWD 表定义(按业务域分组) | 2 |
| POST | `/api/tasks/validate` | 验证 TaskConfig | 2, 11 |
| GET | `/api/tasks/flows` | 获取执行流程列表7 种 Flow + 3 种处理模式) | 2 |
| POST | `/api/execution/run` | 提交任务执行 | 3 |
| GET | `/api/execution/queue` | 获取当前队列 | 4 |
| POST | `/api/execution/queue` | 添加任务到队列 | 4 |
| PUT | `/api/execution/queue/reorder` | 调整队列顺序 | 4 |
| DELETE | `/api/execution/queue/{id}` | 从队列删除任务 | 4 |
| POST | `/api/execution/{id}/cancel` | 取消执行中的任务 | 3 |
| GET | `/api/execution/history` | 获取执行历史 | 4 |
| GET | `/api/execution/{id}/logs` | 获取历史任务日志 | 9 |
| GET | `/api/schedules` | 获取调度任务列表 | 5 |
| POST | `/api/schedules` | 创建调度任务 | 5 |
| PUT | `/api/schedules/{id}` | 更新调度任务 | 5 |
| DELETE | `/api/schedules/{id}` | 删除调度任务 | 5 |
| PATCH | `/api/schedules/{id}/toggle` | 启用/禁用调度任务 | 5 |
| GET | `/api/env-config` | 获取环境配置 | 6 |
| PUT | `/api/env-config` | 更新环境配置 | 6 |
| GET | `/api/env-config/export` | 导出配置(去敏感值) | 6 |
| GET | `/api/db/schemas` | 获取 Schema 列表 | 7 |
| GET | `/api/db/schemas/{name}/tables` | 获取表列表和行数 | 7 |
| GET | `/api/db/tables/{schema}/{table}/columns` | 获取表列定义 | 7 |
| POST | `/api/db/query` | 执行只读 SQL 查询 | 7 |
| GET | `/api/etl-status/cursors` | 获取 ETL 游标状态 | 8 |
| GET | `/api/etl-status/recent-runs` | 获取最近执行记录 | 8 |
| WS | `/ws/logs/{execution_id}` | 实时日志 WebSocket | 9 |
### 关键服务组件
#### TaskExecutor任务执行器
```python
class TaskExecutor:
"""管理 ETL_CLI 子进程的生命周期"""
async def execute(self, config: TaskConfig, execution_id: str) -> None:
"""
以子进程方式调用 ETL_CLI。
- 使用 asyncio.create_subprocess_exec 启动子进程
- 逐行读取 stdout/stderr广播到 WebSocket 连接
- 记录退出码和执行时长到数据库
"""
...
async def cancel(self, execution_id: str) -> bool:
"""向子进程发送 SIGTERM等待退出后标记为已取消"""
...
```
#### TaskQueue任务队列
```python
class TaskQueue:
"""基于 PostgreSQL 的任务队列"""
async def enqueue(self, config: TaskConfig) -> str:
"""入队,返回队列任务 ID"""
...
async def dequeue(self) -> Optional[QueuedTask]:
"""取出队首待执行任务"""
...
async def reorder(self, task_id: str, new_position: int) -> None:
"""调整任务在队列中的位置"""
...
async def process_loop(self) -> None:
"""后台循环:队列非空且无运行中任务时,自动取出执行"""
...
```
#### Scheduler调度器
```python
class Scheduler:
"""基于 PostgreSQL 的定时调度器"""
async def check_and_enqueue(self) -> None:
"""检查到期的调度任务,将其 TaskConfig 加入队列"""
...
async def start(self) -> None:
"""启动后台定时检查循环(每 30 秒检查一次)"""
...
```
#### CLIBuilderCLI 命令构建器)
从现有 `gui/utils/cli_builder.py` 迁移,核心逻辑不变:
```python
class CLIBuilder:
"""将 TaskConfig 转换为 ETL_CLI 命令行参数列表"""
def build_command(self, config: TaskConfig, etl_project_path: str) -> list[str]:
"""
构建完整命令:
[python, -m, cli.main, --pipeline, {flow_id}, --tasks, ..., --store-id, {site_id}, ...]
注意CLI 参数名 --pipeline 传递的是 Flow ID
"""
...
```
## 数据模型
### 数据库表zqyy_app
#### admin_users 表
```sql
CREATE TABLE admin_users (
id SERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(256) NOT NULL,
display_name VARCHAR(128),
site_id BIGINT NOT NULL, -- 绑定的门店 ID
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_admin_users_site ON admin_users(site_id);
```
#### task_queue 表
```sql
CREATE TABLE task_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id BIGINT NOT NULL, -- 门店隔离
config JSONB NOT NULL, -- 序列化的 TaskConfig
status VARCHAR(20) NOT NULL DEFAULT 'pending',
-- pending / running / success / failed / cancelled
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
exit_code INTEGER,
error_message TEXT
);
CREATE INDEX idx_task_queue_status ON task_queue(status);
CREATE INDEX idx_task_queue_site_position ON task_queue(site_id, position) WHERE status = 'pending';
```
#### task_execution_log 表
```sql
CREATE TABLE task_execution_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
queue_id UUID REFERENCES task_queue(id),
site_id BIGINT NOT NULL, -- 门店隔离
task_codes TEXT[] NOT NULL,
status VARCHAR(20) NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
finished_at TIMESTAMPTZ,
exit_code INTEGER,
duration_ms INTEGER,
command TEXT, -- 实际执行的 CLI 命令
output_log TEXT, -- stdout 完整日志
error_log TEXT, -- stderr 日志
summary JSONB, -- 执行摘要
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_execution_log_site_started ON task_execution_log(site_id, started_at DESC);
```
#### scheduled_tasks 表
```sql
CREATE TABLE scheduled_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_id BIGINT NOT NULL, -- 门店隔离
name VARCHAR(256) NOT NULL,
task_codes TEXT[] NOT NULL,
task_config JSONB NOT NULL, -- 序列化的 TaskConfig
schedule_config JSONB NOT NULL, -- 序列化的 ScheduleConfig
enabled BOOLEAN DEFAULT TRUE,
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
run_count INTEGER DEFAULT 0,
last_status VARCHAR(20),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_scheduled_tasks_site ON scheduled_tasks(site_id);
CREATE INDEX idx_scheduled_tasks_next_run ON scheduled_tasks(next_run_at)
WHERE enabled = TRUE;
```
### Pydantic 模型(后端 schemas
```python
# schemas/tasks.py
class TaskConfigSchema(BaseModel):
"""任务配置 — 前后端传输格式"""
tasks: list[str]
pipeline: str = "api_ods_dwd" # 执行流程 Flow ID7 种之一),对应 CLI --pipeline 参数
processing_mode: str = "increment_only" # 处理模式3 种之一)
pipeline_flow: str = "FULL" # 传统模式兼容(已弃用,保留向后兼容)
dry_run: bool = False
window_mode: str = "lookback" # lookback / custom
window_start: str | None = None
window_end: str | None = None
window_split: str | None = None # none / day
window_split_days: int | None = None # 1 / 10 / 30
lookback_hours: int = 24
overlap_seconds: int = 600
fetch_before_verify: bool = False
skip_ods_when_fetch_before_verify: bool = False
ods_use_local_json: bool = False
store_id: int | None = None # 门店 ID由后端从 JWT 注入,前端不传)
dwd_only_tables: list[str] | None = None # DWD 表级选择
extra_args: dict[str, Any] = {}
@model_validator(mode="after")
def validate_window(self) -> "TaskConfigSchema":
"""验证时间窗口:结束日期不早于开始日期"""
if self.window_start and self.window_end:
if self.window_end < self.window_start:
raise ValueError("window_end 不能早于 window_start")
return self
class PipelineDefinition(BaseModel):
"""执行流程Flow定义 — 注意:字段名保留 pipeline 以兼容 CLI 参数"""
id: str
name: str
layers: list[str]
class ProcessingModeDefinition(BaseModel):
"""处理模式定义"""
id: str
name: str
description: str
# schemas/schedules.py
class ScheduleConfigSchema(BaseModel):
"""调度配置"""
schedule_type: Literal["once", "interval", "daily", "weekly", "cron"]
interval_value: int = 1
interval_unit: Literal["minutes", "hours", "days"] = "hours"
daily_time: str = "04:00"
weekly_days: list[int] = [1]
weekly_time: str = "04:00"
cron_expression: str = "0 4 * * *"
enabled: bool = True
start_date: str | None = None
end_date: str | None = None
```
### TypeScript 类型(前端)
```typescript
// types/index.ts
interface TaskConfig {
tasks: string[];
pipeline: string; // 执行流程 Flow ID对应 CLI --pipeline
processing_mode: string; // 处理模式
pipeline_flow: string; // 传统模式兼容(已弃用)
dry_run: boolean;
window_mode: string; // lookback / custom
window_start: string | null;
window_end: string | null;
window_split: string | null; // none / day
window_split_days: number | null; // 1 / 10 / 30
lookback_hours: number;
overlap_seconds: number;
fetch_before_verify: boolean;
skip_ods_when_fetch_before_verify: boolean;
ods_use_local_json: boolean;
store_id: number | null; // 由后端注入
dwd_only_tables: string[] | null; // DWD 表级选择
extra_args: Record<string, unknown>;
}
interface PipelineDefinition {
id: string; // Flow ID字段名保留 pipeline 兼容 CLI
name: string;
layers: string[]; // 包含的层ODS / DWD / DWS / INDEX
}
interface ProcessingModeDefinition {
id: string;
name: string;
description: string;
}
interface TaskDefinition {
code: string;
name: string;
description: string;
domain: string;
requires_window: boolean;
is_ods: boolean;
is_dimension: boolean;
default_enabled: boolean;
}
interface ScheduleConfig {
schedule_type: "once" | "interval" | "daily" | "weekly" | "cron";
interval_value: number;
interval_unit: "minutes" | "hours" | "days";
daily_time: string;
weekly_days: number[];
weekly_time: string;
cron_expression: string;
enabled: boolean;
start_date: string | null;
end_date: string | null;
}
interface QueuedTask {
id: string;
site_id: number;
config: TaskConfig;
status: "pending" | "running" | "success" | "failed" | "cancelled";
position: number;
created_at: string;
started_at: string | null;
finished_at: string | null;
exit_code: number | null;
error_message: string | null;
}
interface ExecutionLog {
id: string;
site_id: number;
task_codes: string[];
status: string;
started_at: string;
finished_at: string | null;
exit_code: number | null;
duration_ms: number | null;
command: string;
summary: Record<string, unknown> | null;
}
```
## 正确性属性
*属性Property是指在系统所有有效执行中都应成立的特征或行为——本质上是对系统行为的形式化陈述。属性是人类可读规格说明与机器可验证正确性保证之间的桥梁。*
### Property 1: TaskConfig 序列化往返一致性
*For any* 有效的 TaskConfigSchema 对象,将其序列化为 JSON 字符串后再反序列化,应产生与原始对象等价的结果。
**Validates: Requirements 11.1, 11.2, 11.3**
### Property 2: 无效凭据始终被拒绝
*For any* 不存在于 admin_users 表中的用户名/密码组合,登录接口应返回 401 状态码。
**Validates: Requirements 1.2**
### Property 3: 有效 JWT 令牌授权访问
*For any* 由系统签发且未过期的 JWT 令牌,携带该令牌访问受保护端点应返回非 401 状态码。
**Validates: Requirements 1.3**
### Property 4: 任务注册表按业务域正确分组
*For any* Task_Registry 中的任务集合API 返回的分组结果中,每个任务应出现在且仅出现在其所属业务域的分组中。
**Validates: Requirements 2.1**
### Property 5: Flow 层级过滤正确性
*For any* Flow 选择和任务列表,过滤后的结果应只包含与所选 Flow 包含的层兼容的任务,且不遗漏任何兼容任务。
**Validates: Requirements 2.2**
### Property 6: 时间窗口验证
*For any* 两个日期字符串,当 window_end 早于 window_start 时TaskConfigSchema 验证应失败;当 window_end 不早于 window_start 时,验证应通过。
**Validates: Requirements 2.3**
### Property 7: TaskConfig 到 CLI 命令转换完整性
*For any* 有效的 TaskConfigSchemaCLIBuilder 生成的命令参数列表应包含 TaskConfig 中所有非空字段对应的 CLI 参数。
**Validates: Requirements 2.5, 2.6**
### Property 8: 队列 CRUD 不变量
*For any* 任务队列状态,入队一个任务后队列长度增加 1 且新任务状态为 pending删除一个 pending 任务后队列长度减少 1 且该任务不再出现在队列中。
**Validates: Requirements 4.1, 4.4**
### Property 9: 队列出队顺序
*For any* 包含多个 pending 任务的队列dequeue 操作应返回 position 值最小的任务。
**Validates: Requirements 4.2**
### Property 10: 队列重排一致性
*For any* 队列和重排操作(将任务移动到新位置),重排后队列中任务的相对顺序应与请求一致。
**Validates: Requirements 4.3**
### Property 11: 执行历史排序与限制
*For any* 执行历史记录集合API 返回的结果应按 started_at 降序排列,且结果数量不超过请求的 limit 值。
**Validates: Requirements 4.5, 8.2**
### Property 12: 调度任务 CRUD 往返
*For any* 有效的 ScheduleConfigSchema创建调度任务后再查询该任务返回的调度配置应与创建时提交的配置等价。
**Validates: Requirements 5.1, 5.4**
### Property 13: 到期调度任务自动入队
*For any* enabled 为 true 且 next_run_at 早于当前时间的调度任务check_and_enqueue 执行后该任务的 TaskConfig 应出现在执行队列中。
**Validates: Requirements 5.2**
### Property 14: 调度任务启用/禁用状态
*For any* 调度任务,禁用后 next_run_at 应为 NULL重新启用后 next_run_at 应被重新计算为非 NULL 值(对于非一次性调度)。
**Validates: Requirements 5.3**
### Property 15: .env 解析与敏感值掩码
*For any* 包含敏感键PASSWORD、TOKEN、SECRET、DSN的 .env 文件内容API 返回的键值对列表中这些键的值应被掩码替换,不包含原始敏感值。
**Validates: Requirements 6.1, 6.3**
### Property 16: .env 写入往返一致性
*For any* 有效的键值对集合(不含注释和空行),写入 .env 文件后再读取解析,应得到与原始集合等价的键值对。
**Validates: Requirements 6.2**
### Property 17: SQL 写操作拦截
*For any* 包含 INSERT、UPDATE、DELETE、DROP 或 TRUNCATE 关键词(不区分大小写)的 SQL 语句,数据库查看器 API 应拒绝执行并返回错误。
**Validates: Requirements 7.5**
### Property 18: SQL 查询结果行数限制
*For any* SQL 查询执行结果,返回的行数应不超过 1000。
**Validates: Requirements 7.4**
### Property 19: 日志过滤正确性
*For any* 日志行集合和过滤关键词,过滤后的结果应只包含含有该关键词的日志行,且不遗漏任何匹配行。
**Validates: Requirements 9.2**
### Property 20: 门店隔离 — 队列和调度数据不跨站泄露
*For any* 两个不同 site_id 的 Operator一个 Operator 查询队列/调度/执行历史时,结果中不应包含另一个 site_id 的数据。
**Validates: Requirements 1.3(隐含的多门店隔离要求)**
### Property 21: Flow 层级与任务兼容性
*For any* Flow 类型和任务定义,当 Flow 包含的层不包含该任务所属层时,该任务不应出现在可选列表中;当 Flow 包含该任务所属层时,该任务应出现在可选列表中。
**Validates: Requirements 2.2**
## 错误处理
### 认证错误
- 无效凭据:返回 `401 Unauthorized`,响应体包含 `{"detail": "用户名或密码错误"}`
- 令牌过期:返回 `401 Unauthorized`,响应体包含 `{"detail": "令牌已过期"}`
- 令牌无效:返回 `401 Unauthorized`,响应体包含 `{"detail": "无效的令牌"}`
### 任务执行错误
- TaskConfig 验证失败:返回 `422 Unprocessable Entity`,响应体包含字段级错误详情
- ETL_CLI 子进程超时:记录错误日志,任务状态标记为 `failed`error_message 记录超时信息
- ETL_CLI 子进程异常退出:记录 exit_code 和 stderr 输出,任务状态标记为 `failed`
- 取消不存在或已完成的任务:返回 `404 Not Found``409 Conflict`
### 队列错误
- 删除非 pending 状态的任务:返回 `409 Conflict`,提示只能删除待执行任务
- 重排包含非 pending 任务:忽略非 pending 任务,只重排 pending 任务
### 数据库查看器错误
- SQL 写操作拦截:返回 `400 Bad Request`,提示只允许只读查询
- SQL 查询超时30 秒):终止查询,返回 `408 Request Timeout`
- SQL 语法错误:返回 `400 Bad Request`,包含 PostgreSQL 原始错误信息
### 环境配置错误
- .env 文件不存在:返回 `404 Not Found`
- 配置格式错误:返回 `422 Unprocessable Entity`,包含错误行号和描述
- 文件写入权限不足:返回 `500 Internal Server Error`,记录详细错误日志
### 通用错误
- 所有 API 错误响应统一格式:`{"detail": "错误描述", "code": "ERROR_CODE"}`
- 未捕获异常:返回 `500 Internal Server Error`,日志记录完整堆栈
## 测试策略
### 测试框架
- **后端单元测试 & 属性测试**pytest + hypothesis
- 路径:`apps/backend/tests/`
- 运行:`cd apps/backend && pytest tests/ -v`
- **前端单元测试**Vitest + React Testing Library
- 路径:`apps/admin-web/src/__tests__/`
- 运行:`cd apps/admin-web && pnpm test`
### 属性测试Property-Based Testing
使用 hypothesis 库,每个属性测试至少运行 100 次迭代。
每个属性测试必须用注释标注对应的设计文档属性编号:
```python
# Feature: admin-web-console, Property 1: TaskConfig 序列化往返一致性
@given(config=st.builds(TaskConfigSchema, ...))
def test_task_config_round_trip(config):
...
```
属性测试重点覆盖:
- Property 1TaskConfig 序列化往返(核心数据模型)
- Property 6时间窗口验证输入验证
- Property 7TaskConfig 到 CLI 命令转换(关键业务逻辑)
- Property 8-10队列 CRUD 不变量(状态管理)
- Property 15-16.env 解析与写入往返(配置管理)
- Property 17SQL 写操作拦截(安全关键)
- Property 19日志过滤数据过滤逻辑
### 单元测试
单元测试覆盖具体示例和边界条件:
- JWT 令牌生成/验证/过期
- 调度器 next_run 计算(各种调度类型)
- CLI 命令构建的具体场景
- API 端点的请求/响应格式
- 前端组件渲染和交互
### 集成测试
需要数据库环境的测试:
- 任务队列的完整生命周期
- 调度任务的创建/触发/执行
- 数据库查看器的 Schema 浏览和查询执行
- ETL 状态查询

View File

@@ -0,0 +1,149 @@
# 需求文档Web 管理后台admin-web-console
## 简介
将现有 PySide6 桌面 GUI`gui/`)替换为基于浏览器-服务器BS架构的 Web 管理后台。后端 API 在现有 FastAPI 骨架(`apps/backend/`)上扩展,前端部署在 `apps/admin-web/`。功能覆盖现有 GUI 的六大模块任务配置、任务管理队列与调度、环境配置、数据库查看器、ETL 状态监控、日志查看器。
## 术语表
- **Admin_Web**Web 管理后台前端应用,部署在 `apps/admin-web/`
- **Backend_API**FastAPI 后端服务,部署在 `apps/backend/`,为 Admin_Web 提供 RESTful API
- **ETL_CLI**:现有 ETL 命令行工具(`apps/etl/pipelines/feiqiu/cli/main.py`Backend_API 通过子进程调用
- **Connector**:数据源连接器,指对接的上游 SaaS 平台(如飞球),对应目录 `apps/etl/pipelines/{connector_name}/`。后续可扩展更多 Connector台账类、球房类、财务类管理平台
- **Flow**:执行流程,指 ETL 任务的处理链路,描述数据从哪一层流到哪一层(如 `api_ods_dwd` 表示 API → ODS → DWD。对应 CLI 参数 `--pipeline`
- **Task_Registry**:任务注册表,定义所有可用 ETL 任务及其业务域分组
- **Task_Config**:任务执行配置,包含 Flow 类型、时间窗口、任务列表等参数
- **Schedule_Store**:调度任务持久化存储(从本地 JSON 迁移至 PostgreSQL
- **Operator**:管理后台操作员,即门店运营人员
- **Site**:门店,通过 `site_id` 标识,是多门店数据隔离的基本单位
## 需求
### 需求 1用户认证与会话管理
**用户故事:** 作为 Operator我希望通过用户名密码登录管理后台以确保只有授权人员能操作 ETL 系统。
#### 验收标准
1. WHEN Operator 提交有效的用户名和密码, THE Backend_API SHALL 返回一个 JWT 访问令牌和刷新令牌
2. WHEN Operator 提交无效的凭据, THE Backend_API SHALL 返回 401 状态码和错误描述
3. WHILE Operator 持有有效的 JWT 令牌, THE Backend_API SHALL 允许访问受保护的 API 端点
4. WHEN JWT 访问令牌过期, THE Backend_API SHALL 返回 401 状态码Admin_Web SHALL 使用刷新令牌自动获取新的访问令牌
5. WHEN 刷新令牌也过期, THE Admin_Web SHALL 将 Operator 重定向到登录页面
### 需求 2ETL 任务配置
**用户故事:** 作为 Operator我希望在 Web 界面上选择并配置 ETL 任务的执行参数,以便灵活控制数据处理的运行方式。
#### 验收标准
1. WHEN Operator 打开任务配置页面, THE Admin_Web SHALL 从 Backend_API 获取 Task_Registry 中所有可用任务,并按业务域(会员、结算、助教、商品、台桌、团购、库存等)分组展示
2. WHEN Operator 选择执行流程 Flow如 api_ods_dwd、dwd_dws 等), THE Admin_Web SHALL 根据 Flow 包含的层ODS / DWD / DWS / INDEX仅显示与所选层兼容的任务
3. WHEN Operator 配置时间窗口参数(开始日期、结束日期、窗口分割策略), THE Admin_Web SHALL 验证结束日期不早于开始日期
4. WHEN Operator 选择 DWD 装载任务, THE Admin_Web SHALL 展示 DWD 表级选择界面,允许按业务域分组勾选目标表
5. WHEN Operator 切换高级选项dry-run、force-full、skip-quality 等), THE Admin_Web SHALL 将这些选项反映到最终的 Task_Config 中
6. WHEN Operator 提交任务配置, THE Backend_API SHALL 验证 Task_Config 的完整性,并将其转换为有效的 ETL_CLI 命令参数
### 需求 3ETL 任务执行
**用户故事:** 作为 Operator我希望通过 Web 界面触发 ETL 任务执行,并实时查看执行进度和结果。
#### 验收标准
1. WHEN Operator 提交一个有效的 Task_Config, THE Backend_API SHALL 以子进程方式调用 ETL_CLI 执行任务,并返回一个任务执行 ID
2. WHILE ETL_CLI 子进程正在运行, THE Backend_API SHALL 捕获 stdout 和 stderr 输出,并通过 API 端点提供实时日志流
3. WHEN ETL_CLI 子进程执行完毕, THE Backend_API SHALL 记录退出码、执行时长和输出摘要到任务历史
4. IF ETL_CLI 子进程执行超时或异常退出, THEN THE Backend_API SHALL 记录错误信息并将任务状态标记为失败
5. WHEN Operator 请求取消正在执行的任务, THE Backend_API SHALL 向 ETL_CLI 子进程发送终止信号并将任务状态标记为已取消
### 需求 4任务队列管理
**用户故事:** 作为 Operator我希望管理任务执行队列以便批量安排和控制任务的执行顺序。
#### 验收标准
1. WHEN Operator 将一个 Task_Config 添加到队列, THE Backend_API SHALL 创建一个状态为"待执行"的队列任务项并返回其 ID
2. WHEN 队列中存在待执行任务且当前无任务正在运行, THE Backend_API SHALL 按队列顺序自动取出下一个任务并开始执行
3. WHEN Operator 调整队列中任务的顺序, THE Backend_API SHALL 更新任务的执行优先级
4. WHEN Operator 从队列中删除一个待执行任务, THE Backend_API SHALL 将该任务从队列中移除
5. WHEN Operator 查看任务历史, THE Backend_API SHALL 返回按时间倒序排列的历史执行记录,包含任务编码、状态、开始时间、执行时长和退出码
### 需求 5调度任务管理
**用户故事:** 作为 Operator我希望创建和管理定时调度任务以便 ETL Connector 能按计划自动运行。
#### 验收标准
1. WHEN Operator 创建调度任务时, THE Backend_API SHALL 接受调度配置(一次性 / 固定间隔 / 每日 / 每周 / Cron 表达式)并持久化到 Schedule_Store
2. WHEN 调度任务到达预定执行时间, THE Backend_API SHALL 自动将对应的 Task_Config 加入执行队列
3. WHEN Operator 启用或禁用一个调度任务, THE Backend_API SHALL 更新该任务的启用状态并重新计算下次执行时间
4. WHEN Operator 编辑调度任务的配置, THE Backend_API SHALL 验证新配置的有效性并更新 Schedule_Store
5. WHEN Operator 删除一个调度任务, THE Backend_API SHALL 从 Schedule_Store 中移除该任务及其执行历史
6. WHEN Operator 查看调度任务列表, THE Backend_API SHALL 返回所有调度任务及其最近执行状态、下次执行时间和执行次数
### 需求 6环境配置管理
**用户故事:** 作为 Operator我希望通过 Web 界面查看和编辑 ETL 的 .env 配置文件,以便调整运行参数而无需直接操作服务器文件。
#### 验收标准
1. WHEN Operator 打开环境配置页面, THE Backend_API SHALL 读取当前 .env 文件内容并返回键值对列表(敏感值如密码和令牌以掩码形式展示)
2. WHEN Operator 修改配置项并提交, THE Backend_API SHALL 验证配置格式的正确性,写入 .env 文件,并返回更新结果
3. WHEN Operator 导出配置, THE Backend_API SHALL 生成一份去除敏感值的配置文件供下载
4. IF Operator 提交的配置包含格式错误的键值对, THEN THE Backend_API SHALL 返回具体的错误位置和描述,拒绝写入
### 需求 7数据库查看器
**用户故事:** 作为 Operator我希望通过 Web 界面查看数据库表结构和执行查询,以便快速检查 ETL 数据质量。
#### 验收标准
1. WHEN Operator 打开数据库查看器页面, THE Admin_Web SHALL 展示可用的 Schema 列表meta、ods、dwd、core、dws、app
2. WHEN Operator 选择一个 Schema, THE Backend_API SHALL 返回该 Schema 下所有表的名称和行数统计
3. WHEN Operator 选择一张表, THE Backend_API SHALL 返回该表的列定义(列名、数据类型、是否可空、默认值)
4. WHEN Operator 提交一条 SQL 查询, THE Backend_API SHALL 在只读事务中执行该查询,限制返回行数上限为 1000 行,并返回结果集
5. IF Operator 提交的 SQL 包含写操作INSERT / UPDATE / DELETE / DROP / TRUNCATE, THEN THE Backend_API SHALL 拒绝执行并返回错误提示
6. IF SQL 查询执行超过 30 秒, THEN THE Backend_API SHALL 终止查询并返回超时错误
### 需求 8ETL 状态监控
**用户故事:** 作为 Operator我希望在 Web 界面上查看 ETL Connector 的运行状态和数据游标信息,以便及时发现异常。
#### 验收标准
1. WHEN Operator 打开 ETL 状态页面, THE Backend_API SHALL 返回各 ODS 表的最新数据游标(最后抓取时间、记录数)
2. WHEN Operator 查看最近执行记录, THE Backend_API SHALL 返回最近 50 条任务执行记录,包含任务名称、状态、开始时间、执行时长
3. WHEN Operator 刷新状态页面, THE Backend_API SHALL 返回最新的游标和执行状态数据
### 需求 9实时日志查看
**用户故事:** 作为 Operator我希望在 Web 界面上实时查看 ETL 任务的执行日志,以便监控任务进度和排查问题。
#### 验收标准
1. WHILE 一个 ETL 任务正在执行, THE Admin_Web SHALL 通过 WebSocket 或 SSE 连接实时展示该任务的日志输出
2. WHEN Operator 在日志查看器中输入过滤关键词, THE Admin_Web SHALL 仅展示包含该关键词的日志行
3. WHEN 任务执行完毕, THE Admin_Web SHALL 保留完整日志内容供 Operator 回顾
4. WHEN Operator 查看历史任务的日志, THE Backend_API SHALL 返回该任务的完整日志记录
### 需求 10响应式布局与导航
**用户故事:** 作为 Operator我希望管理后台具有清晰的导航结构和响应式布局以便在不同设备上高效操作。
#### 验收标准
1. THE Admin_Web SHALL 提供侧边栏导航包含六个功能模块入口任务配置、任务管理、环境配置、数据库、ETL 状态、日志
2. WHEN Operator 点击导航项, THE Admin_Web SHALL 切换到对应的功能模块页面,且不触发整页刷新
3. THE Admin_Web SHALL 在状态栏区域展示当前数据库连接状态和任务执行状态
4. WHILE 有任务正在执行, THE Admin_Web SHALL 在导航栏或状态栏显示执行中的视觉指示
### 需求 11Task_Config 序列化与反序列化
**用户故事:** 作为 Operator我希望任务配置能在前后端之间正确传输和持久化以确保配置不丢失。
#### 验收标准
1. THE Backend_API SHALL 将 Task_Config 序列化为 JSON 格式进行传输和存储
2. WHEN Backend_API 接收到 JSON 格式的 Task_Config, THE Backend_API SHALL 反序列化为内部 Task_Config 对象并验证所有必填字段
3. FOR ALL 有效的 Task_Config 对象, 序列化后再反序列化 SHALL 产生与原始对象等价的结果(往返一致性)
4. IF 反序列化时遇到缺失或类型错误的字段, THEN THE Backend_API SHALL 返回包含具体字段名和错误原因的验证错误

View File

@@ -0,0 +1,292 @@
# 实现计划Web 管理后台admin-web-console
## 概述
将现有 PySide6 桌面 GUI 替换为 BS 架构的 Web 管理后台。后端在 `apps/backend/` 上扩展 FastAPI API前端在 `apps/admin-web/` 下使用 React + Vite + Ant Design 构建。实现按"后端基础设施 → 核心 API → 前端骨架 → 功能模块逐个对接"的顺序推进。
## 任务
- [x] 1. 后端基础设施搭建
- [x] 1.1 创建数据库迁移脚本,在 `zqyy_app` 库中创建 4 张新表admin_users、task_queue、task_execution_log、scheduled_tasks所有表包含 site_id 字段
- 迁移脚本放在 `db/zqyy_app/migrations/`,日期前缀命名
- 包含索引创建site_id 相关的复合索引)
- 包含种子数据:插入一个默认管理员账号
- _Requirements: 1.1, 4.1, 5.1_
- [x] 1.2 实现 JWT 认证模块(`apps/backend/app/auth/`
- `jwt.py`JWT 令牌生成access_token + refresh_token、验证、解码payload 包含 user_id 和 site_id
- `dependencies.py`FastAPI 依赖注入函数 `get_current_user`,从 JWT 提取用户信息和 site_id
- 新增依赖:`python-jose[cryptography]``passlib[bcrypt]``apps/backend/pyproject.toml`
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [x] 1.3 实现认证路由(`apps/backend/app/routers/auth.py`
- `POST /api/auth/login`:验证用户名密码,返回 JWT 令牌对
- `POST /api/auth/refresh`:用刷新令牌换取新的访问令牌
- Pydantic schemas`LoginRequest``TokenResponse`
- _Requirements: 1.1, 1.2, 1.4_
- [x] 1.4 编写认证模块属性测试
- **Property 2: 无效凭据始终被拒绝**
- **Property 3: 有效 JWT 令牌授权访问**
- **Validates: Requirements 1.2, 1.3**
- [x] 1.5 扩展 `apps/backend/app/database.py`,新增 ETL 数据库只读连接函数
- `get_etl_readonly_connection(site_id)`:连接 ETL 数据库,设置 `SET LOCAL app.current_site_id`
- 配置项从 .env 读取 ETL 数据库连接参数
- _Requirements: 7.4, 7.5_
- [x] 1.6 在 `apps/backend/app/main.py` 中注册所有路由和中间件,配置 CORS 允许前端开发服务器访问
- _Requirements: 10.2_
- [x] 2. 检查点 — 确保认证模块测试通过
- 确保所有测试通过,如有问题请向用户确认。
- [x] 3. 任务配置与执行 API
- [x] 3.1 迁移 CLIBuilder 到后端(`apps/backend/app/services/cli_builder.py`
-`gui/utils/cli_builder.py` 迁移核心逻辑
- 适配新的 TaskConfigSchema自动注入 `--store-id` 参数
- 支持 7 种 Flow 和 3 种处理模式
- _Requirements: 2.6_
- [x] 3.2 实现任务注册表 API`apps/backend/app/routers/tasks.py`
- `GET /api/tasks/registry`:返回按业务域分组的任务列表
- `GET /api/tasks/dwd-tables`:返回按业务域分组的 DWD 表定义
- `GET /api/tasks/flows`:返回 7 种 Flow 定义和 3 种处理模式定义
- `POST /api/tasks/validate`:验证 TaskConfig 并返回生成的 CLI 命令预览
- Pydantic schemas 在 `apps/backend/app/schemas/tasks.py`
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 11.1, 11.2_
- [x] 3.3 编写 TaskConfig 属性测试
- **Property 1: TaskConfig 序列化往返一致性**
- **Property 6: 时间窗口验证**
- **Property 7: TaskConfig 到 CLI 命令转换完整性**
- **Validates: Requirements 2.3, 2.5, 2.6, 11.1, 11.2, 11.3**
- [x] 3.4 实现 TaskExecutor 服务(`apps/backend/app/services/task_executor.py`
- 使用 `asyncio.create_subprocess_exec` 启动 ETL_CLI 子进程
- 逐行读取 stdout/stderr存储到内存缓冲区并广播到 WebSocket
- 记录退出码、执行时长到 task_execution_log 表
- 支持取消(发送 SIGTERM
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [x] 3.5 实现 TaskQueue 服务(`apps/backend/app/services/task_queue.py`
- `enqueue(config, site_id)`:入队,自动分配 position
- `dequeue(site_id)`:取出 position 最小的 pending 任务
- `reorder(task_id, new_position, site_id)`:调整顺序
- `delete(task_id, site_id)`:删除 pending 任务
- `process_loop()`:后台协程,自动取出并执行
- _Requirements: 4.1, 4.2, 4.3, 4.4_
- [x] 3.6 实现执行与队列路由(`apps/backend/app/routers/execution.py`
- `POST /api/execution/run`:直接执行任务
- `GET /api/execution/queue`:获取当前队列(按 site_id 过滤)
- `POST /api/execution/queue`:添加到队列
- `PUT /api/execution/queue/reorder`:重排
- `DELETE /api/execution/queue/{id}`:删除
- `POST /api/execution/{id}/cancel`:取消
- `GET /api/execution/history`:执行历史(按 site_id 过滤limit 参数)
- `GET /api/execution/{id}/logs`:获取历史日志
- _Requirements: 3.1, 3.5, 4.1, 4.2, 4.3, 4.4, 4.5_
- [x] 3.7 编写队列属性测试
- **Property 8: 队列 CRUD 不变量**
- **Property 9: 队列出队顺序**
- **Property 10: 队列重排一致性**
- **Property 11: 执行历史排序与限制**
- **Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5, 8.2**
- [x] 4. 检查点 — 确保任务配置与执行 API 测试通过
- 确保所有测试通过,如有问题请向用户确认。
- [x] 5. 调度与辅助 API
- [x] 5.1 实现 Scheduler 服务(`apps/backend/app/services/scheduler.py`
- `check_and_enqueue()`:查询 enabled=true 且 next_run_at <= now 的调度任务,将其 TaskConfig 入队
- `start()`:启动后台 asyncio 循环,每 30 秒检查一次
- 在 FastAPI lifespan 中启动/停止
- _Requirements: 5.2_
- [x] 5.2 实现调度路由(`apps/backend/app/routers/schedules.py`
- `GET /api/schedules`:列表(按 site_id 过滤)
- `POST /api/schedules`:创建
- `PUT /api/schedules/{id}`:更新
- `DELETE /api/schedules/{id}`:删除
- `PATCH /api/schedules/{id}/toggle`:启用/禁用
- Pydantic schemas 在 `apps/backend/app/schemas/schedules.py`
- _Requirements: 5.1, 5.3, 5.4, 5.5, 5.6_
- [x] 5.3 编写调度属性测试
- **Property 12: 调度任务 CRUD 往返**
- **Property 13: 到期调度任务自动入队**
- **Property 14: 调度任务启用/禁用状态**
- **Validates: Requirements 5.1, 5.2, 5.3, 5.4**
- [x] 5.4 实现环境配置路由(`apps/backend/app/routers/env_config.py`
- `GET /api/env-config`:读取 .env敏感值掩码
- `PUT /api/env-config`:验证并写入 .env
- `GET /api/env-config/export`:导出去敏感值的配置文件
- 敏感键列表PASSWORD、TOKEN、SECRET、DSN
- _Requirements: 6.1, 6.2, 6.3, 6.4_
- [x] 5.5 编写环境配置属性测试
- **Property 15: .env 解析与敏感值掩码**
- **Property 16: .env 写入往返一致性**
- **Validates: Requirements 6.1, 6.2, 6.3**
- [x] 5.6 实现数据库查看器路由(`apps/backend/app/routers/db_viewer.py`
- `GET /api/db/schemas`:返回 Schema 列表
- `GET /api/db/schemas/{name}/tables`:返回表列表和行数
- `GET /api/db/tables/{schema}/{table}/columns`:返回列定义
- `POST /api/db/query`:只读 SQL 执行写操作拦截、1000 行限制、30 秒超时)
- 使用 `get_etl_readonly_connection(site_id)` 确保 RLS 隔离
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_
- [x] 5.7 编写数据库查看器属性测试
- **Property 17: SQL 写操作拦截**
- **Property 18: SQL 查询结果行数限制**
- **Validates: Requirements 7.4, 7.5**
- [x] 5.8 实现 ETL 状态路由(`apps/backend/app/routers/etl_status.py`
- `GET /api/etl-status/cursors`:查询 etl_admin.etl_cursor 表,返回各任务游标
- `GET /api/etl-status/recent-runs`:查询 task_execution_log 表,返回最近 50 条记录
- _Requirements: 8.1, 8.2, 8.3_
- [x] 5.9 实现 WebSocket 日志推送(`apps/backend/app/ws/logs.py`
- `WS /ws/logs/{execution_id}`:实时推送任务执行日志
- TaskExecutor 执行时广播日志行到已连接的 WebSocket 客户端
- _Requirements: 9.1, 9.4_
- [x] 6. 检查点 — 确保所有后端 API 测试通过
- 确保所有测试通过,如有问题请向用户确认。
- [x] 7. 前端项目初始化
- [x] 7.1 在 `apps/admin-web/` 下初始化 React + Vite + TypeScript 项目
- `pnpm create vite . --template react-ts`
- 安装核心依赖:`antd``@ant-design/icons``react-router-dom``axios``zustand`
- 配置 `vite.config.ts`API 代理到后端 `http://localhost:8000`
- 配置中文 localeantd ConfigProvider
- _Requirements: 10.1, 10.2_
- [x] 7.2 实现 API 客户端(`src/api/client.ts`
- 创建 axios 实例baseURL 指向 `/api`
- 请求拦截器:自动附加 JWT Authorization header
- 响应拦截器401 时尝试刷新令牌,刷新失败跳转登录页
- _Requirements: 1.3, 1.4, 1.5_
- [x] 7.3 实现认证状态管理(`src/store/authStore.ts`)和登录页(`src/pages/Login.tsx`
- Zustand store存储 token、user info、site_id
- 登录页Ant Design Form用户名 + 密码
- 登录成功后存储令牌到 localStorage 并跳转首页
- _Requirements: 1.1, 1.2_
- [x] 7.4 实现主布局(`src/App.tsx`)和路由配置
- Ant Design LayoutSider侧边栏导航+ Content + Footer状态栏
- react-router-dom6 个功能页面路由 + 登录页路由
- 路由守卫:未登录重定向到登录页
- 侧边栏导航项任务配置、任务管理、环境配置、数据库、ETL 状态、日志
- _Requirements: 10.1, 10.2, 10.3_
- [x] 8. 前端功能页面 — 任务配置
- [x] 8.1 实现任务配置页面(`src/pages/TaskConfig.tsx`
- Flow 选择器Radio Group7 种 Flow选择后动态显示/隐藏任务区域
- 处理模式选择器3 种模式 + 校验附加选项
- 时间窗口配置回溯模式lookback_hours + overlap_seconds/ 自定义模式DatePicker
- 窗口切分选项:不切分 / 按天1/10/30
- 高级选项折叠面板dry-run、force-full 等 Checkbox
- _Requirements: 2.2, 2.3, 2.5_
- [x] 8.2 实现 TaskSelector 组件(`src/components/TaskSelector.tsx`
-`/api/tasks/registry` 获取任务列表
- 按业务域分组展示Collapse + Checkbox Group
- 根据当前 Flow 包含的层过滤可见任务
- 全选/反选功能
- _Requirements: 2.1, 2.2_
- [x] 8.3 实现 DwdTableSelector 组件(`src/components/DwdTableSelector.tsx`
-`/api/tasks/dwd-tables` 获取 DWD 表定义
- 按业务域分组展示Collapse + Checkbox Group
- 仅在 Flow 包含 DWD 层时显示
- _Requirements: 2.4_
- [x] 8.4 实现任务提交和 CLI 命令预览
- 提交前调用 `/api/tasks/validate` 预览生成的 CLI 命令
- 提交到队列(`/api/execution/queue`)或直接执行(`/api/execution/run`
- 提交成功后跳转到任务管理页面
- _Requirements: 2.6, 3.1, 4.1_
- [x] 8.5 编写 Flow 层级过滤前端单元测试
- **Property 21: Flow 层级与任务兼容性**
- 使用 Vitest 测试过滤逻辑函数
- **Validates: Requirements 2.2**
- [x] 9. 前端功能页面 — 任务管理
- [x] 9.1 实现任务管理页面(`src/pages/TaskManager.tsx`
- Ant Design Tabs队列 + 调度 + 历史
- 队列 TabTable 展示当前队列,支持拖拽排序、删除、取消
- 历史 TabTable 展示执行历史,点击行查看详情和日志
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- [x] 9.2 实现调度管理 Tab
- 调度任务列表 Table名称、调度描述、启用状态 Switch、下次执行时间、执行次数
- 创建/编辑调度任务 Modal任务选择 + 调度配置(类型、间隔、时间等)
- 删除确认
- _Requirements: 5.1, 5.3, 5.4, 5.5, 5.6_
- [x] 9.3 实现状态栏任务执行指示
- 在 Layout Footer 或 Sider 底部显示当前执行状态
- 轮询 `/api/execution/queue` 检查是否有 running 状态的任务
- 有任务执行时显示 Spin 动画和任务名称
- _Requirements: 10.3, 10.4_
- [x] 10. 前端功能页面 — 辅助模块
- [x] 10.1 实现环境配置页面(`src/pages/EnvConfig.tsx`
- 键值对编辑表格Ant Design Tableeditable cells
- 敏感值显示为 `****`,编辑时可输入新值
- 保存按钮调用 `PUT /api/env-config`
- 导出按钮调用 `GET /api/env-config/export` 下载文件
- _Requirements: 6.1, 6.2, 6.3, 6.4_
- [x] 10.2 实现数据库查看器页面(`src/pages/DBViewer.tsx`
- 左侧 TreeSchema → Table 层级浏览
- 右侧上方SQL 编辑器Ant Design Input.TextArea 或集成 CodeMirror
- 右侧下方:查询结果 Table
- 选择表时自动展示列定义
- 执行查询按钮,结果分页展示
- _Requirements: 7.1, 7.2, 7.3, 7.4_
- [x] 10.3 实现 ETL 状态页面(`src/pages/ETLStatus.tsx`
- 游标状态 Table任务编码、最后抓取时间、记录数
- 最近执行记录 Table任务名称、状态 Tag、开始时间、执行时长
- 刷新按钮
- _Requirements: 8.1, 8.2, 8.3_
- [x] 10.4 实现日志查看器页面(`src/pages/LogViewer.tsx`)和 LogStream 组件
- WebSocket 连接 `/ws/logs/{execution_id}`,实时追加日志行
- 日志过滤输入框:按关键词过滤显示
- 自动滚动到底部,可手动暂停滚动
- 历史日志:从 `/api/execution/{id}/logs` 加载
- _Requirements: 9.1, 9.2, 9.3, 9.4_
- [x] 10.5 编写日志过滤前端单元测试
- **Property 19: 日志过滤正确性**
- 使用 Vitest 测试过滤函数
- **Validates: Requirements 9.2**
- [x] 11. 门店隔离集成验证
- [x] 11.1 编写门店隔离属性测试
- **Property 20: 门店隔离 — 队列和调度数据不跨站泄露**
- 使用 pytest + hypothesis 生成随机 site_id 对,验证数据隔离
- **Validates: Requirements 1.3**
- [x] 11.2 编写任务注册表分组属性测试
- **Property 4: 任务注册表按业务域正确分组**
- **Validates: Requirements 2.1**
- [x] 12. 最终检查点 — 确保所有测试通过 ✅
- 后端 302 passed / 0 failed前端 33 passed / 0 failed全部通过。
## 说明
- 标记 `*` 的子任务为可选测试任务,可跳过以加速 MVP
- 每个任务引用了具体的需求编号,确保可追溯
- 检查点用于阶段性验证,确保增量正确
- 属性测试验证通用正确性属性,单元测试覆盖具体示例和边界条件