# 执行计划：门口屏模拟器今日会议人脸页面定时拉取

> **对应 PRD**: `_PRD_需求优化_门口屏今日会议人脸页面定时拉取.md`
> **创建日期**: 2026-08-19
> **状态**: 待执行

---

## 阶段一：扩展 DoorTokenClient —— 新增 get_today_meeting_face_page()

**目标**：在 `door_token_client.py` 中新增今日会议人脸页面接口调用能力。

### 任务清单

| # | 任务 | 状态 |
|---|------|------|
| 1.1 | 新增 `FACE_PAGE_API_PATH = "/exapi/manageUser/getTodayMeetingFacePage/encrypt"` 常量 | ✅ |
| 1.2 | 新增 `get_today_meeting_face_page(authorization, conference_id, company_number)` 实例方法：POST 请求 + form-urlencoded 体 + 动态签名头 | ✅ |
| 1.3 | 请求体构造：`conferenceId` + `companyNumber`，Content-Type: `application/x-www-form-urlencoded` | ✅ |
| 1.4 | 动态签名复用 `_generate_dynamic_sign(params, bearer_token)`（已实现，与消息列表相同） | ✅ |
| 1.5 | 响应校验：仅判断 HTTP 200 + success=true，不解析加密 result | ✅ |
| 1.6 | 错误处理：网络错误/超时不重试（下次定时再试），业务失败警告日志 | ✅ |

### 关键设计决策

1. **复用现有签名算法**：`_generate_dynamic_sign()` 已支持带 params 的场景，传入 `{"conferenceId": ..., "companyNumber": ...}` 即可
2. **请求体格式**：POST + form-urlencoded（与消息列表的 GET + query params 不同），需注意设置正确的 Content-Type
3. **Authorization 格式**：与消息列表一致，使用 `BearereyJ...`（无空格格式）
4. **【实测参数发现】body 必须含 companyNumber**：
   - 仅传 conferenceId → `A0002 缺少参数`
   - body 传 `companyNumber=CN-34H-UBAINS` → `200 OPERATION_SUCCESSFUL`（conferenceId 可选，同时传不影响）
   - companyNumber 来自 token 响应的 `token.companyNumber`，已回填到 `topic_params.company_id`

---

## 阶段二：DoorSimulator 集成 —— 定时循环中追加人脸页面调用

**目标**：在现有消息列表定时循环中，每次循环追加一次人脸页面接口调用。

### 任务清单

| # | 任务 | 状态 |
|---|------|------|
| 2.1 | 新增 `_call_face_page_once()` 方法：单次调用人脸页面接口 + 记录 ReportLog | ✅ |
| 2.2 | 修改 `_message_list_loop()`：每次循环先调 `_call_message_list_once()`，再调 `_call_face_page_once()` | ✅ |
| 2.3 | 修改 `_start_message_list_polling()`：首次立即调用时也包含人脸页面接口 | ✅ |
| 2.4 | 未获取 authorization 时跳过人脸页面调用（与消息列表一致） | ✅ |

### 关键设计决策

1. **共用同一定时线程**：不新增线程，消息列表和人脸页面在同一个循环中串行调用（顺序为先消息列表后脸页面），避免线程膨胀
2. **间隔不变**：仍使用 `self._report_interval`，一次循环内串行调两个接口，总耗时≈两个 HTTP 调用时间（约 1s），不影响间隔精度
3. **失败不中断**：任一接口失败不影响另一接口的调用，也不影响下一次定时循环
4. **首次立即执行**：在 `_start_message_list_polling()` 中首次调用时，两个接口都立即执行一次

---

## 阶段三：验证

| # | 验证项 | 验证方式 | 状态 |
|---|--------|----------|------|
| 3.1 | Python 语法检查 | `python -m py_compile backend/app/simulators/door_token_client.py` | ✅ 通过 |
| 3.2 | Python 语法检查 | `python -m py_compile backend/app/simulators/door_simulator.py` | ✅ 通过 |
| 3.3 | 本地真实调用验证：token → 人脸页面接口 HTTP 200 + success=true | 本地脚本调用 | ✅ 通过（code=200, success=True, result_len=172） |

### 验证过程记录

1. **参数探测**：初始仅传 `conferenceId` → `A0002 缺少参数`（签名与 Authorization 均已通过校验，仅 body 参数不足）
2. **逐一排查**：queryType/companyNumber/token/appToken/minTime+maxTime 组合测试 → **`companyNumber` 组合返回 200**
3. **最小参数集确认**：`companyNumber` 单独传 → 200 成功；`companyNumber+conferenceId` → 200 成功（result_len=172 一致）
4. **端到端验证**：通过 `DoorTokenClient.get_token()` + `get_today_meeting_face_page()` 完整链路调用成功
   - 注意：授权码 `AND-34H-0101` 已被服务端绑定到会话 E 的 regUdid `79f18c1a9a3bcfb1`，验证时需复用该 regUdid（新 regUdid 返回 `A0305 授权码已使用`）

---

## 影响范围

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/door_token_client.py` | 修改 | 新增 `get_today_meeting_face_page()` 方法 + 常量 |
| `backend/app/simulators/door_simulator.py` | 修改 | 定时循环中追加人脸页面调用 |