# HANDOFF_性能测试

> 生成时间：2026-08-12 15:00
> 最后更新：2026-08-20 23:30
> 当前分支：`platform-auto-test`
> 最近提交：`7cb9b7c1 docs(performance): 更新 HANDOFF 记录批量执行合并报告实现 + 540 设备批量启动验证`
> 主分支：`master`

---

## 整体状态

| 阶段 | 状态 | 说明 |
|------|------|------|
| Phase 1 - 数据模型 + Schema | ✅ 完成 | ORM 模型 + Pydantic 校验 |
| Phase 2 - 压测执行引擎 | ✅ 完成 | 轻量级 asyncio + aiohttp 自研引擎 |
| Phase 3 - 后端路由 + Service | ✅ 完成 | 11 个 API 路由 + Service 层 |
| Phase 4 - 前端页面 | ✅ 完成 | 任务管理/监控/报告 三个面板 |
| Phase 5 - 集成测试 | ✅ 完成核心 | 真实 API（会议预约）端到端验证通过（2026-08-17） |
| Phase 6 - 响应捕获 + 跨任务引用 | ✅ 完成核心 | capture_rules + performance_task_outputs + REF 占位符（2026-08-17） |
| **读取系统配置被测系统URL** | ✅ 完成 | 运行时读取系统配置，不再依赖任务 target_url（2026-08-19） |
| **任务级自定义登录凭据** | ✅ 完成 | 新建/编辑任务可填自定义用户名/密码，验证码仍固定 csba（2026-08-19） |
| **从 curl 自动生成压测任务** | ✅ 完成 | 新建弹窗「从 curl 导入」→ 解析回填表单（2026-08-19） |
| **项目管理（项目卡片 + 批量执行）** | ✅ 完成 | 前端 Projects/ProjectDetail/TaskList 适配，后端路由已存在 |
| **指标体系增强（10项新指标）** | ✅ 完成 | 后端 P95/标准差/Apdex/延迟/连接时间/字节数/峰值TPS/错误分类 + 前端报告面板展示（2026-08-20） |
| **批量执行合并报告** | ✅ 完成 | 项目批量/一键执行生成**一份合并报告**（聚合指标 + 各任务明细），后端服务 + 前端合并报告页 + 项目详情适配（2026-08-20） |

---

## 🆕 最新会话进度（2026-08-20 23:30 — 指标体系增强部署）

### 本次会话完成的工作

#### 指标体系增强（10项新指标）✅ 已部署 5.60

**部署时间**：2026-08-20 23:00–23:10

**部署文件**（后端 5 个）：
- `backend/app/executors/performance_executor.py`
- `backend/app/services/performance_service.py`
- `backend/app/models/performance.py`
- `backend/app/schemas/performance.py`
- `backend/app/database.py`

**部署文件**（前端 dist 全量）：
- `npm run build` 通过 ✅（40.35s，无 TS 错误）
- dist/ 全量上传至 `/data/third_party/plat-auto-test/frontend/dist/`

**部署步骤**：
1. SCP 后端 5 文件 → `ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/app/`
2. SCP 前端 dist → `ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/dist/`
3. `docker exec plat-auto-test-app sh -c "find /app -name '*.pyc' -delete"` 清理缓存
4. `docker compose restart app` → 等待 healthy
5. 验证 `_ensure_columns` 自动迁移：
   - `performance_tasks` 新增 17 列（p95_response_time, std_dev, latency_avg, latency_min, latency_max, connect_time_avg, connect_time_max, peak_tps, total_sent_bytes, total_received_bytes, apdex, error_type_timeout, error_type_connect_error, error_type_client_error, error_type_http_4xx, error_type_http_5xx, error_type_assertion）
   - `performance_snapshots` 新增 7 列（p95_response_time, std_dev, latency_avg, connect_time_avg, sent_bytes, received_bytes, peak_tps）
6. API 验证：`GET /api/performance/tasks` 返回新指标字段（p95ResponseTime: 0.0, stdDev: 0.0 等），旧任务均为 0（需重新执行才填充）

**部署踩坑**：
- `find ... -delete` 在宿主机执行 Permission denied（ubains 无 `__pycache__` 写权限）→ 改用 `docker exec` 在容器内执行
- 历史任务新指标字段为 0 — 正常行为，只有部署后新执行的任务才会填充真实值

**遗留**：集成验证（跑一次短压测端到端验证新指标采集 → 快照 → 报告全流程）

**用户需求**：对标 JMeter 的市场标准，增强性能测试指标体系，提供更全面的分析维度。

**参考标准**：JMeter 默认指标 + 行业主流（P95/标准差/Apdex/吞吐量/错误分类等）

**本次新增 10 项指标**：

| # | 指标 | 含义 | 采集方式 |
|---|------|------|---------|
| 1 | **P95** | 95% 请求响应时间阈值 | 线性插值百分位数 |
| 2 | **标准差 (Std Dev)** | 响应时间离散程度 | 总体标准差 `sqrt(sum((x-mean)²)/n)` |
| 3 | **Apdex** | 用户满意度指数 | `(满意+容忍/2)/总数`，T=500ms，4T=2000ms |
| 4 | **首字节延迟 (Latency/TTFB)** | 从发送到收到第一个字节 | 请求级记录，平均/最大 |
| 5 | **连接时间 (Connect Time)** | TCP 连接建立耗时 | 请求级记录，平均/最大 |
| 6 | **发送/接收速率 (KB/s)** | 网络吞吐量 | 周期统计 `bytes / elapsed_sec` |
| 7 | **峰值 TPS** | 最高每秒吞吐量 | 周期快照中取最大值 |
| 8 | **错误分类(6种)** | timeout/connect_error/client_error/http_4xx/http_5xx/assertion_fail | record() 时按 error_type/status/error 分类 |
| 9 | **实时并发用户数** | 当前活跃线程数 | 外部传入，快照中记录 |
| 10 | **P50 响应时间** | 中位数响应时间 | 线性插值百分位数 |

**后端变更（3 文件）**：

| 文件 | 变更 |
|------|------|
| `backend/app/executors/performance_executor.py` | **MetricsCollector 增强** — 新增 `_std_dev()` / `_apdex()` / `_percentile()` 静态方法；`record()` 新增 `latency_ms/connect_ms/sent_bytes/received_bytes` 参数 + 错误分类逻辑；`snapshot()` 新增 `std_dev/p95/latency_avg/connect_time_avg/sent_bytes/received_bytes/peak_tps/concurrent_users` 输出；`summary()` 新增 `p50/p95/std_dev/apdex/latency_avg/connect_time_avg/total_sent/received_bytes/sent_kb_per_sec/received_kb_per_sec/error_type_*` 输出；`_reset()` 修复缺少 `_peak_tps` 初始化导致的 AttributeError |
| `backend/app/services/performance_service.py` | `_run_sync()` 中 `record()` 调用补传 `latency_ms/connect_ms/sent_bytes/received_bytes`；`summary()` 结果写入 DB 的 `p50/p95/std_dev/apdex/latency_avg/connect_time_avg/sent_kb_per_sec/received_kb_per_sec/error_type_*` 字段 |
| `backend/app/database.py` | 迁移 `performance_task_results` 表新增 10 个指标列 + 3 个错误分类列（`_ensure_columns` 追加 `avg_latency/avg_connect_time/std_dev/apdex/p95/p50/sent_kb_per_sec/received_kb_per_sec/error_type_connect_error/error_type_client_error/error_type_assertion`） |

**后端模型/schema 变更（2 文件）**：

| 文件 | 变更 |
|------|------|
| `backend/app/models/performance.py` | `PerformanceTaskResult` 新增 10 个指标字段 + 3 个错误分类字段（`avg_latency/max_latency/avg_connect_time/max_connect_time/std_dev/apdex/p95/p50/sent_kb_per_sec/received_kb_per_sec/error_type_connect_error/error_type_client_error/error_type_assertion`） |
| `backend/app/schemas/performance.py` | `PerformanceReportMetrics` 补全所有新指标字段；`_ensure_columns` 迁移逻辑同步 |

**前端变更（3 文件）**：

| 文件 | 变更 |
|------|------|
| `frontend/src/types/performance.ts` | `PerformanceReportMetrics` 新增 `stdDev/apdex/latencyAvg/latencyMin/latencyMax/connectTimeAvg/connectTimeMax/sentKbPerSec/receivedKbPerSec/errorTypeConnectError/errorTypeClientError/errorTypeAssertion`；`PerformanceSnapshot` 新增 `stdDev/latencyAvg/connectTimeAvg/sentBytes/receivedBytes/peakTps`；`PerfWsSnapshot.data` 新增 `stdDev/latencyAvg/connectTimeAvg/sentBytes/receivedBytes/peakTps`；`ApiPreset`/`ApiPresetCreate` 新增 `credentialName/loginUsername/loginPassword` |
| `frontend/src/views/performance/MonitorPanel.vue` | 实时监控面板新增 3 张 ECharts 图表：**响应时间趋势（含 P95）**（4 条线 → 5 条线，新增 P95 橙色线）、**延迟分解趋势**（首字节延迟 + 连接时间，虚线区分）、**带宽趋势**（发送/接收 KB/s），新增 `latencyChartRef`/`bandwidthChartRef` 引用和 dispose 逻辑 |
| `frontend/src/views/performance/ReportPanel.vue` | 报告面板核心指标从 8 个扩展到 **16 个卡片**（4 行）：Row1 总请求数/成功数/失败数/错误率；Row2 TPS/峰值 TPS/平均响应时间/**P95**（颜色编码 <300ms 绿 <1000ms 黄 ≥1000ms 红）；Row3 **标准差**/**Apdex**（颜色编码 ≥0.94 绿 ≥0.85 黄 <0.85 红）/**平均首字节延迟**/**平均连接时间**；Row4 **发送速率 KB/s**/**接收速率 KB/s**/最小/最大响应时间。新增**分位数表**（P95 行）、**错误分类表**（6 种错误类型+数量+占比）、**网络传输统计表**、**延迟分解图**（首字节延迟+连接时间）、**带宽趋势图**（发送/接收 KB/s） |

