# 执行计划：门口屏模拟器消息列表定时拉取

> **对应 PRD**: `_PRD_需求优化_门口屏消息列表定时拉取.md`
> **创建日期**: 2026-08-19
> **状态**: 执行中

---

## 阶段一：扩展 DoorTokenClient —— 新增 get_message_list() + 动态签名

**目标**：在 `door_token_client.py` 中新增消息列表接口调用能力。

### 任务清单

| # | 任务 | 状态 |
|---|------|------|
| 1.1 | 新增动态签名方法 `_generate_dynamic_sign()`：基于 Bearer token + 查询参数计算 X-SIGN（SHA256 + AES-CBC），复用 `http_client.py` 已验证的算法 | ⬜ |
| 1.2 | 新增 `MESSAGE_LIST_API_PATH = "/exapi/message/getMsgPageList/encrypt"` 常量 | ⬜ |
| 1.3 | 新增 `get_message_list(authorization, conference_id)` 实例方法：GET 请求 + Bearer token + 动态签名头 + query 参数 | ⬜ |
| 1.4 | 参数构造：`queryType=2, pageNum=1, pageSize=100, conferenceId={token响应}, minTime={now-24h}, maxTime={now}` | ⓧ |
| 1.5 | 响应校验：仅判断 HTTP 200 + code="200"，不解析加密 result | ⬜ |
| 1.6 | 会话 GET 重试适配：`allowed_methods` 从 `["POST"]` 扩展为 `["GET", "POST"]` | ⬜ |

### 关键设计决策

1. **X-SIGN 动态计算**（不复用 token 接口的固定 X_SIGN_FIXED）：
   - token 接口的固定签名已实测通过，但消息接口每次参数不同（时间变化），固定签名无法复用
   - 复用 `http_client.py` 的动态签名算法（SHA256 + AES-CBC，已在该系统 Web 端验证过）
   - 注意：`http_client.py` 用毫秒时间戳，真实设备日志（秒级 1787135913）不同——先按 http_client 算法实现，实测验证，若服务端校验秒级再调整

2. **Authorization 格式（实测关键发现）**：
   - 真实设备日志 Authorization 为 **`BearereyJ...`（无空格）**
   - token 接口返回的 `result.Authorization` 即为此格式，直接原样使用
   - 若手动改为标准 `Bearer eyJ...`（带空格），消息接口返回 `A0076 无效token`
   - 签名密钥源与请求头字符串必须一致（都用无空格格式）

3. **首调即验**：方法实现后先本地直接调用真实接口验证（appToken=AND-34H-0101 + regUdid=79f18c1a9a3bcfb1 先调 token 接口拿 Bearer，再调消息接口）

---

## 阶段二：DoorSimulator 集成 —— 定时拉取线程

**目标**：门口屏模拟器启动后按上报间隔定时调用消息列表接口。

### 任务清单

| # | 任务 | 状态 |
|---|------|------|
| 2.1 | `DoorSimulator.__init__` 新增 `_token_client`（复用 token 获取时的客户端实例）和 `_msg_thread`/`_msg_running` 线程控制属性 | ⬜ |
| 2.2 | `start()` token 获取成功后：立即调用一次 `get_message_list()` + 启动定时线程（间隔 = `self._report_interval`，即导入时填写的上报间隔） | ⬜ |
| 2.3 | 新增 `_message_list_loop()` 方法：循环调用 `get_message_list()`，成功/失败仅记日志 + `_notify_report` 上报记录，异常不中断 | ⬜ |
| 2.4 | `stop()` 中置 `_msg_running = False` 停止拉取线程 | ⬜ |
| 2.5 | 未配置 `token_api_host`（跳过 token 获取）时同样跳过消息拉取（向后兼容） | ⬜ |

### 关键设计决策

1. **间隔来源**：直接用 `self._report_interval`（设备导入时 Excel 填写的上报间隔），不新增配置项，与 MQTT 上报节奏一致
2. **独立线程**：消息拉取是 HTTP 调用，与 MQTT 上报循环分离，避免 HTTP 超时阻塞上报
3. **线程命名**：`msg_{device_id[:8]}`，daemon=True
4. **失败不影响运行**：消息接口调用失败只记日志，不停止设备（与 token 获取失败即停止的策略不同——token 是启动前提，消息拉取是持续性行为模拟）

---

## 阶段三：验证

| # | 验证项 | 状态 |
|---|--------|------|
| 3.1 | Python 语法检查（py_compile） | ✅ 通过 |
| 3.2 | 本地真实调用验证：token → 消息列表接口 HTTP 200 | ✅ 通过（result_len=189548） |
| 3.3 | 签名算法验证：秒级时间戳 + AES 算法通过服务端校验 | ✅ 通过 |
| 3.4 | Authorization 无空格格式（BearereyJ...）验证 | ✅ 必须无空格，带空格返回 A0076 无效token |

---

## 七、后续工作（本期不做）

- 消息内容解密（服务端响应 result 加密数据的解密算法）
- 动态 X-SIGN 算法与真实设备的完全对齐（如服务端校验拒绝毫秒时间戳签名，需逆向 Android 端签名逻辑）
- 消息列表结果与 MQTT 主题上报联动
