# 问题分析：全部启动功能失败 — token_api_host 错误回退导致批量启动阻塞

> **分析日期**: 2026-08-20
> **相关模块**: 设备模拟（门口屏/无纸化全部启动）
> **问题现象**: 点击"全部启动"后，页面长时间无响应，设备状态不更新

---

## 一、问题描述

点击门口屏或无纸化的"全部启动"按钮后，页面一直处于加载状态，设备未能成功启动。之前版本的"全部启动"功能可以正常工作。

---

## 二、问题复现

1. 设备模拟页面 → 选择门口屏（540 台设备）或 无纸化（500 台设备）
2. 点击"全部启动"按钮
3. 页面显示加载状态，长时间无响应
4. 设备状态未更新为 running

---

## 三、根因分析

### 3.1 代码变更

会话 H 新增了门口屏 Token 获取功能（`door_simulator.py` 重写 `start()` 方法），在 `device_sim_service.py` 的 `start_simulator()` 中新增了 `token_api_host` 参数传递逻辑：

```python
# 获取 token_api_host（从环境配置的 default_topic_params 中）
token_api_host = None
if config.default_topic_params:
    token_api_host = config.default_topic_params.get("token_api_host") or config.broker_host
```

**问题在第 843 行**：`config.default_topic_params.get("token_api_host")` 返回 `None`（环境配置中未设置该字段），然后 `None or config.broker_host` 将 `token_api_host` 回退为 MQTT Broker 地址 `192.168.5.44`。

### 3.2 服务器环境配置现状

5.60 服务器的环境配置 `default_topic_params` 内容：

```json
{
  "room_ids": ["29dcd33aa47797e7d29d79ae6711d597"],
  "company_id": "CN-WQF-UBAINS",
  "app_token_end": 40,
  "conference_id": "1088",
  "app_token_start": 1,
  "conference_name": "北京展厅会议室",
  "app_token_prefix": "AND-IEL"
}
```

**不包含 `token_api_host` 字段**。因此 `token_api_host` 被错误地设为 `"192.168.5.44"`（MQTT Broker 地址，非 Token 接口地址）。

### 3.3 阻塞链路

`batch_start_simulators()` 按设备 ID **串行遍历**（`for device_id in device_ids`），每个设备启动时：

```
start_simulator(device_id)
  → 创建 DoorSimulator 实例（传入 token_api_host="192.168.5.44"）
  → sim.start()
    → DoorSimulator.start()
      → token_api_host = "192.168.5.44"（非 None，进入 token 获取分支）
      → allocate_app_token() 登记 appToken ✅
      → DoorTokenClient(host="192.168.5.44")
        → POST https://192.168.5.44/exapi/api-android/token/getTokenInfoByToken
        → ❌ 接口不存在/连接失败（Token API 在 192.168.5.48，不在 192.168.5.44）
        → 抛 ValueError，door_simulator.start() 返回 False
      → 记录错误日志，继续下一个设备
```

**540 台门口屏 × 每次失败耗时 ≈ 1-5 秒 = 大量时间浪费**，且所有设备都因 token 获取失败而启动失败。

### 3.4 为什么之前版本正常

之前版本的 `door_simulator.py` 没有重写 `start()` 方法，直接调用 `BaseSimulator.start()`，不涉及 Token 获取，因此不会阻塞。新增 Token 获取功能后，即便 `token_api_host` 未配置，代码也会回退到 `broker_host` 并尝试调用 Token 接口，导致批量启动全部失败。

---

## 四、影响范围

| 功能 | 是否受影响 | 原因 |
|------|-----------|------|
| 门口屏全部启动（500台） | ✅ **严重受影响** | 每台设备串行调用 Token 接口失败，全部启动失败 |
| 无纸化全部启动（500台） | ✅ **受影响** | 无纸化不涉及 Token 获取，但受 `create_simulator` 传入 `token_api_host` 影响？ |
| 门口屏单台启动 | ✅ **受影响** | 同样会尝试 Token 获取，但单台用户可感知失败信息 |
| 无纸化/中控/集控客户端单台启动 | ⚠️ 可能受影响 | 需检查是否也触发 Token 获取逻辑 |

---

## 五、修复方案

### 方案 A（推荐）：修复 token_api_host 回退逻辑

修改 `device_sim_service.py` 第 843 行，**不要将 `broker_host` 作为 `token_api_host` 的默认值**：

```python
# 修复前
token_api_host = config.default_topic_params.get("token_api_host") or config.broker_host

# 修复后
token_api_host = config.default_topic_params.get("token_api_host")
```

这样如果环境配置未设置 `token_api_host`，则 `token_api_host = None`，`door_simulator.start()` 中 `if not token_host:` 判断为 True，跳过 Token 获取，直接走 MQTT 启动流程，与之前版本行为一致。

### 方案 B：在环境配置中添加 token_api_host

在 5.60 服务器的环境配置 `default_topic_params` 中添加 `token_api_host: "192.168.5.48"`。但该方案需要用户手动配置，且不兼容已有环境配置，不推荐。

---

## 六、预防措施

1. **新增参数时避免不安全默认值**：`or config.broker_host` 这种回退逻辑假设 Token API 和 MQTT Broker 在同一台服务器，但实际场景中通常是不同服务器
2. **批量操作前增加超时保护**：`batch_start_simulators` 串行遍历 500+ 设备，任何一个设备卡住都会导致整个操作挂起，应增加超时或并行控制
3. **环境配置升级脚本**：新增配置字段时，应提供迁移脚本为已有环境配置设置默认值

---

*本文档由 Claude Code 生成，2026-08-20*