# 需求文档：执行跳转闭环与执行历史

> 生成时间：2026-08-24
> 关联文档：`HANDOFF_性能测试.md`、`_PRD_批量执行跳转监控页.md`、`_PRD_批量执行合并报告.md`

---

## 1. 背景与动机

用户在执行瞬时并发（burst）登录接口任务时反馈两类问题：

### 问题一：执行流程无跳转闭环

点击任务列表的「执行」按钮后：
1. **不自动跳转到监控页** —— 只有成功提示，用户需手动找到「监控」按钮才能看到实时曲线；
2. **执行完成后不自动跳转到报告页** —— 用户需再次手动点击「报告」按钮；
3. 更深一层：`POST /tasks/{task_id}/run` 是**同步阻塞式 API**（`run_task()` 内部 `await run_in_executor(...)` 等待执行完毕才返回）。瞬时并发任务几秒内完成尚可接受，长稳压测（8 小时 endurance）会一直挂起请求直到 axios 5 分钟超时，此时任务实际仍在后台执行，前端状态与服务端脱节。

### 问题二：无执行历史，且现有数据已被污染

1. **只能看最新一份**：`PerformanceTask` 表直接存储执行结果（total_requests / actual_tps / p95 等约 40 个统计字段），每次执行**覆盖**上一次，过往执行结果全部丢失；
2. **监控数据无法回看**：`MonitorPanel.vue` 仅通过 WebSocket 接收实时推送，执行结束后再进入页面无任何数据（无回放能力）；
3. **报告仅一份**：`ReportPanel.vue` 读取的 `GET /tasks/{task_id}/report` 只反映任务当前字段，即最后一次执行；
4. **快照累积污染图表（隐藏缺陷）**：`_save_snapshots()` 只做 `add_all`，不清理旧快照。`performance_snapshots` 表仅按 `task_id` 关联、无执行批次标识，同一任务多次执行后快照**混存**，报告页按 `elapsed` 排序加载全部快照 → 趋势图上多次执行的曲线叠加交错，**执行次数越多图表越失真**；
5. **空态体验差**：从侧边菜单直接进入「执行监控」「报告查看」时（无 `taskId` 参数），页面只给一个任务下拉框，必须先选任务才能看到内容，且选完也只能看该任务最新一次的数据。

**用户诉求**：点「执行」→ 自动进入监控页 → 完成后自动进入报告页；每次执行都留档，过往的监控曲线与报告都能随时查看。

---

## 2. 现状分析

### 2.1 执行调用链（单任务）

```
TaskList.vue:handleRun()
    ↓
runTask(taskId) → POST /api/performance/tasks/{taskId}/run   [阻塞直到执行完成]
    ↓
service.run_task()
    ├── task.status = "running"、start_time = now
    ├── 线程池执行 PerformanceExecutor，每秒回调 snapshot_callback
    │     ├── 快照存入内存 snapshot_buffer
    │     └── WebSocket 广播 perf_snapshot → perf_{task_id} 分组
    ├── 完成：task.status = "completed" + _apply_result() 覆盖任务统计字段
    └── finally：批量写入快照（不删旧数据）+ 广播 perf_complete
    ↓
HTTP 响应返回 → 前端 ElMessage.success + 刷新列表（无任何跳转）
```

### 2.2 数据模型现状

| 表 | 问题 |
|------|------|
| `performance_tasks` | 结果统计字段（~40 个）直接挂在任务上，每次执行覆盖；`resource_summary` / `target_resource_summary` / `transaction_summary` / `api_summary` 四个 JSON 汇总同样覆盖 |
| `performance_snapshots` | 仅 `task_id` 外键，无执行批次标识；写入不清理旧快照 → 跨执行累积混存 |
| `performance_batch_executions` | 记录的是"一次批量执行多个任务"的批次，**不是**单任务的多次执行历史 |
| （缺失） | 无 `performance_executions` 执行记录表 |

### 2.3 前端页面现状

