# 执行计划：修复会议模板任务 body 字符串存储导致 4XX 响应

> 生成时间：2026-08-21
> 关联文档：`_问题处理_会议模板任务body字符串存储导致4XX响应.md`
> 状态：📋 待执行

---

## 问题概述

通过「会议模板」接口预设（ApiPreset）创建的性能测试任务，执行时**全部请求返回 4XX**。

**根因**：前端 `TaskList.vue` 的 `handleSave()` 第 769 行 `body: form.body || null` **未做 `JSON.parse()`**（对比 `ApiPresetDialog.vue` 第 295 行正确解析），导致 body 作为**字符串**（且含 Windows CMD `^` 转义残留）存入 MySQL JSON 列。执行时 `_resolve_body()` 返回字符串，`json=str` 被 aiohttp **双重序列化**，API 收到字符串字面量无法反序列化 → 4XX。

---

## 执行步骤

### Step 1: 前端修复 — TaskList.vue 补 JSON.parse

**文件**：`frontend/src/views/performance/TaskList.vue` → `handleSave()`（约第 769 行）

**改动**：

```typescript
// 修复前
body: form.body || null,

// 修复后：与 bodyTemplate 保持一致，JSON.stringify 存储的对象需反解析
body: form.body ? JSON.parse(form.body) : null,
```

**验收标准**：
- 在 TaskList.vue 新建/编辑任务，body 填写合法 JSON 数组/对象
- 点击保存，`POST /api/performance/tasks` 请求体中 `body` 为**对象/数组**（而非字符串）
- 执行任务返回 2XX，不再 4XX

### Step 2: 后端防御修复 — 执行器字符串 body 反序列化

**文件**：`backend/app/executors/performance_executor.py` → `_send_request()`（约第 1018 行）

**改动**：在 `session.request(json=...)` 之前，对字符串 body 做防御性 `json.loads`：

```python
# 防御：字符串 body 反序列化回 JSON 对象（防止前端直传字符串的历史脏数据）
if isinstance(json_data, str):
    try:
        json_data = json.loads(json_data)
    except (json.JSONDecodeError, TypeError):
        logger.warning(f"任务 {task.id} body 不是合法 JSON 字符串，原样发送: {str(json_data)[:80]}")
        pass
```

**验收标准**：
- 对已存在的脏数据任务（body 为字符串）执行时，先 `json.loads` 再发送，请求体为 JSON 对象
- 非 JSON 字符串（如纯文本）保持原样发送，不抛异常

### Step 3: 数据库脏数据修复

**执行命令**（5.60 MySQL 容器内）：

```bash
docker exec -it plat-auto-test-mysql mysql -uroot -p 平台数据库名 -e "
UPDATE performance_tasks
SET body = JSON_REMOVE(body, '$')
WHERE JSON_TYPE(body) = 'STRING'
  AND JSON_UNQUOTE(body) LIKE '^[%'
  AND id = 'perf_ecbd92aae3b845ab9bd651ca49ca111e';
"
```

> 说明：`JSON_REMOVE(body, '$')` 会去掉字符串外层引号，将 JSON 字符串转回 JSON 对象。执行前先备份或先 SELECT 验证。

**验收标准**：
- `SELECT JSON_TYPE(body), body FROM performance_tasks WHERE id='perf_ecbd92...'` 显示 `JSON_TYPE` 为 `ARRAY`/`OBJECT`（不再 STRING）
- `^` 字符残留需在 Step 4 的代码层清洗或手动更新

### Step 4: 前端构建

```bash
cd frontend && npm run build
```

**验收标准**：构建成功，无 TypeScript 类型错误。

### Step 5: 部署到 5.60

```bash
scp backend/app/executors/performance_executor.py ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/app/executors/
scp -r frontend/dist ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```

**验收标准**：
- 容器重启成功（`Container plat-auto-test-app Started`）
- `grep -n 'json.loads' backend/app/executors/performance_executor.py` 确认防御代码已部署
- `grep -n 'JSON.parse(form.body)' frontend/dist/assets/index-*.js` 确认前端已部署

### Step 6: 端到端验证

1. 浏览器 **Ctrl+F5** 强刷前端
2. 项目管理 → 新建任务 → body 填写 JSON 数组（模拟会议模板 insertByFkeyId）
3. 保存并执行
4. 监控页确认：**status_2xx > 0，status_4xx = 0**
5. 对问题任务 `perf_ecbd92...` 重新执行，确认 4XX 消失

---

## 改动清单

| 文件 | 类型 | 说明 |
|------|------|------|
| `frontend/src/views/performance/TaskList.vue` | **MODIFY** | `handleSave()` body 加 `JSON.parse()`（1 行） |
| `backend/app/executors/performance_executor.py` | **MODIFY** | `_send_request()` 字符串 body 防御性 `json.loads`（+6 行） |
| `performance_tasks` 表 | **DATA** | 清洗问题任务 body 脏数据（`JSON_REMOVE`） |
| `Docs/.../_问题处理_会议模板任务body字符串存储导致4XX响应.md` | **NEW** | 问题处理文档 |
| `Docs/.../_执行计划_修复会议模板任务body字符串存储导致4XX响应.md` | **NEW** | 本次执行计划 |

**无需改动**：
- 后端 API 路由 / schema（`body: Optional[Any]` 本身无问题）
- `ApiPresetDialog.vue`（已有正确 JSON.parse）
- 数据库表结构

---

## 部署后回归

| 验证项 | 预期 |
|--------|------|
| TaskList.vue 新建任务（有 body） | 保存成功，body 为 JSON 对象，执行 2XX |
| TaskList.vue 新建任务（bodyTemplate） | 不受影响，bodyTemplate 正常 |
| ApiPresetDialog 创建预设 | 不受影响，仍正常 |
| 问题任务 perf_ecbd92...（脏数据） | 后端防御解析后正常发送 |
| 旧任务回归（会议 book PUT 等） | 正常执行 |

---

*本文档由 Claude Code 生成*