**测试文件（1 新建，25 个测试）**：

| 文件 | 测试 |
|------|------|
| `backend/tests/test_performance_executor.py` | **25 passed** — `TestStdDev`(3): 空/单值/常数/已知值；`TestApdex`(5): 空/全满意/全容忍/混合/自定义阈值；`TestPercentile`(4): 单值/空/线性插值/未排序输入；`TestRecord`(7): 成功记录/4xx/5xx/断言失败/timeout/连接错误；`TestSnapshot`(3): 空快照/字段重置/多周期；`TestSummary`(3): 空汇总/聚合/错误类型/速率 |

**验证结果**：
- 后端单元测试：`pytest tests/test_performance_executor.py -v` → **25 passed in 0.83s** ✅
- 后端全量测试：`pytest tests/ -v` → 252 passed / 4 failed / 8 errors（4 failed 均为 pre-existing `execution_cancel` 测试，8 errors 为 pre-existing API 测试错误，非本次引入）✅
- 前端 `npm run build` → **built in 30.21s**，vue-tsc 类型检查无错误 ✅（修复了 ApiPreset 类型缺少 `credentialName/loginUsername/loginPassword` 的 TS 错误）
- **尚未部署 5.60 服务器**

**⚠️ 关键算法**：
- **P95 线性插值**：`sorted_data[f] + (sorted_data[c] - sorted_data[f]) * (k - f)`，其中 `k = p/100 * (n-1)`，`f=floor(k)`，`c=ceil(k)`
- **总体标准差**：`math.sqrt(sum((x-mean)²)/n)`（分母为 n，非 n-1）
- **Apdex**：T=500ms 固定，满意 ≤T，容忍 ≤4T，超出 >4T；公式 `(satisfied + tolerating/2) / total`
- **错误分类优先级**：`error_type` 字段 > HTTP 状态码 → 错误消息中的关键字

---

### 🆕 项目管理与批量执行（2026-08-20 晚段，当前会话）

**用户需求**：性能测试模块参考 UI 自动化的模块管理，实现**项目/标准版区分**（project_type: standard/custom）+ **一键执行整个项目** + **进入项目勾选多个任务批量执行**。

**产出文档**：
- `Docs/PRD/性能测试/需求文档/_PRD_性能测试项目管理与批量执行.md`
- `Docs/PRD/性能测试/需求文档/_PRD_性能测试项目管理与批量执行_计划执行.md`（prd-plan 生成）

**后端（Phase 1 已完整实现并验证，10 条路由全部注册）** — 本次会话前后端均已实现：

| 层 | 变更 |
|------|------|
| `backend/app/models/performance.py` | **`PerformanceProject` 模型（新建）** — id(UUID proj_)/name/description/project_type(standard/custom)/icon/sort_order/filter 字段 + `to_dict()`/`to_stats_dict()`（含 task_count/last_run_status/last_run_time） |
| `backend/app/schemas/performance.py` | **项目 5 组 schema（新建）** — `PerformanceProjectCreate/Update/Response/ListResponse/StatsResponse` + `BatchRunRequest(taskIds, projectId?)`/`BatchRunResponse(message, batchId, taskCount)` |
| `backend/app/database.py` | 建表迁移：`performance_projects` 表 + `performance_tasks.project_id` 列（INDEX） |
| `backend/app/services/performance_service.py` | **项目 CRUD + stats**（task_count/last_run_status/last_run_time 聚合）、**`batch_run_tasks`**（串行逐个 await，失败不中断，进度 WS 广播 `perf_batch_progress`：batch_id/project_id/current_index/total_count/current_task_id/current_task_name/current_status/success_count/fail_count）、**`run_project_all`**（只执行 pending/completed/failed/cancelled 状态任务，按 created_at 升序，无任务抛 ValueError）、**`list_tasks` 支持 project_id 过滤**（`""`/`"null"` → 未分配 is_(None)，不传 → 全部） |
| `backend/app/routers/performance.py` | **项目路由 5 条**：GET/POST `/projects`、GET/PUT/DELETE `/projects/{id}`、GET `/projects/{id}/stats`；**批量路由 2 条**：POST `/tasks/batch-run`、POST `/projects/{id}/run-all` |

**项目-任务关系**：1:N，`project_id` 可空；删除项目不级联删除任务（仅置空，前端确认框已注明）。

**前端（3 文件新建/适配，构建已通过）**：

| 文件 | 状态 | 说明 |
|------|------|------|
| `frontend/src/views/performance/Projects.vue` | ✅ 新建 | **项目管理页**：标准版/项目定制 el-tabs 切换、卡片网格（任务数 + 最近执行状态/时间）、项目 CRUD 弹窗（名称/类型/图标/排序/描述）、「执行全部」（runProjectAll 串行）、点击卡片进入详情 |
| `frontend/src/views/performance/ProjectDetail.vue` | ✅ 新建 | **项目详情页**：返回按钮 + 项目信息头部、任务列表（按项目过滤 + 状态筛选 + 分页）、复选框 + 「批量执行」（batchRunTasks）+「执行全部」、「批量进度条卡片」（WebSocket `perf_batch_progress` 监听），单任务执行/停止/删除/跳监控报告 |
| `frontend/src/views/performance/TaskList.vue` | ✅ 适配 | 工具栏新增**项目筛选下拉**、表格新增**所属项目列**（未归属灰字）、创建/编辑弹窗新增**归属项目选择器**（任务名称旁 el-select）、保存提交 projectId、编辑回填 projectId |
| `frontend/src/router/index.ts` | ✅ 适配 | performance children 新增 `projects` + `projects/:id` 两路由（tasks 之前） |
| `frontend/src/App.vue` | ✅ 适配 | 侧边栏性能测试子菜单新增「项目管理」（任务管理之前）+ PERFORMANCE_LABELS |
| `frontend/src/types/performance.ts` | ✅ 前置已完成 | PerformanceTask/List 含 projectId；末尾有 PerfProjectType/PerformanceProject/Create/Update/ListResponse/Stats + BatchRunRequest/BatchRunResponse/PerfBatchProgress |
| `frontend/src/api/performance.ts` | ✅ 前置已完成 | listProjects/getProject/createProject/updateProject/deleteProject/getProjectStats/batchRunTasks/runProjectAll |

**⚠️ 关键设计决策（重要）**：
- **批量执行为串行**：逐个 `await`，任务失败不中断其余任务（符合产品预期，也避免并发写库锁）
- **WebSocket 进度推送存在缺口**：后端 `batch_run_tasks`/`run_project_all` 通过 `manager.broadcast("perf_batch", ...)` 广播 `perf_batch_progress`，**但后端目前只有 `/ws/{task_id}` WS 路由（分组 `perf_{task_id}`），尚无 `perf_batch` 分组的订阅端点**。ProjectDetail.vue 中 `connectBatchWs()` 已写好 `/api/performance/ws/batch-progress` 连接代码，但**该端点不存在，收不到推送**。当前依赖 POST 阻塞响应 + 刷新列表。**待办：后端补 WS 路由 `/ws/batch/{batch_id}`（订阅 perf_batch 分组）后实时进度条才可用**。
- **run_project_all 无任务时抛 `ValueError("项目下没有可执行的任务")`** → 前端显示错误消息

**构建验证**：`npm run build` → **built in 30.52s** ✅（首次 5 个 TS 错误全部修复：TS2440 deleteProject 同名冲突改名 handleDeleteProject、TS2322 PerfProjectType 断言、TS2345 参数类型、TS6133 未使用 import/参数；第二次构建因模板拼接瑕疵 Invalid end tag 修复后通过）

---

### 🆕 批量执行合并报告（2026-08-20 夜间，当前会话）✅ 已实现 + 已部署 5.60

**用户需求**："如果是批量的肯定是要合并的一份报告才行，输出需求文档和计划执行文档后实现" —— 项目维度批量执行/一键执行全部时，**生成一份合并报告**（聚合所有任务指标 + 各任务明细），取代 N 份独立报告。

**产出文档**：
- `Docs/PRD/性能测试/需求文档/_PRD_批量执行合并报告.md`（v1.0）
- `Docs/PRD/性能测试/需求文档/_PRD_批量执行合并报告_计划执行.md`（prd-plan 生成）

**后端（Phase 1）**：

| 层 | 变更 |
|------|------|
| `backend/app/models/performance.py` | **`PerformanceBatchExecution`（新表 `performance_batch_executions`）** — id(VARCHAR 64)/project_id/task_ids(TEXT)/status/total_count/success_count/fail_count/started_at/completed_at/created_at + `to_dict()`；**`PerformanceProjectReport`（新表 `performance_project_reports`）** — id/project_id/batch_id/task_ids + 全部聚合指标列（total_requests/success_count/fail_count/error_rate/actual_tps/avg/min/max/p50/p90/p95/p99_response_time/std_dev/apdex/status_2xx~5xx/total_sent_bytes/total_received_bytes/peak_tps/duration_actual）+ task_summaries(TEXT JSON)/created_at + `to_dict()` |
| `backend/app/schemas/performance.py` | **`BatchRunResponse.report_id: Optional[str]`**；`TaskSummaryItem`（task_id/task_name/status/total_requests/success_count/fail_count/avg_response_time/actual_tps/error_rate）；`ProjectReportSummary`（全部聚合指标）；`ProjectReportResponse`（id/project_id/batch_id/created_at/summary/task_summaries）；`ProjectReportListResponse`（total/items） |
| `backend/app/services/performance_service.py` | **`_aggregate_task_results()`** — 核心聚合算法；**`_weighted_percentile()`** — 加权百分位；`batch_run_tasks()` 改造 — 开始建 BatchExecution(running)，结束后聚合生成 ProjectReport，更新批次状态，返回值带 `report_id`；`get_project_report()`/`get_batch_report()`/`list_project_reports()` 查询；`_report_to_dict()` ORM→API |
| `backend/app/routers/performance.py` | 新增 3 条路由：`GET /projects/{project_id}/report`（最新合并报告）、`GET /batch-reports/{batch_id}`（按批次查）、`GET /projects/{project_id}/reports`（分页历史列表）；`batch-run`/`run-all` 响应补 `report_id` |