| 页面 | 现状 |
|------|------|
| `MonitorPanel.vue` | `?taskId=` 进入后连 WS 看实时数据；无参数进入只显示任务下拉框；无历史回放 |
| `ReportPanel.vue` | `?taskId=` 进入后调 `getReport(taskId)` 看任务当前字段；无参数进入只显示任务下拉框；无历史报告 |
| `TaskList.vue` | 「执行」后仅提示 + 刷新列表；操作列有手动「监控」「报告」按钮（仅 completed/failed 可见） |
| `BatchMonitor.vue` | 批量执行已有非阻塞 + 跳转模式（2026-08-21 上线），本次单任务跳转闭环与之对齐 |

---

## 3. 功能需求

### FR-1: 单任务执行 API 非阻塞化

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | `POST /tasks/{task_id}/run` 立即创建执行记录并返回，压测在后台执行（对齐批量执行的 `asyncio.create_task` 模式） |

**接口变更**：

**Response**（`PerformanceRunResponse` 新增 `execution_id` 字段）：

```json
{
  "message": "任务已开始执行",
  "task_id": "task_xxx",
  "execution_id": "exec_xxx",
  "status": "running"
}
```

**技术要点**：
- 参照 `create_batch_and_start()` → `_run_batch_background()` 的既有模式，后台任务持有独立的数据库会话与生命周期管理（`_background_tasks` 集合防 GC）；
- `run_task()` 内部核心逻辑保持"执行并等待"，供批量串行执行复用；
- 后台执行期间任务状态、执行记录状态由 WS 推送（`perf_snapshot` / `perf_complete`），前端不再依赖 HTTP 响应获知结果。

### FR-2: 执行后自动跳转监控页

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | `TaskList.vue` 的「执行」按钮点击、收到 FR-1 响应后，立即跳转 `/performance/monitor?taskId={taskId}&executionId={executionId}`，进入实时监控 |

**流程**：

```
点击「执行」 → API 立即返回 execution_id
    ↓ (< 500ms)
router.push('/performance/monitor?taskId=xxx&executionId=xxx')
    ↓
MonitorPanel 实时模式：WS 连接 + 从 0 开始绘制曲线
```

### FR-3: 执行完成后自动跳转报告页

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | `MonitorPanel.vue` 收到 `perf_complete` 消息后，展示完成横幅并倒计时 3 秒自动跳转 `/performance/report?executionId={executionId}` |

**交互细节**：
- 横幅显示：执行状态（completed ✅ / failed ❌）+ 核心指标摘要（请求数 / TPS / 错误率 / P95）；
- 横幅提供两个按钮：**「立即查看报告」**（跳过倒计时）、**「留在本页」**（取消自动跳转，保留完整曲线）；
- 执行失败同样跳转报告页（报告页展示失败原因 `error_message`）；
- WS 消息 `perf_complete` 增加 `execution_id` 字段。

### FR-4: 新增执行记录表 `performance_executions`

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | 每次执行创建一条不可变的执行记录，承载该次执行的全部结果数据 |

**表结构**：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | String(64) PK | `exec_xxx`（UUID） |
| `task_id` | String(64), FK→performance_tasks, INDEX, CASCADE | 关联任务 |
| `task_name` | String(200) | 任务名快照（冗余，供历史列表直接展示） |
| `project_id` | String(64), nullable, INDEX | 项目快照（便于按项目过滤） |
| `status` | String(20) | running / completed / failed / cancelled |
| `error_message` | Text, nullable | 失败原因 |
| `start_time` / `end_time` | DateTime | 起止时间 |
| `duration_actual` | Float | 实际时长（秒） |
| 统计指标 ×34 | 同 PerformanceTask 结果字段 | total_requests / success_count / fail_count / error_rate / actual_tps / peak_tps / min·max·avg·p50·p90·p95·p99_response_time / std_dev / latency_avg·min·max / connect_time_avg·max / apdex / total_sent·received_bytes / error_type_* ×6 / status_2xx·3xx·4xx·5xx |
| 汇总 JSON ×4 | JSON | resource_summary / target_resource_summary / transaction_summary / api_summary |
| `snapshot_count` | Integer | 该次执行快照条数 |
| `triggered_by` | String(20) | manual（单任务执行）/ batch（批量执行） |
| `batch_id` | String(64), nullable | 批量执行时关联批次 |
| `created_at` | DateTime | 创建时间 |

