# 执行计划 - 修复退出登录步骤超时与后端僵尸进程

> **文档类型**: 执行计划文档
> **创建日期**: 2026-07-16
> **作者**: czj
> **关联文档**: `_问题处理_退出登录步骤超时与后端僵尸进程.md`
> **状态**: 待执行

---

## 一、改动总览

| # | 改动项 | 范围 | 说明 |
|---|--------|------|------|
| 1 | 杀掉所有僵尸 python 进程 | 运维 | 清理 8001 端口 8 个僵尸进程 |
| 2 | 杀掉 8002 端口后端进程 | 运维 | 统一端口，避免混乱 |
| 3 | 清理调试代码 | `playwright_executor.py` | 移除 `logout_debug.log` 写入 |
| 4 | 优化退出登录逻辑 | `playwright_executor.py` | 增加步骤 params.logout 标志位，不依赖步骤名 |
| 5 | 在 8001 端口启动后端 | 运维 | 不用 --reload，避免再次产生僵尸 |
| 6 | 验证会议管理 6 用例全通过 | 测试 | 退出登录步骤不再超时 |

**总计**：1 个代码文件改动，2 个运维操作，1 个验证步骤。

---

## 二、详细执行步骤

### Step 1：清理僵尸进程

**操作**：杀掉所有 python.exe 的 multiprocessing.spawn 子进程 + 8002 端口进程

```bash
# 杀掉所有 multiprocessing.spawn 子进程（8 个僵尸）
taskkill /F /PID 4852 /PID 4068 /PID 13408 /PID 25872 /PID 6644 /PID 23564 /PID 21476 /PID 7448

# 杀掉 8002 端口后端
taskkill /F /PID 28076

# 验证 8001 端口已释放
netstat -ano | grep 8001
```

**预期**：8001 端口无 LISTENING 记录，8002 端口也无 LISTENING。

### Step 2：清理调试代码 + 优化退出登录逻辑

**文件**：`backend/app/executors/playwright_executor.py`

#### 2.1 移除 logout_debug.log 调试代码

**修改前**（第 710-726 行）：

```python
# 退出登录：导航到登录页时，清除 cookies 强制登出
# （SPA hash 路由在已登录时访问 #/login 会被重定向回主页）
with open('logout_debug.log', 'a', encoding='utf-8') as _f:
    _f.write(f"navigate: name={step_result.name} url={url} logged_in={self._is_logged_in}\n")
if "login" in url.lower():
    with open('logout_debug.log', 'a', encoding='utf-8') as _f:
        _f.write(f"走reset分支\n")
    try:
        self.reset_login_state()
        with open('logout_debug.log', 'a', encoding='utf-8') as _f:
            _f.write(f"reset成功\n")
    except Exception as re:
        with open('logout_debug.log', 'a', encoding='utf-8') as _f:
            _f.write(f"reset异常: {type(re).__name__}: {re}\n")
    step_result.log = f"✓ 退出登录，回到登录页"
    step_result.status = "passed"
    break
```

**修改后**：

```python
# 退出登录：导航到登录页时，清除 cookies 强制登出
# （SPA hash 路由在已登录时访问 #/login 会被重定向回主页）
if "login" in url.lower() or params.get("logout", False):
    logger.info(f"退出登录: name={step_result.name}, url={url}")
    try:
        self.reset_login_state()
    except Exception as re:
        logger.error(f"退出登录 reset 失败: {re}")
        raise
    step_result.log = f"✓ 退出登录，回到登录页"
    step_result.status = "passed"
    break
```

#### 2.2 优化智能跳过逻辑

**修改前**（第 695-705 行）：

```python
# 智能处理：如果已登录且步骤是导航到登录页或首页，跳过
# 但"退出登录"步骤不跳过（步骤名称包含"退出"）
if (action == "navigate" and self._is_logged_in
        and "退出" not in step_result.name):
    url = params.get("url", "")
    if (self.LOGIN_CONFIG["base_url"] in url and
        ("login" in url.lower() or url == self.LOGIN_CONFIG["base_url"] or url.endswith("/"))):
        step_result.status = "passed"
        step_result.log = f"✓ 已登录，跳过导航: {url}"
        logger.info(f"✓ 已登录，跳过步骤 {step_result.order}: {step_result.name}")
        break
```

**修改后**：

```python
# 智能处理：如果已登录且步骤是导航到登录页或首页，跳过
# 但退出登录步骤不跳过（通过 params.logout 或 URL 含 login 判断）
if (action == "navigate" and self._is_logged_in
        and not params.get("logout", False)
        and "退出" not in step_result.name):
    url = params.get("url", "")
    if (self.LOGIN_CONFIG["base_url"] in url and
        ("login" in url.lower() or url == self.LOGIN_CONFIG["base_url"] or url.endswith("/"))):
        step_result.status = "passed"
        step_result.log = f"✓ 已登录，跳过导航: {url}"
        logger.info(f"✓ 已登录，跳过步骤 {step_result.order}: {step_result.name}")
        break
```

**改动说明**：
- 增加 `params.get("logout", False)` 判断，步骤可通过 `params.logout: true` 显式声明退出登录
- 保留 `"退出" not in step_result.name` 兼容旧数据
- 退出登录的 navigate 分支增加 `params.get("logout", False)` 条件，双重保障

#### 2.3 删除残留的 logout_debug.log 文件

```bash
rm -f backend/logout_debug.log
```

### Step 3：在 8001 端口启动后端

```bash
cd backend
uvicorn app.main:app --port 8001
# ⚠️ 不用 --reload，避免再次产生僵尸进程
```

**验证**：

```bash
curl http://localhost:8001/health
# 预期: {"status":"healthy",...}
```

### Step 4：验证会议管理 6 用例全通过

1. 访问 http://localhost:3000/cases/ui
2. 筛选"会议管理"模块，全选 6 个用例
3. 点击"执行选中"，自动跳转 /execution/ui
4. 等待执行完成
5. 验证 6/6 全部 passed，退出登录步骤不再超时

---

## 三、验证步骤

### 3.1 端口验证

| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | `netstat -ano \| grep 8001` | 只有 1 个 LISTENING（新后端进程） |
| 2 | `curl localhost:8001/health` | 返回 healthy |
| 3 | `curl localhost:8002/health` | 连接失败（8002 已关闭） |

### 3.2 代码验证

| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | grep "logout_debug" playwright_executor.py | 无匹配（调试代码已移除） |
| 2 | grep "params.get.*logout" playwright_executor.py | 有匹配（新增 logout 标志位） |

### 3.3 功能验证

| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | 批量执行会议管理 6 用例 | 6/6 全部 passed |
| 2 | 查看末用例"预定数据"步骤详情 | "退出登录"步骤状态 passed |
| 3 | 查看首用例"个人日程"步骤详情 | 登录步骤正常，auto_login=False 手动登录 |

---

## 四、风险评估

| 风险 | 概率 | 影响 | 缓解 |
|------|------|------|------|
| 杀进程时误杀其他 python 服务 | 低 | 其他服务中断 | 仅杀确认的 multiprocessing.spawn 子进程和 8002 进程 |
| 8001 端口 TIME_WAIT 未释放 | 中 | 新后端启动失败 | 等待 30-60 秒后重试，或换端口 |
| 退出登录 reset_login_state 仍失败 | 低 | 末用例失败 | 已验证独立脚本通过，问题在进程而非代码 |
| 前端代理仍指向 8001 | 无 | 无 | vite.config.ts 已指向 8001，无需修改 |

---

*本文档由 Claude Code 生成，遵循项目执行计划文档规范。*