**⚠️ 聚合算法（重要）**：
- **计数类**（总请求/成功/失败/状态码/字节数）：**直接求和**
- **平均响应时间 / Apdex**：**加权平均**（权重 = 各任务请求数）
- **响应时间 min/max**：取各任务极值
- **P50/P90/P95/P99**：**加权百分位近似** — 按各任务该分位值排序，累计请求数达到目标分位位置的任务取值（精确分位需原始数据，当前为近似）
- **标准差**：**合并方差公式** `sqrt(Σ(n_i-1)·s_i² + Σn_i·(m_i-M)²) / (N-1)`（按各任务方差/均值/样本数合并）
- **错误率 / 实际 TPS**：聚合后**重新计算**（fail/total、total/duration）
- 任务失败不中断，`taskSummaries` 中按任务记录 status/指标，合并报告照常生成

**前端（Phase 2 + 3）**：

| 文件 | 状态 | 说明 |
|------|------|------|
| `frontend/src/views/performance/ProjectReport.vue` | ✅ 新建 | **合并报告页**：返回项目详情 + 页面标题 + **历史报告下拉切换**（同项目历史合并报告）+ 刷新/导出 JSON；执行摘要（el-descriptions）；**16 个聚合指标卡片**（4 行：请求统计/吞吐+响应时间/稳定性+极值/带宽）；**分位数表**（P50/P90/P95/P99）；**状态码分布表**（2xx~5xx + 占比）；**各任务明细表**（状态/请求/错误率/平均响应时间/TPS + 「查看报告」跳单任务报告）。`loadReport()` 支持 reportId 优先、projectId 兜底（最新）；`watch(reportId)` 路由切历史时自动重载 |
| `frontend/src/views/performance/ProjectDetail.vue` | ✅ 适配 | 新增「**查看合并报告**」按钮（`v-if="lastReportId"`，type=success）+「**历史报告**」按钮（常显）；`handleRunAll()`/`handleBatchRun()` 前置清空 `lastReportId`、成功后 `lastReportId.value = res.reportId`；新增 `viewMergedReport()`/`viewHistoryReports()` 跳转 |
| `frontend/src/router/index.ts` | ✅ 适配 | performance children 新增 `project-report` 路由（`/performance/project-report`，title 合并报告） |
| `frontend/src/types/performance.ts` | ✅ 适配 | `BatchRunResponse.reportId?`；`TaskSummaryItem`/`ProjectReportSummary`/`ProjectReportResponse`/`ProjectReportListResponse` |
| `frontend/src/api/performance.ts` | ✅ 适配 | `getProjectReport(projectId)`/`getBatchReport(batchId)`/`getProjectReports(projectId, params?)` |

**⚠️ 关键设计决策**：
- **合并报告在批量执行全部完成后一次性生成**（batch 内任务串行跑完 → 聚合 → 落库 `performance_project_reports`），响应阻塞返回 `report_id`，前端据此跳转
- **向后兼容**：单任务执行/报告链路完全不动；旧任务独立报告不受影响
- **WS 进度缺口仍未补**：`ProjectDetail.vue` 的 `connectBatchWs()` 连接的 `/api/performance/ws/batch-progress` 端点仍不存在（见下一节待办），进度条当前依赖 POST 阻塞响应 + 刷新

**部署（2026-08-20 夜间，已完成 5.60）**：
- 后端 5 文件 scp：`models/performance.py`、`schemas/performance.py`、`services/performance_service.py`、`routers/performance.py`、`database.py`（建表迁移重启时执行）✅
- 前端 dist 全量 scp ✅
- 清理 `*.pyc` + `docker restart plat-auto-test-app` → healthy ✅

**5.60 服务器实测验证（全部通过）**：
- `GET /api/performance/projects` → 项目列表 ✅
- `POST /api/performance/projects/{id}/run-all` → 返回 `batch_id` + `report_id` ✅
- `GET /api/performance/projects/{id}/report` → 合并报告（summary 聚合指标 + taskSummaries 明细）✅
- `GET /api/performance/batch-reports/{batch_id}` → 按批次查合并报告 ✅
- `GET /api/performance/projects/{id}/reports` → 分页历史合并报告列表 ✅

**⚠️ 部署经验（Python 3.14 兼容）**：
- 服务器容器 Python 3.14 **不支持 f-string 嵌套引号**（如 `f"SKIP {t["name"]}"`），脚本/代码必须用变量先取值或字符串拼接
- 脚本内字典键要用 `t["id"]` 不能用 `t[id]`

---

### 上一会话（2026-08-19）完成的核心工作

#### 0. 从 curl 自动生成压测任务 ✅（已在 5.60 部署）

**用户需求**：新建性能测试任务时，粘贴接口调用 curl（含 `-X`/URL/`-H`/`-d`）+ 补充账号密码/压测模式/并发数/时长/预热 → 自动回填表单生成压测任务，减少手动填写。

**范围（用户已确认）**：仅解析 + 回填新建任务表单，**不含**「另存为接口预设」联动。Authorization 头剥离 → 置登录开关，Cookie 保留到 headers。

**产出文档**：
- `Docs/PRD/性能测试/需求文档/_PRD_从curl自动生成压测任务.md`
- `Docs/PRD/性能测试/需求文档/_PRD_从curl自动生成压测任务_计划执行.md`

**后端（3 文件 + 测试）**：

| 文件 | 变更 |
|------|------|
| `backend/app/utils/curl_parser.py` | **新建** — 纯函数 curl 解析器，仅 `shlex` 标准库，无外部依赖 |
| `backend/app/schemas/performance.py` | **追加** — `CurlParseRequest` / `CurlParseResponse` schema |
| `backend/app/routers/performance.py` | **追加** — `POST /api/performance/parse-curl` 路由（ValueError → 400） |
| `backend/tests/test_curl_parser.py` | **新建** — 11 个单元测试覆盖全部场景 |

**解析器能力矩阵**：
- 支持：`-X/--request`、`-H/--header`（多个 `; ` 合并）、`-d/--data/--data-raw/--data-binary/--data-json`（多个 `&` 拼接 + JSON 解析回退 raw）、URL 位置参数、`--key=value` 内联赋值
- 认证识别：`Authorization: Bearer` → `auth_required=true` 并**从 headers 剥离**；`X-RANDOM/X-TIMESTAMP/X-SIGN` → `sign_request=true` 并剥离；`Cookie` **保留**在 headers 并记录到 `cookie` 字段
- 方法推断：无 `-X` 时有 body → POST，否则 GET
- 不支持（进 `errors` 列表友好提示，不阻断）：`-F/--form` 文件上传、`-u/--user` Basic Auth、`--data-urlencode`
- 边界：空命令 / 无 URL → `ValueError` → 400

**⚠️ 关键决策**：`CurlParseRequest.curl_command` **故意不加 `min_length=1`** —— 否则 Pydantic 先返回 422，违反 PRD「空 curl → 400」约定；改为由 `parse_curl()` 抛 ValueError 转 400。

**前端（3 文件）**：

| 文件 | 变更 |
|------|------|
| `frontend/src/types/performance.ts` | **追加** — `CurlParseResult` 接口 |
| `frontend/src/api/performance.ts` | **追加** — `parseCurl(curlCommand)` API 封装 |
| `frontend/src/views/performance/TaskList.vue` | **修改** — 新建弹窗预设卡片旁「从 curl 导入」按钮 + 粘贴 dialog（textarea + errors 警告循环）+ 回填逻辑 |

**前端交互**：新建任务弹窗 → 「从 curl 导入」→ 粘贴 curl → 解析成功自动回填 method/targetUrl/headers/body/authRequired/signRequest，**清空 presetId 解除 disabled**，accountKey 回退默认；有 errors 弹警告提示；解析失败 ElMessage.error 提示。

**验证结果**：
- 后端单元测试：`python -m pytest tests/test_curl_parser.py` → **11 passed in 0.04s** ✅
- API 端点测试（httpx ASGITransport，8 项）：标准 POST / GET 推断 / Authorization 剥离 / 签名头剥离+Cookie 保留 / errors 列表 / 空命令 400 / 无 URL 400 / 内联赋值 → **全部通过** ✅
- 前端 `npm run build` → **built in 32.52s**，vue-tsc 类型检查无错误 ✅
- **已部署 5.60 服务器**（本次会话晚段）：前端 dist + 后端 7 文件（含 curl_parser.py），删 pyc + 重启容器 ✅

#### 0.5 第2批 + 第3批部署到 5.60 服务器 ✅（本次会话晚段完成）

**背景**：第2批（任务级自定义登录凭据）与第3批（curl 导入）本地改动均已完成验证但未部署；用户要求记录进度后部署到 5.60 服务器。