**写入规则**（`run_task()` 双写）：
1. 执行启动时创建记录（status=running）；
2. 执行完成后将结果**同时**写入 execution 记录与 `PerformanceTask` 现有字段（任务字段保留"最新一次结果"语义，任务列表页、批量报告等现有功能零回归）；
3. 批量执行（`batch_run_tasks`）内部调用同样创建 execution 记录（triggered_by=batch，带 batch_id）。

### FR-5: 快照表关联执行记录

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | `performance_snapshots` 新增 `execution_id` 列（String(64)，nullable，INDEX），修复快照累积混存缺陷 |

- 新快照写入时必须带 `execution_id`；
- 报告 / 回放查询一律按 `(execution_id, elapsed)` 过滤排序，不再按裸 `task_id`；
- `task_id` 列保留（兼容 + 便于运维排查）；
- **报告图表数据修复**：`get_report()` 改为按执行记录取快照后，多次执行不再互相污染。

### FR-6: 执行历史查询 API

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | 新增执行历史列表与详情端点 |

**接口定义**：

```
GET /api/performance/executions?task_id=&project_id=&status=&page=&page_size=
```

**Response（列表项）**：

```json
{
  "total": 42,
  "items": [
    {
      "id": "exec_xxx",
      "task_id": "task_xxx",
      "task_name": "登录接口-瞬时并发",
      "project_id": "proj_xxx",
      "status": "completed",
      "start_time": "2026-08-24T10:00:00",
      "end_time": "2026-08-24T10:01:00",
      "duration_actual": 60.0,
      "total_requests": 3600,
      "actual_tps": 59.8,
      "avg_response_time": 85.3,
      "p95_response_time": 152.1,
      "error_rate": 0.005,
      "triggered_by": "manual",
      "snapshot_count": 60
    }
  ]
}
```

- 按开始时间倒序分页；
- 可选删除：`DELETE /api/performance/executions/{execution_id}`（连带删除其快照，P1）。

### FR-7: 历史报告 API

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | 新增按执行记录查看报告的端点 |

```
GET /api/performance/executions/{execution_id}/report
```

- 响应结构与现有 `GET /tasks/{task_id}/report` 完全一致（summary / metrics / snapshots / 四类汇总），数据源改为 execution 记录 + 该执行的快照；
- 现有 `GET /tasks/{task_id}/report` **保留**并改为内部转发"该任务最新一条执行"，向后兼容（旧入口、合并报告无回归）。

### FR-8: 历史监控回放 + 快照查询 API

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | MonitorPanel 支持历史回放模式，从数据库加载某次执行的全部快照绘制图表 |

```
GET /api/performance/executions/{execution_id}/snapshots?page=&page_size=
```

**MonitorPanel 模式判定**：

| 进入方式 | 模式 | 数据源 |
|---------|------|--------|
| `?taskId=x&executionId=y`（执行中） | 实时 | WS 推送 |
| `?executionId=y`（已结束） | 回放 | 快照 API 全量加载 |
| `?taskId=x`（未带 executionId） | 自动判定 | 任务 running → 实时；否则定位最新执行 → 回放 |

**回放模式 UI**：
- 页面顶部显示「⏱ 历史回放」徽标 + 执行时间 + 状态标签，与实时模式明确区分；
- 图表复用现有全部图表（TPS / 响应时间 / 延迟 / 并发 / 状态码 / 资源 / 目标机 / Java 进程），一次性全量渲染；
- 长稳任务（数万点）图表启用 ECharts 降采样（`sampling: 'lttb'`）。

### FR-9: 监控页 / 报告页空态与选择器改造

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | 两个页面的空态从"任务下拉框"改为"最近执行记录列表"，已查看报告/监控时提供执行切换器 |

**空态（无参数进入）**：
- 显示跨任务的「最近执行记录」表格：任务名 / 项目 / 场景 / 状态 / 开始时间 / 时长 / TPS / 错误率 / 触发方式；
- 每行两个操作：**「监控回放」**（`/performance/monitor?executionId=x`）与**「查看报告」**（`/performance/report?executionId=x`）；
- 正在运行中的执行显示运行中标识，操作为**「实时监控」**；
- 支持按任务名 / 状态筛选。

**报告页执行切换器**：
- 报告内容区顶部显示当前执行的标识（任务名 + 第 N 次 + 时间），右侧下拉列出该任务的全部历史执行，切换即更新 URL `executionId` 并重载；
- `?taskId=x` 兼容：自动解析为该任务最新执行。

**TaskList 操作列**：
- 「历史」入口 → `/performance/report?taskId=x`（查看该任务执行历史与报告）。

### FR-10: 存量数据回填迁移

| 属性 | 说明 |
|------|------|
| 优先级 | P0 |
| 描述 | 为存量任务生成一条 legacy 执行记录，使旧数据在新 UI 下可看 |

**回填规则**（应用启动时执行一次，幂等）：
1. 对每个 `start_time` 非空的任务，将其当前结果字段（统计指标 + 四类 JSON 汇总 + 起止时间 + 状态）拷贝为一条 execution 记录（triggered_by 标记为 `legacy`）；
2. 该任务名下 `execution_id IS NULL` 的存量快照全部归入这条 legacy 记录；
3. 已存在执行记录的任务跳过（幂等保护）；
4. 迁移方式沿用 `_ensure_columns` 自动加列机制，SQLite（本地）与 MySQL（5.60）双端兼容（注意 MySQL TEXT 列不支持 DEFAULT 的既有踩坑）。

---

## 4. 非功能需求

| 类型 | 要求 |
|------|------|
| 性能 | 执行接口响应 < 500ms（非阻塞后不再受压测时长影响）；回放页首屏 < 2s（1 万快照内） |
| 数据一致性 | 任务字段（最新结果）与执行记录（每档结果）双写原子提交；快照与执行记录同事务 |
| 兼容性 | `GET /tasks/{id}/report`、任务列表指标、批量执行、合并报告全部无回归；旧 URL（`?taskId=`）行为不破坏 |
| 健壮性 | 后台执行异常不拖垮 API 进程（`_background_tasks` 管理回调）；执行记录创建失败则拒绝启动执行 |
| 数据量 | 单执行快照量级最高 ~28800 条（8h 长稳），快照查询必须走 `(execution_id)` 索引 |
| 部署 | `_ensure_columns` 自动迁移 + 启动回填，5.60 部署时无需手工 SQL（除非 MySQL 列默认值限制） |

---

## 5. 不在范围

- 执行记录自动过期清理策略（保留 N 条 / N 天）—— 后续迭代，本次仅提供手动删除
- 实时监控断线重连后的快照补齐（WS 断线期间丢失的秒级数据不回补）
- 报告对比（两次执行 diff）、PDF 导出 —— 已在 P2 规划
- 批量执行监控页（BatchMonitor）的历史回放 —— 批次报告已有合并报告承载
- 定时触发性能任务的执行留档（当前无定时性能任务入口）

---

## 6. 影响范围

### 后端

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `backend/app/models/performance.py` | MODIFY | 新增 `PerformanceExecution` 模型；`PerformanceSnapshot` 新增 `execution_id` 列 |
| `backend/app/schemas/performance.py` | MODIFY | 执行历史 / 历史报告 / 历史快照响应 Schema；`PerformanceRunResponse` 新增 `execution_id` |
| `backend/app/services/performance_service.py` | MODIFY | `run_task()` 创建执行记录 + 双写；新增执行历史 / 历史报告 / 历史快照查询；`get_report()` 按执行取数；存量回填 |
| `backend/app/routers/performance.py` | MODIFY | run 端点非阻塞化；新增 `/executions` 系列 4 个端点 |
| `backend/app/database.py` | MODIFY | `_ensure_columns` 覆盖新表新列；启动回填钩子 |

### 前端

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `frontend/src/types/performance.ts` | MODIFY | 执行记录类型 + RunResponse 扩展 |
| `frontend/src/api/performance.ts` | MODIFY | 执行历史 / 历史报告 / 历史快照 / 删除 API 封装 |
| `frontend/src/views/performance/TaskList.vue` | MODIFY | `handleRun()` 跳转监控页；操作列「历史」入口 |
| `frontend/src/views/performance/MonitorPanel.vue` | MODIFY | 实时/回放双模式；完成横幅 + 倒计时跳报告；空态改执行历史列表 |
| `frontend/src/views/performance/ReportPanel.vue` | MODIFY | 按 executionId 加载；执行切换器；空态改执行历史列表 |

---

*本文档由 Claude Code 生成*