**执行步骤**：
1. scp 后端 6 文件：`app/utils/curl_parser.py`（新）、`app/models/performance.py`、`app/schemas/performance.py`、`app/services/performance_service.py`、`app/database.py`、`app/routers/performance.py` → `/data/third_party/plat-auto-test/backend/app/` 对应子目录 ✅
2. scp 前端 dist 全量 → `/data/third_party/plat-auto-test/frontend/dist/` ✅
3. `docker exec plat-auto-test-app sh -c "find /app -name '*.pyc' -delete"`（stale 字节码坑，必做）✅
4. `docker restart plat-auto-test-app` → 容器 `Up (healthy)` ✅

**服务器实测验证**：
- `GET /health` → `{"status":"healthy","service":"平台自动化测试系统","version":"1.0.0","debug":false}` ✅
- `POST /api/performance/parse-curl` 实测（带 `Authorization: Bearer` + `X-RANDOM` 签名头 + JSON body）：
  ```json
  {"method":"POST","targetUrl":"https://192.168.5.44/api/test","headers":{"Content-Type":"application/json"},
   "body":{"key":"val"},"authRequired":true,"signRequest":true,"cookie":null,"errors":[]}
  ```
  → Authorization 正确剥离置登录开关、签名头剥离置签名开关、body JSON 解析 ✅
- 空命令边界 → **HTTP 400** `{"detail":"curl 命令不能为空"}` ✅（符合 PRD 约定，非 422）
- `GET /api/performance/tasks` → 任务列表正常返回（MySQL 无异常，2 个历史任务）✅

**结论**：第2批 + 第3批改动已全部上线，用户可直接在新建任务弹窗使用「从 curl 导入」。

#### 1. 性能测试模块改为读取系统管理-系统配置的被测系统 URL ✅（本会话早段完成）

**背景**：系统管理-系统配置（`/system/settings`）已支持保存被测系统 URL，UI 自动化执行（`execution_service.py`）已运行时读取 `settings.TARGET_URL`。但性能测试模块存在两处问题：
1. `performance_service.py` 模块级常量 `_DEFAULT_BASE_URL = settings.TARGET_URL` 在 **import 时冻结**，运行时修改 `settings.TARGET_URL` 不生效
2. `_build_client_config()` 从任务的 `target_url` 字段解析 base_url，**覆盖**系统配置——切换被测环境要逐个改任务

**本次改动（按 PRD → 计划 → 代码流程，文档见 `Docs/PRD/性能测试/需求文档/`）**：

| 文件 | 变更 |
|------|------|
| `backend/app/config.py` | 新增 `AUTH_LOGIN_PATH`（默认 `/platform/api/auth/login`）、`AUTH_CAPTCHA_PATH`（默认 `/platform/api/code`） |
| `backend/app/models/dingtalk_config.py` | 新增 `login_path` / `captcha_path` 列（String(200)），`to_dict()` 返回 |
| `backend/app/schemas/dingtalk_config.py` | `SutConfigResponse`/`SutConfigUpdateRequest` 新增认证路径字段 |
| `backend/app/routers/system.py` | GET/PUT `/api/sut-config` 读写认证路径，PUT 校验路径以 `/` 开头 |
| `backend/app/database.py` | 旧库迁移：`dingtalk_configs` 加 `login_path`/`captcha_path` 列 |
| `backend/app/main.py` | **lifespan 启动时从数据库加载 `AUTH_LOGIN_PATH`/`AUTH_CAPTCHA_PATH`**（与 TARGET_URL 同段代码） |
| `backend/app/services/performance_service.py` | **删除 `_DEFAULT_BASE_URL` 模块常量与整个 `_DEFAULT_AUTH_CONFIG` dict**；`_build_client_config()` 改为运行时读取 `settings.TARGET_URL` / `AUTH_LOGIN_PATH` / `AUTH_CAPTCHA_PATH`，不再解析任务 `target_url` |
| `frontend/src/api/system.ts` | `updateSutConfig()` 改为接收 `{ url, login_path?, captcha_path? }` 对象 |
| `frontend/src/views/system/Settings.vue` | 被测系统配置新增「登录接口路径」「验证码接口路径」输入框 |
| `frontend/src/router/index.ts` | **注释掉未开发完成的 document 路由**（引用不存在的文件导致构建失败） |

**新版 `_build_client_config()` 行为**：
```python
@staticmethod
def _build_client_config(task_dict):
    return {
        "target": {
            "base_url": settings.TARGET_URL or "",   # 运行时读取，切换环境无需改任务
            "verify_ssl": False,
            "timeout": 30,
        },
        "accounts": dict(_DEFAULT_ACCOUNTS),
        "auth": {
            "login_path": settings.AUTH_LOGIN_PATH or "/platform/api/auth/login",
            "captcha_path": settings.AUTH_CAPTCHA_PATH or "/platform/api/code",
        },
    }
```

**验证结果**：
- 前端 `npm run build` ✅（修复 document 路由后 38.36s 构建通过，无 TS 错误）
- 服务器容器内验证 `_build_client_config({'target_url': 'http://old.example'})` → `base_url = https://192.168.5.44`（读取系统配置，任务字段不再生效）✅
- 服务器 `GET /api/system/sut-config` 返回 `{"url":"https://192.168.5.44","source":"db","login_path":"/platform/api/auth/login","captcha_path":"/platform/api/code"}` ✅
- 容器 healthy，`/health` 正常 ✅

**⚠️ 已部署到 5.60 服务器**（2026-08-19）：前端 dist + 7 个后端文件已 SCP，容器已重启。

#### 2. 任务级自定义登录用户名/密码 ✅（本次新增）

**用户需求**：系统登录的用户密码也需可配置，**在新建性能测试任务时提供输入**；验证码固定 `csba`。

**实现方式**：`PerformanceTask` 新增两个可选字段 `login_username` / `login_password`。新建/编辑任务时填写了自定义凭据 → 压测执行时使用该凭据登录；留空 → 回退 `_DEFAULT_ACCOUNTS` 默认值（admin@xty / Ubains@13579）。

**本次改动（按计划文件 `radiant-launching-eagle.md` 实施）**：

| 文件 | 变更 |
|------|------|
| `backend/app/models/performance.py` | `PerformanceTask` 新增 `login_username` / `login_password`（String(200), nullable=True），`to_dict()` 返回两字段 |
| `backend/app/schemas/performance.py` | `PerformanceTaskCreate` / `PerformanceTaskUpdate` / `PerformanceTaskResponse` 各新增两个 `Optional[str]` 字段 |
| `backend/app/database.py` | `_ensure_columns()` 追加 `performance_tasks.login_username` / `login_password` 两条迁移（VARCHAR(200) DEFAULT NULL，旧库自动补列） |
| `backend/app/services/performance_service.py` | `_build_client_config()` 中：若 task_dict 含 `login_username`+`login_password`，则以 `account_key` 为键覆盖 accounts 字典凭据，否则保持默认 |
| `frontend/src/types/performance.ts` | `PerformanceTask` / `Create` / `Update` 三个接口新增 `loginUsername?` / `loginPassword?` |
| `frontend/src/views/performance/TaskList.vue` | `TaskForm` + `defaultForm()` 新增两字段；弹窗「账号」选择器下方新增用户名/密码两个输入框（`form.authRequired` 时显示，仅 `el-input` 样式）；`handleSave()` 提交空串转 null；`openEditDialog()` 回填 |

**关键代码（`_build_client_config()` 凭据注入）**：
```python
# 构建 accounts 字典（优先使用自定义凭据）
accounts = dict(_DEFAULT_ACCOUNTS)
if task_dict.get("login_username") and task_dict.get("login_password"):
    key = task_dict.get("account_key", "custom")
    accounts[key] = {
        "username": task_dict["login_username"],
        "password": task_dict["login_password"],
    }
```

**无需改动**：`http_client.py`、`performance_executor.py`（`_get_login_body()` 已从 `http_client_config["accounts"]` 读取凭据，Service 注入后自动生效）、验证码固定 `csba`、`account_key` 选择器保留（兼容旧任务）。

**验证结果**：
- 前端 `npm run build` ✅（vue-tsc 类型检查 + vite 构建，2344 modules，30.64s）
- 后端 `py_compile` ✅（models / schemas / service / database 四个文件）
- **已部署 5.60 服务器**（本次会话与 curl 导入一起部署，2026-08-19）

**⚠️ 使用注意**：登录凭据（密码）以明文存库（`performance_tasks.login_password`），内网平台可接受；后续如做权限系统需考虑脱敏。

#### 3. 从 curl 自动生成压测任务 ✅（已实现 + 已部署 5.60）

详见本会话 「#### 0. 从 curl 自动生成压测任务」节，本小节原为待办，现已完成。

#### 4. 修复 curl 导入 `[object Object]`（P0 已修复 + 已部署 5.60）

**问题**：用户粘贴 Windows CMD 风格 curl 命令（含 `^` 续行符 + `^"` 转义引号）后，弹窗显示 `curl 解析失败: [object Object]`。

**根因（3 层叠加）**：
1. **主因**：`CurlParseRequest` 缺少 `model_config`，前端发送 camelCase `{curlCommand}` → Pydantic 无法映射到 `curl_command` → 422
2. **Windows CMD `^` 语法未处理**：`shlex.split()` 不认识 `^` 续行符和 `^"` 转义引号，导致 `^` 拼进 token（如 `^Authorization`）→ 认证/签名头检测失败
3. **缺失 `-b`/`--cookie` 支持**：`-b "sid=abc"` 被静默忽略

**修复（3 个文件）**：

| 文件 | 变更 |
|------|------|
| `backend/app/schemas/performance.py` | `CurlParseRequest` 补全 `model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)` |
| `backend/app/utils/curl_parser.py` | 新增 `_preprocess_cmd_style()` 还原 `^` 续行/`^"` 转义；新增 `-b`/`--cookie` 分支 |
| `backend/tests/test_curl_parser.py` | 新增 4 个测试用例（15 passed） |

**文档**：`Docs/PRD/性能测试/问题处理/_问题处理_curl导入解析失败[object Object].md` + `_执行计划_修复curl导入解析失败[object Object].md`

**部署**：scp → 清理 pyc → 重启容器 ✅
**验证**：camelCase `{curlCommand}` → 200 ✅；Windows CMD 完整 curl（Authorization + 签名头 + -b Cookie）→ authRequired=true / signRequest=true / cookie 有值 ✅

#### 5. 修复保存任务 422（P0 已修复 + 已部署 5.60）← 本次会话新增

**问题**：curl 导入解析成功后，点击保存按钮提示 `保存失败: [object Object],[object Object],[object Object],[object Object]`。用户质疑"确定更新了吗？怎么还是新增报错？"

**根因**：
1. **后端 schema 非 Optional 字段接收 null**：`PerformanceTaskCreate` 的 `qps_target / ramp_up / step_concurrency / step_duration` 定义为 `int`/`List[int]`（非 Optional）。前端在 `concurrent` 模式下给这些字段发 `null` → Pydantic v2 对非 Optional 字段拒绝 null → 422。
2. **前端错误信息不可读**：`e.response?.data?.detail` 是 Pydantic 错误对象数组，`.toString()` 变成 `[object Object],[object Object],[object Object],[object Object]`（4 个字段各一个校验错误）。
3. **`stepConcurrency` 数组双重包裹**：`[form.stepConcurrency]` 如果 `form.stepConcurrency` 本身已是数组（编辑回填时），会变成 `[[10, 20, 30]]` → 校验失败。

**修复（3 个文件）**：

| 文件 | 变更 |
|------|------|
| `backend/app/schemas/performance.py` | `qps_target/ramp_up/step_concurrency/step_duration` 改为 `Optional` + `default=None` |
| `backend/app/services/performance_service.py` | `create_task()` 中补齐 Optional 压测参数默认值（`100/10/[]/60`） |
| `frontend/src/views/performance/TaskList.vue` | 新增 `extractApiError()` 渲染 Pydantic 错误数组为可读文本；`handleSave()` 修正 `stepConcurrency` 双重包裹判断；`handleCurlParse` 错误处理改用 `extractApiError()` |

**部署（本次会话）**：
- 后端：scp `schemas/performance.py` + `services/performance_service.py` → 清理 pyc → `docker restart plat-auto-test-app` ✅
- 前端：`npm run build`（31.34s，无错误）→ scp `dist/*` 全量 ✅
- 验证：发送 `qpsTarget/rampUp/stepConcurrency/stepDuration` 全为 null → **201 创建成功**，DB 落库值 `100/10/[]/60` ✅

---

### 上一会话（2026-08-17）完成的核心工作

#### 1. 「新建会议压测」任务端到端验证通过 ✅

**任务**：`perf_5a1a3b4e00e04b2f96713f4108580d2b`（新建会议压测）
**目标接口**：`PUT https://192.168.5.44/meetingV3/api/message/book`（JWT + X-RANDOM/X-TIMESTAMP/X-SIGN 签名）
**最终配置**：concurrency=2（用户要求 2 线程快速验证）/ duration=15s / rampUp=0 / 时段 +37H~+38H / duration字段=60

**最终验证结果（干净代码，调试日志已清除）**：

| 指标 | 值 |
|------|-----|
| 总请求数 | 169 |
| 成功数 | 169（HTTP 200） |
| 失败数 | 0 |
| 错误率 | 0.0% |
| 实际 TPS | 10.96 |
| 平均响应时间 | 178.41ms（min 145 / max 231） |
| **message_ids 捕获** | ✅ `['2026-0817013311822', '2026-0817013311824']` |

捕获输出 API 验证：`GET /api/performance/tasks/{id}/outputs` → `{"total":1,"items":[{"key":"message_ids","value":[...]}]}`

#### 2. A0027「时间格式不正确」根因定位与修复 ✅（用户反馈的问题）

**现象**：服务日志持续打印 `body={"success":false,"code":"A0027","message":"时间格式不正确"}`，但 HTTP 状态 200 → 统计全"成功"，捕获为空。

**根因（双层）**：
1. **服务器上的 `template_resolver.py` 是旧版本**：`_resolve_token()` 对所有 token 用 `token.split(":")` 分割，而任务占位符是 `NOW_+30_M`（下划线分隔）→ `kind="NOW_+30_M"` 不匹配 `"NOW"` → 占位符**原样发送**（字面量 `{__NOW_+30_M__}`）→ API 返回 A0027。
   - 正确版本（本地）：REF 用 `:` 分割，其余（NOW/RANDOM_NAME/RANDOM_INT/UUID/INDEX）用 `_` 分割。
2. **stale `.pyc` 字节码缓存**：修复源码后容器仍加载旧字节码（`__pycache__/template_resolver.cpython-310.pyc` mtime 早于源码修改时间），必须删除 pyc + 重启容器。

**修复步骤（5.60 服务器）**：
```bash
# 1. 通过 paramiko base64 部署正确的 template_resolver.py（本地版本）
# 2. 删除字节码缓存
docker exec plat-auto-test-app sh -c "find /app -name '*.pyc' -delete"
# 3. 重启容器
docker restart plat-auto-test-app
# 4. 验证（容器内）
python3 /tmp/test_tr.py   # 输出 NOW: 2026-08-17 12:59:09（带秒）✓
```

**修复后 DBG_RESOLVE 日志确认**：`startTime: '2026-08-17 13:30:02'`（秒精度，占位符已替换）

#### 3. 会议预约 API 业务限制探测（重要经验）

探测脚本（本地 backend/meeting_test25.py 等）验证会议室 `29dcd33aa47797e7d29d79ae6711d597` 的规则：

| 错误码 | 含义 | 触发条件 | 规避方法 |
|--------|------|---------|---------|
| A0027 | 时间格式不正确 | startTime/endTime 非秒精度或占位符未解析 | 修复 resolver（已完成） |
| A0023 | 该时间段已有会议 | 目标时段与已有会议重叠 | 选无人占用时段（如 +37H 凌晨）；**注意探测脚本自己创建的会议也会占用时段** |
| A0026 | 超过会议室可预订的最大时长 | body 中 `duration=120`（该会议室上限 90 分钟） | `duration` ≤ 90 且与 start/end 跨度一致 |
| B0001 | failure | `cnum=""`（不指定会议室） | 必须携带有效会议室 ID |

**预约接口 body 必需字段**：JSON **数组** `[{...}]`、`convergentMediaTypes:"1"`、`companyNumber:"CN-WQF-UBAINS"`（JWT 声明一致）、`uid:8`（数字）、`configValueId:2`（数字）、有效 `cnum`、`participantList:[]`、秒精度时间。

#### 4. 压测任务设计注意事项（同一时段冲突问题）

**发现**：concurrent 模式下所有并发请求解析出**相同的 startTime/endTime**（`{__NOW_+37_H__}` 对所有请求解析结果相同）→ 第一批请求订走时段后，后续请求全部 A0023。

**当前状态**：2 并发时前 2 个请求成功（message_ids 捕获 2 个），其余 A0023（HTTP 200，统计不报错）。

**待优化方案**：给模板解析器增加按请求序号偏移时间的语法，例如 `{__NOW_+37_H_INDEX__}`（每请求 +1 分钟错开时段），需要在 `template_resolver.py` 的 `_resolve_token` 中处理 `parts[3] == "INDEX"` 分支（尚未实现）。

### 响应捕获（capture_rules）链路（本次已全链路验证）

```
任务 capture_rules（JSON）
  → executor._capture_response(rules, body_text, status)   # JSONPath $.result[*].messageId 提取
  → self._captured.setdefault(key, []).append(value)         # _capture_max=10000 上限
  → result["captured"]（execute() finally 块）
  → service._save_outputs(task_id, captured)                 # upsert 到 performance_task_outputs 表
  → GET /api/performance/tasks/{id}/outputs                  # 前端/下游任务读取
```

- 捕获仅在 HTTP 2xx 时执行；`_save_outputs` 空捕获时 early return（不写库）
- `PerformanceTaskOutput` 表（MySQL `performance_task_outputs`）按 task_id+key upsert
- 新增文件：`models/performance_output.py` / `routers/performance_output.py` / `schemas/performance_output.py`
- service 侧 `_save_outputs` 在 `run_task` 的 finally 中调用（此前服务器旧版 service 缺此函数，已部署本地新版修复）

### 服务器（192.168.5.60）部署情况

- 容器：`plat-auto-test-app`（volume mount `/data/third_party/plat-auto-test/backend:/app`，内部端口 80）
- **2026-08-17 部署**（paramiko + base64 方式，scp 不可用）：
  - `app/executors/template_resolver.py` — 正确的 `_` 分割版本（修复 A0027）
  - `app/services/performance_service.py` — 含 `_save_outputs` 的新版
  - `app/executors/performance_executor.py` — 已含捕获逻辑，临时加过 DBG_RESOLVE/DBG_CAPTURE 调试日志，**现已清除**（验证 grep DBG = 0）
- **2026-08-19 部署（第1批）**（scp 方式，已恢复正常）：
  - `app/config.py` — 新增 `AUTH_LOGIN_PATH` / `AUTH_CAPTCHA_PATH`
  - `app/database.py` — 旧库迁移 ADD COLUMN login_path / captcha_path
  - `app/main.py` — lifespan 加载认证路径
  - `app/models/dingtalk_config.py` — 新增 login_path / captcha_path 字段
  - `app/schemas/dingtalk_config.py` — SutConfig 新增字段
  - `app/routers/system.py` — GET/PUT 读写认证路径
  - `app/services/performance_service.py` — 删除 `_DEFAULT_BASE_URL`，运行时读取系统配置
  - 前端 `dist/` — 重构 Settings.vue（新增认证路径输入框）
- **2026-08-19 本地改动（第2批+第3批，已部署服务器）**：
  - 第2批：性能测试任务级自定义登录凭据（model + schema + service + 前端 TaskList 表单）
  - 第3批：从 curl 自动生成压测任务（curl_parser.py + 路由 + schema + 测试 + 前端导入交互）
  - 部署详情：scp 后端 6 个文件 + 前端 dist 全量 → 清理 *.pyc → 重启容器 ✅
- **部署后处理**：删除全部 `*.pyc` + 重启容器（否则旧字节码仍生效）
- 调试日志补丁模式（本次使用后已移除）：
  - `[DBG_RESOLVE]` 在 `_resolve_body` 的 resolve() 调用后打印解析结果
  - `[DBG_CAPTURE]` 在 `_capture_response` 开头打印规则/状态/响应体前 200 字符

### 本地会话遗留状态

- 本地 backend 以 `python -m uvicorn app.main:app --port 8001`（无 --reload）方式运行，日志 `/tmp/backend_8001.log`
- 本地验证脚本：`backend/meeting_test22.py` ~ `meeting_test25.py`（探测 API 最小必需字段/时段/时长限制，可复用）
- 本地任务 `perf_f27debe671d84b4fb3908af5beac67ed`（同名任务）尚未做完整验证（服务器版已验证通过）

**后端代码行数**：3037 行（executor 1138 + service 854 + router 329 + model 337 + schema 379）
**前端代码行数**：2746 行（index 45 + TaskList 917 + MonitorPanel 617 + ReportPanel 626 + api 160 + types 381）

---

## 多窗口并行开发注意

如需同时开多个 Claude Code 窗口开发不同子菜单功能，请先阅读 `Docs/多窗口并行开发指南.md`。

性能测试模块的端口与文件：
- 后端：8001（默认）/ 8002（当前可用）
- 前端：3000
- 数据库：`data/test_platform.db`（共享同一个 SQLite）
- Git 分支：`platform-auto-test`

---

## 已完成事项

### 后端（Phase 1-3）

#### 1. 数据模型 (`backend/app/models/performance.py`)

| 模型 | 表名 | 说明 |
|------|------|------|
| `PerformanceTask` | `performance_tasks` | 任务主表（名称/URL/方法/模式/并发/时长/状态/认证/断言） |
| `PerformanceSnapshot` | `performance_snapshots` | 每秒快照（TPS/响应时间P50/P90/P99/状态码分布） |
| `PerformanceTaskResult` | `performance_task_results` | 最终结果（汇总指标 + JSON 详情） |

**关键字段**：
- `PerformanceTask.mode` → `concurrent` / `qps` / `step`
- `PerformanceTask.status` → `pending` / `running` / `completed` / `failed` / `cancelled`
- `PerformanceTask.account_key` → `superadmin` / `admin` / `user`
- `PerformanceSnapshot` 使用 camelCase 别名（`p50_response_time` → `p50` / `p90` / `p99`）

#### 2. Pydantic Schema (`backend/app/schemas/performance.py`)

- 全部 schema 使用 `populate_by_name=True, alias_generator=to_camel` 实现 camelCase 兼容
- 断言规则：`AssertionRule`（status_code / response_time / response_body + JSON Path）
- 请求/响应结构：`PerformanceTaskCreate` / `PerformanceTaskUpdate` / `PerformanceTaskResponse` / `PerformanceTaskListItem` / `PerformanceTaskListResponse`
- 执行相关：`PerformanceRunRequest` / `PerformanceRunResponse`
- 数据：`PerformanceSnapshotResponse` / `PerformanceSnapshotListResponse`
- 报告：`PerformanceReportResponse`（含 `summary` / `metrics` / `snapshots`）

#### 3. 压测执行引擎 (`backend/app/executors/performance_executor.py`)

**核心架构**：
- `MetricsCollector` — 线程安全指标采集器（带锁的 period 累积 + 全局累加）
  - `record()` 记录单次请求结果（响应时间 + 状态码 + 错误）
  - `snapshot()` 生成每秒快照（TPS / P50/P90/P99 / 状态码分布）
  - `summary()` 汇总最终指标
  - `snapshot_callback` 实时推送（WebSocket 每 1 秒回调）
- `ResultValidator` — 三种断言类型（status_code / response_time / response_body JSON Path）
- `PerformanceExecutor` — 主执行器
  - `execute()` 入口：Token 获取 → 并发执行 → 指标采集 → 断言验证
  - 使用 `HttpClient`（`app.executors.http_client`）获取 Token
  - 三种模式：
    - **concurrent**：固定并发数，持续 duration 秒
    - **qps**：固定 QPS 限流，持续 duration 秒
    - **step**：阶梯并发，每 step_duration 秒递增 step_concurrency
  - 通过 `asyncio.Event` 实现优雅停止
  - 预热阶段（ramp_up）逐步增加并发

**运行模式（Windows 兼容）**：
- 引擎运行在独立线程中
- `asyncio.set_event_loop(None)` → `new_event_loop()` → `run_until_complete(executor.execute())`
- 复用 `playwright_executor.py` 已验证的 Windows 兼容模式

#### 4. Service 层 (`backend/app/services/performance_service.py`)

**CRUD 操作**：
- `create_task()` / `get_task()` / `list_tasks()` / `update_task()` / `delete_task()`

**执行控制**：
- `run_task()` — 启动执行（`_run_sync` 线程池模式）
- `stop_task()` — 发送停止信号（`asyncio.Event.set()`）
- `_run_sync()` — 线程内执行完整流程（登录 → 执行 → 保存快照/结果 → 更新状态）
- `_running_executors: Dict[str, PerformanceExecutor]` 类缓存跟踪运行中任务

**数据查询**：
- `get_snapshots()` / `get_report()` / `get_task_results()`

**认证配置**：
```python
_DEFAULT_BASE_URL = "https://192.168.5.44"
_DEFAULT_ACCOUNTS = {
    "superadmin": {"username": "admin@xty", "password": "Ubains@13579"},
    "admin": {"username": "admin@xty", "password": "Ubains@13579"},
    "user": {"username": "admin@xty", "password": "Ubains@13579"},
}
```
- 验证码固定 `csba`
- 认证路径：`/platform/api/auth/login`
- 验证码路径：`/platform/api/code`
- Token 通过 `HttpClient.login(account_key)` 获取 → 注入请求头

#### 5. API 路由 (`backend/app/routers/performance.py`)

前缀 `/api/performance`，注册于 `main.py` 第 216-219 行，tags `["性能测试"]`

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/performance/tasks` | 任务列表（分页 + 状态过滤） |
| POST | `/api/performance/tasks` | 创建任务 |
| GET | `/api/performance/tasks/{task_id}` | 获取单个任务 |
| PUT | `/api/performance/tasks/{task_id}` | 更新任务 |
| DELETE | `/api/performance/tasks/{task_id}` | 删除任务 |
| POST | `/api/performance/tasks/{task_id}/run` | 执行任务 |
| POST | `/api/performance/tasks/{task_id}/stop` | 停止任务 |
| GET | `/api/performance/tasks/{task_id}/snapshots` | 获取快照列表 |
| GET | `/api/performance/tasks/{task_id}/report` | 获取报告 |
| GET | `/api/performance/tasks/{task_id}/results` | 获取执行结果 |
| WS | `/api/performance/ws/{task_id}` | 实时监控 WebSocket |

**WebSocket**：
- 端点：`/api/performance/ws/{task_id}`
- 分组名：`perf_{task_id}`
- 复用 `app/websocket/manager.py` 的 `ConnectionManager`
- 推送格式：执行器每秒回调的 JSON 快照

#### 6. 后端注册 (`backend/app/main.py`)

```python
from app.routers import performance as performance_router
# ...
app.include_router(performance_router.router, prefix="/api/performance", tags=["性能测试"])
```

### 前端（Phase 4）

#### 1. 路由 (`frontend/src/router/index.ts`)

```
/performance → Performance index.vue (懒加载)
/cases/performance → redirect /performance
/execution/performance → redirect /performance
/reports/performance → redirect /performance
```

#### 2. 菜单 (`frontend/src/App.vue`)

三个侧边栏菜单项指向 `/performance`：
- 用例管理 → 性能测试
- 执行中心 → 性能测试
- 报告中心 → 性能测试

#### 3. 主容器 (`frontend/src/views/performance/index.vue`)

三个 el-tabs：任务管理 / 执行监控 / 报告查看
- 状态管理：`monitorTaskId`、`reportTaskId`
- Tab 切换时联动（点击"监控"→ 切换到监控 tab 并传入 taskId）
- 通过 `defineExpose` 暴露 `loadReport()` / `refresh()`

#### 4. 任务列表 (`frontend/src/views/performance/TaskList.vue`)

- el-table 展示：名称/URL/方法/模式/并发/时长/状态/TPS/错误率/创建时间
- 操作列：执行/停止/监控/报告/编辑/删除
- 状态过滤下拉框 + 分页
- 创建/编辑弹窗（el-dialog）：名称/URL/方法/模式/并发/时长/预热/QPS目标/阶梯并发/请求头/请求体/认证开关/账号选择/描述
- 表单校验：名称/URL/方法/模式 必填
- `defineEmits<{ viewMonitor: [taskId: string]; viewReport: [taskId: string] }>()`

#### 5. 实时监控 (`frontend/src/views/performance/MonitorPanel.vue`)

- WebSocket 实时连接（`createPerfMonitorStream`）
- 三张 ECharts 图表：TPS 趋势（折线图 + 面积渐变）/ 响应时间趋势（平均/P50/P90/P99 四条线）/ 状态码分布（环形饼图）
- 顶部 4 个指标卡片：当前 TPS / 平均响应时间 / 总请求数 / 错误率
- 连接状态指示器（绿/黄/灰圆点）
- 数据缓冲区上限 300 点，先进先出
- 自动断开处理 + 错误提示

#### 6. 报告查看 (`frontend/src/views/performance/ReportPanel.vue`)

- 执行摘要（el-descriptions：任务名/URL/方法/模式/并发数/账号/开始/结束/实际时长）
- 核心指标卡片（8 个）：总请求数/成功数/失败数/错误率/TPS/平均/最小/最大响应时间
- 响应时间分位数表（P50/P90/P99）
- 状态码分布表（2xx/3xx/4xx/5xx + 数量 + 占比）
- TPS 趋势图 + 响应时间趋势图（ECharts）
- 导出 JSON 功能

#### 7. API 封装 (`frontend/src/api/performance.ts`)

```typescript
BASE = '/api/performance'
listTasks(params) → GET /tasks
getTask(id) → GET /tasks/{id}
createTask(data) → POST /tasks
updateTask(id, data) → PUT /tasks/{id}
deleteTask(id) → DELETE /tasks/{id}
runTask(id) → POST /tasks/{id}/run
stopTask(id) → POST /tasks/{id}/stop
getSnapshots(id) → GET /tasks/{id}/snapshots
getReport(id) → GET /tasks/{id}/report
getTaskResults(id) → GET /tasks/{id}/results
createPerfMonitorStream(id, onSnapshot, onComplete, onError, onClose) → WebSocket
```

#### 8. 类型定义 (`frontend/src/types/performance.ts`)

所有接口类型（camelCase 风格）：
- `PerformanceTask` / `PerformanceTaskListItem` / `PerformanceTaskCreate`
- `PerformanceSnapshot` / `PerformanceSnapshotListResponse`
- `PerformanceReportResponse` / `PerformanceReportSummary` / `PerformanceReportMetrics`
- `AssertionRule`
- `PerformanceRunRequest` / `PerformanceRunResponse`

#### 9. 前端构建验证

```bash
cd frontend && npm run build
# ✓ 构建成功 (34.48s), 无 TypeScript 错误
```

---

## 错误修复记录

### 1. 后端 404 / OpenAPI 不显示性能测试路由

**现象**：访问 `http://localhost:8001/docs` 看不到性能测试 API，`/api/performance/tasks` 返回 404

**根因**：运行在 8001 端口的 uvicorn 是**旧进程**（PID 10912，启动于 2026-08-11 15:50），早于性能模块代码的编写时间。代码中的 `performance` 路由注册在 `main.py` 中，但旧进程加载的是旧代码。

**验证**：通过 Python 直接 import 确认路由注册正确：
```python
from app.main import app
routes = [r.path for r in app.routes if "/api/performance" in str(r.path)]
# 输出: 11 条路由，全部正确
```

**修复**：尝试 kill 旧进程失败（access denied / not found），改用端口 8002 启动新后端。已验证 8002 正常工作。

### 2. 无法 kill 旧 uvicorn 进程

**尝试过的方法**（全部失败）：
- `taskkill /F /PID 10912` → 访问被拒绝
- `taskkill /F /PID 27100` → 未找到进程
- PowerShell `Stop-Process` → not found
- `kill -9` → no such process
- `os.kill(pid, 9)` → WinError 5 拒绝访问
- `wmic` → not found in bash

**原因**：进程由不同上下文的用户启动（可能是通过 Windows 服务或 Administrator 启动的），当前 bash 会话无权限操作。

**当前解决**：使用端口 8002 启动新后端：
```bash
cd backend && uvicorn app.main:app --reload --port 8002
```

### 3. 前端 TS 类型错误（本会话前已修复）

- TS6133: `emit` 未使用 → 移除
- TS2339: `tasks`/`total` 找不到 → 修复类型定义
- TS2345: `string | null` 不能赋值给 `string` → 修复 `formatTime` 参数类型

### 4. A0027 时间格式不正确（2026-08-17 已修复）

**现象**：会议预约压测全部返回 `{"success":false,"code":"A0027","message":"时间格式不正确"}`，HTTP 200 导致统计显示成功但捕获为空。

**根因**：服务器 `template_resolver.py` 旧版 `_resolve_token()` 用 `token.split(":")` 分割所有 token；任务占位符 `NOW_+30_M` 无冒号 → `kind="NOW_+30_M"` 匹配失败 → 占位符原样发送。本地版本已用 `_` 分割（仅 REF 用 `:`），部署时曾发现更早版本 `_now_str` 为 `%H:%M`（无秒）——两层问题叠加。

**修复**：部署本地正确版 `template_resolver.py` 到 5.60 → 删 `*.pyc` → 重启容器。验证 `_now_str()` → `2026-08-17 12:59:09`。

**踩坑**：
- 源码改了但容器仍旧行为 → stale `__pycache__` 字节码（mtime 早于源码），必须删 pyc + 重启
- 部署方式：paramiko exec + base64（scp 因 UTF-8 内容失败；sshpass 不在 PATH）
- 5.60 容器内部端口是 **80**（不是 8001），`docker port` 查映射

### 5. A0026 超过会议室可预订的最大时长（2026-08-17 定位）

**现象**：任务 body `duration: 120` 时全部返回 A0026。

**根因**：会议室 `29dcd33aa47797e7d29d79ae6711d597` 单次预订上限 **90 分钟**。

**修复**：`duration` 改为 60（与 start/end 跨度一致）。

### 6. A0023 该时间段已有会议（2026-08-17 规避）

**现象**：任务时段与已有会议重叠时全部 A0023。

**根因**：a) 时段本身有真实会议；b) **探测脚本自己创建的会议也占用时段**（探测 + 验证共用同一会议室时极易自我冲突）。

**修复**：改用 +37H/+38H（次日次日凌晨）无占用时段。教训：探测可用性会**污染**日程，探测与正式压测要用不同时段/会议室。

---

## 当前运行状态

### 生产服务器 192.168.5.60（2026-08-20 23:10）
- 容器 `plat-auto-test-app`：healthy，内部端口 80（宿主机 80）
- **当前部署版本**：含 curl 导入 + 自定义凭据 + 读取系统配置 URL + 项目管理与批量执行 + 合并报告 + **指标体系增强（10项新指标）**（2026-08-20 夜间全量部署）
- **已部署**：后端 5 文件 + 前端 dist（指标体系增强指标卡片 + 3 新图表 + 16 指标分位数表/错误分类表/延迟分解图/带宽趋势图）
- **已部署**：合并报告 API 5 端点全部验证通过
- **已部署**：指标体系增强 DB 迁移（`performance_tasks` 17 列 + `performance_snapshots` 7 列全部创建 ✅）
- 任务 `perf_5a1a3b4e00e04b2f96713f4108580d2b` 验证通过（169 请求 / 0 失败 / TPS 10.96 / message_ids 捕获 2 个）
- MySQL 表 `performance_task_outputs` 已存在且有数据

### 本地（Windows 开发机）
- 后端：`python -m uvicorn app.main:app --port 8001`（当前无 --reload 运行中）
- 前端：`http://localhost:3000`
- 本地数据库 `data/test_platform.db` 含历史任务
- **最新改动**：指标体系增强（10 项新指标，25 个单元测试全部通过 ✅）+ **项目管理与批量执行**（前端 3 文件 + 路由 + 菜单，构建 30.52s 通过 ✅）+ **批量执行合并报告**（后端聚合 + 前端 ProjectReport + 5 条 API，npm run build 通过 ✅）+ **指标体系增强已部署 5.60** ✅
- **前端构建**：`npm run build` 通过 ✅

---

## 未提交的代码

性能测试模块代码尚未 commit。影响文件（含本次会话新增）：

**本次会话（批量执行合并报告，2026-08-20 夜间）新增/修改：**

```
# 后端
backend/app/models/performance.py                 (MODIFIED: +PerformanceBatchExecution +PerformanceProjectReport)
backend/app/schemas/performance.py                 (MODIFIED: +BatchRunResponse.report_id/TaskSummaryItem/ProjectReportSummary/ProjectReportResponse/ProjectReportListResponse)
backend/app/services/performance_service.py        (MODIFIED: +聚合算法/weighted_percentile/get_project_report/get_batch_report/list_project_reports)
backend/app/routers/performance.py                 (MODIFIED: +3条报告路由 +batch-run/run-all补report_id)
backend/app/database.py                            (MODIFIED: +建表迁移)

# 前端
frontend/src/views/performance/ProjectReport.vue    (NEW, 466行 - 合并报告页)
frontend/src/views/performance/ProjectDetail.vue    (MODIFIED: +查看合并报告/历史报告按钮 +lastReportId)
frontend/src/router/index.ts                        (MODIFIED: +project-report路由)
frontend/src/types/performance.ts                   (MODIFIED: +合并报告类型)
frontend/src/api/performance.ts                     (MODIFIED: +getProjectReport/getBatchReport/getProjectReports)
```

**上一会话（项目管理与批量执行）新增/修改：**

```
# 后端
backend/app/services/performance_service.py       (MODIFIED: +项目CRUD/批量执行/run-all)
backend/app/routers/performance.py                (MODIFIED: +项目7条路由)
backend/app/schemas/performance.py                (MODIFIED: +项目/批量schema)
backend/app/models/performance.py                 (MODIFIED: +PerformanceProject模型)
backend/app/database.py                           (MODIFIED: +建表迁移)

# 前端
frontend/src/views/performance/Projects.vue        (NEW, 293行)
frontend/src/views/performance/ProjectDetail.vue   (NEW, 439行)
frontend/src/views/performance/TaskList.vue         (MODIFIED: +项目适配)
frontend/src/router/index.ts                       (MODIFIED: +projects路由)
frontend/src/App.vue                               (MODIFIED: +菜单项)
```

**上一会话（指标体系增强）新增/修改：**

```
backend/app/executors/performance_executor.py       (MODIFIED: MetricsCollector增强)
backend/app/services/performance_service.py          (MODIFIED)
backend/app/database.py                              (MODIFIED)
backend/app/models/performance.py                    (MODIFIED: +新指标字段)
backend/app/schemas/performance.py                   (MODIFIED: +新指标字段)
backend/tests/test_performance_executor.py            (NEW, 25 tests)
frontend/src/views/performance/MonitorPanel.vue      (MODIFIED: +3新图表)
frontend/src/views/performance/ReportPanel.vue       (MODIFIED: +16指标卡片)
frontend/src/types/performance.ts                    (MODIFIED: +新指标类型)
```

---

## 下一步任务

### 批量执行合并报告 — 部署 ✅ → WS 进度补全（P1，待用户确认）

合并报告功能**已部署 5.60 并验证通过**。剩余缺口：
- **WebSocket 批量进度仍无订阅端点**：后端 `batch_run_tasks`/`run_project_all` 广播 `perf_batch_progress` 到 `perf_batch` 分组，但无 `/ws/batch/{batch_id}` 订阅端点 → ProjectDetail.vue 的实时进度条收不到推送（success/fail 计数为 0）。当前体验：等待 POST 阻塞响应期间只有 loading，进度条不更新。
- **方案**：后端新增 WS 路由 `GET /api/performance/ws/batch/{batch_id}`（accept 后 `manager.connect(websocket, "perf_batch")` 循环 receive 心跳），前端 `connectBatchWs()` 改为连接 `/api/performance/ws/batch/{batchId}`（batchId 从响应中获取，需先等 `batch_run_tasks` 返回 batchId 再连）。**需要用户确认后实施。**
- 合并报告页可考虑：失败任务红标醒目化、报告对比（多次执行并列）、导出 PDF

### 合并报告 5.60 部署回归（建议下次会话首步）

已部署 + 5 端点实测通过，但**前端页面流程**（进入项目详情 → 批量执行 → 查看合并报告 → 历史切换）建议在浏览器端完整走一遍回归。

### 指标体系增强部署（已部署 5.60 ✅）

指标体系增强（10 项新指标）已于 2026-08-20 23:10 部署到 5.60 服务器。详见上方「2026-08-20 23:30 — 指标体系增强部署」节。

### 集成验证（待完成）

- 运行一次短压测（concurrency=1, duration=5s），验证:
  - 报告 API 返回所有新指标字段 ✅
  - WebSocket 快照含新字段（std_dev/latency_avg/connect_time_avg/sentBytes/receivedBytes/peakTps）
  - 前端报告面板显示 16 个指标卡片 + 新图表
  - 错误分类（可手动构造 4xx/5xx 场景验证）

### P0 - 按请求序号错开时间段（并发压测预约类接口的关键）

以下环节已在 5.60 服务器 + 真实会议预约 API 上验证通过：
- 创建/更新任务（PUT 更新 concurrency/duration/body）✅
- 执行任务（`{"message":"任务已开始执行"}` → completed）✅
- 捕获输出落库与查询（`/outputs` 返回 message_ids）✅
- 真实 API 认证（JWT + 签名）+ 模板占位符解析 ✅

仍待验证：
- WebSocket `/api/performance/ws/{task_id}` 实时快照推送
- 前端任务列表/监控图表/报告面板对新数据（outputs）的展示
- 本地任务 `perf_f27debe671d84b4fb3908af5beac67ed` 的完整回归

### P0 - 按请求序号错开时间段（并发压测预约类接口的关键）

**问题**：concurrent 模式下所有请求解析出相同 startTime → 第一批订走时段，后续全部 A0023。

**方案**：`template_resolver.py` 的 `_resolve_token()` 增加 `INDEX` 后缀支持，如 `{__NOW_+37_H_INDEX__}`（token parts = `["NOW","+37","H","INDEX"]`），解析时在基准时间上再加 `request_index` 分钟。实现位置：`_resolve_token` 的 NOW 分支处理 `len(parts) > 3 and parts[3] == "INDEX"`，需要新增 `_now_with_offset` 的带 index 变体。实现后任务 body 改用该语法即可全并发成功。

### P1 - 修改/取消会议压测任务

- 修改会议：`PUT /meetingV3/api/message/{...}`（需先拿 book 的 messageId → 已可通过 REF 占位符 `{__REF:task_id:message_ids__}` 引用）
- 取消会议：需探测取消接口路径与 body 格式（`meeting_test*.py` 系列脚本可扩展）

### P2 - 后续优化

- 断言规则验证（`ResultValidator` 执行中启用；注意当前 `success:false` + HTTP 200 会被 status_code 断言判成功，建议增加 response_body/JSONPath 断言 `$.success == true`）
- 更详细的错误处理（Token 过期重试、网络错误分类）
- 报告对比功能（多次执行结果对比）
- 批量导出报告
- 压力测试数据清理（定期清理旧快照；探测脚本创建的真实会议也需清理策略）
- schemas/performance.py 服务器版同步：本地 `body_template` 已放宽为 `Optional[Any]`，服务器 schema 若仍为 `Optional[Dict]` 需同步部署

### P3 - 从 curl 自动生成压测任务 ✅（已实现 + 已部署 5.60）

已实现并部署，详见本会话「#### 0. 从 curl 自动生成压测任务」节。

**2026-08-19 问题修复（curl 导入 `[object Object]`，P0）：**

1. **根因**：`CurlParseRequest` 缺少 `model_config` alias 配置，前端发送 camelCase `{curlCommand}` → 后端 422 → 前端 `Array.toString()` 显示 `[object Object]`
2. **已修复（本次会话，已部署 5.60）**：
   - `schemas/performance.py`：`CurlParseRequest` 补全 `model_config`
   - `utils/curl_parser.py`：新增 `_preprocess_cmd_style()` 兼容 Windows CMD `^` 续行/`^"` 转义
   - `utils/curl_parser.py`：新增 `-b`/`--cookie` 参数支持
   - `tests/test_curl_parser.py`：新增 4 个测试用例（15 passed）
3. **文档**：`Docs/PRD/性能测试/问题处理/_问题处理_curl导入解析失败[object Object].md` + `_执行计划_修复curl导入解析失败[object Object].md`
4. **待办**：提交代码；用用户真实 CMD curl 复验完整导入（已用 CMD 风格命令实测通过）

**2026-08-19 部署结果**：schemas/performance.py + utils/curl_parser.py 已 scp → 清理 *.pyc → 重启容器（healthy）✅
- 5.60 实测 camelCase `{curlCommand}` → 200 ✅
- 5.60 实测完整 Windows CMD curl（^ 续行 + Authorization + 签名头 + -b Cookie）→ authRequired=true / signRequest=true / cookie 有值 ✅

### P3.5 - 修复 curl 导入后保存任务 422 [object Object]（P0 已修复 + 已部署 5.60）

**问题**：curl 导入解析成功 → 点保存 → `保存失败: [object Object],[object Object],[object Object],[object Object]`（用户质疑"确定更新了吗？怎么还是新增报错？"）

**根因（3 层独立问题）**：
1. **schema 非 Optional 字段拒绝 null**：`PerformanceTaskCreate` 的 `qps_target/ramp_up/step_concurrency/step_duration` 为 `int`/`List[int]`，前端在非 step/qps 模式发 `null` → Pydantic v2 422
2. **错误信息不可读**：`e.response.data.detail` 是 Pydantic 错误对象数组，`.toString()` → 4 个 `[object Object]`
3. **stepConcurrency 数组双重包裹**：编辑回填时 `[form.stepConcurrency]` 套已有数组 → `[[10,20,30]]` 校验失败

**修复（3 文件）**：
| 文件 | 变更 |
|------|------|
| `backend/app/schemas/performance.py` | 4 字段改 `Optional` + `default=None`（`qps_target/ramp_up/step_concurrency/step_duration`） |
| `backend/app/services/performance_service.py` | `create_task()` 补齐 Optional 压测参数默认值（`100/10/[]/60`） |
| `frontend/src/views/performance/TaskList.vue` | 新增 `extractApiError()` 渲染 `字段: msg` 可读错误；`handleSave()` 修复 `stepConcurrency` 双重包裹；`handleCurlParse()` 错误处理走 `extractApiError()` |

**部署（2026-08-19 18:00 左右）**：scp schemas/performance.py + performance_service.py → 清理 *.pyc → 重启容器 healthy ✅；`npm run build`（31.34s）→ scp 前端 dist 全量 ✅
**验证**：5.60 实测带 null 的 POST /tasks → **201 创建成功**，DB 落库 `100/10/[]/60` ✅（测试任务已删除）

**⚠️ 用户侧**：浏览器需 **Ctrl+F5 强刷** 加载新前端（dist 静态文件无需重启容器，但浏览器可能缓存旧 JS）。

### P4 - 任务级自定义凭据的部署与验证 ✅（已部署）

- 第 2 批本地改动（自定义登录凭据）已部署 5.60 服务器（与 curl 导入一起，2026-08-19）
- **部署后验证（本次会话）**：新建任务填自定义账号/密码 → 执行 → 日志确认用自定义凭据登录；留空 → 回退默认（验证方式，可复用）

---

*本文档由 Claude Code 自动生成，用于多会话协作开发时的上下文传递。*