# PRD 需求文档：门口屏模拟器消息列表定时拉取

> **版本**: v1.0
> **创建日期**: 2026-08-19
> **状态**: 待实现

---

## 一、需求概述

### 1.1 背景

在门口屏模拟器获取 token 成功后，真实设备会定时调用 `/exapi/message/getMsgPageList/encrypt` 接口拉取消息列表。

当前模拟器只实现了 token 获取（会话 E），但缺失了后续的定时消息列表拉取行为，导致模拟器行为与真实设备不一致。

### 1.2 目标

门口屏模拟器在获取 token 成功后，按照设备导入时配置的**上报间隔**（默认 30 秒）定时调用消息列表接口，仅确保**调用成功**（HTTP 200），不解析/不处理加密的响应数据。

### 1.3 真实设备日志参考

```
GET https://192.168.5.48/exapi/message/getMsgPageList/encrypt
  ?queryType=2
  &pageNum=1
  &conferenceId=50
  &pageSize=100
  &maxTime=2026-08-19%2023%3A59%3A59
  &minTime=2026-08-18%2018%3A38%3A33

Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
X-SIGN: 6QO4bEnbqwun/VqgV7iqLMHNw8pYdMMe1E4mW7tnOKk...
X-TIMESTAMP: 1787135913
X-RANDOM: RF8cFOj0PD1uJIxN

Response: {"success":true,"code":"200","message":"OPERATION_SUCCESSFUL",
           "result":"VkjxFfHBF2tbT2CMaJPAZzZoPCEAknf0..."}
```

---

## 二、功能需求

### 2.1 消息列表接口调用

| 项目 | 说明 |
|------|------|
| 接口路径 | `GET /exapi/message/getMsgPageList/encrypt` |
| 请求头 | `Authorization: Bearer {token}`（来自 getTokenInfoByToken 的响应） |
| 签名 | 动态 X-SIGN/X-TIMESTAMP/X-RANDOM（与 token 接口不同，需根据 Bearer token 和请求参数实时计算） |
| 查询参数 | `queryType=2`、`pageNum=1`、`conferenceId={来自token响应}`、`pageSize=100`、`maxTime={当前时间}`、`minTime={24小时前}` |
| 响应处理 | 仅校验 HTTP 200 + `success=true`，**不解密 result 字段** |

### 2.2 定时周期

- 调用间隔 = **设备导入时填写的上报间隔**（`report_config.interval`，默认 30 秒）
- 与定时上报线程共用同一间隔值，保证行为一致
- 首次调用在 token 获取成功后立即执行一次

### 2.3 参数来源

| 参数 | 来源 |
|------|------|
| Bearer token | `DoorTokenClient.get_token()` 返回的 `authorization` 字段 |
| conferenceId | token 响应中 `conference.conferenceId`（已回填到 `topic_params.conference_id`） |
| queryType | 固定值 `2` |
| pageSize | 固定值 `100` |
| minTime | 当前时间 - 24 小时（格式 `YYYY-MM-DD HH:mm:ss`） |
| maxTime | 当前时间（格式 `YYYY-MM-DD HH:mm:ss`） |

### 2.4 签名算法

与 `http_client.py` 中已有的签名算法一致（前端 JS 逆向）：

1. 生成随机 x_random（8-16 位字母数字）
2. x_timestamp = 当前**毫秒**时间戳
3. sign_str = timestamp + JSON.stringify(params) + random（有查询参数时）
4. sign_hash = SHA256(sign_str)
5. aes_key_source = SHA256(bearer_token)（用完整的 Bearer 字符串，含 "Bearer " 前缀）
6. aes_key = aes_key_source[16:32]（16 字符）
7. aes_iv = aes_key_source[0:8] + aes_key_source[-8:]（16 字符）
8. x_sign = AES-CBC 加密(sign_hash, aes_key, aes_iv) → Base64 编码

### 2.5 错误处理

| 场景 | 行为 |
|------|------|
| HTTP 200 + success=true | 日志记录成功，不处理 result |
| HTTP 非 200 | 日志警告，不影响下次定时调用 |
| 网络错误/超时 | 日志警告，下次定时继续尝试 |
| 请求异常 | 捕获异常，不影响设备运行 |

---

## 三、非功能需求

### 3.1 性能

- 定时调用在独立线程中执行，不阻塞主上报循环
- 每次调用超时 5 秒

### 3.2 兼容性

- 仅门口屏（door）设备需要此行为
- 未配置 token_api_host 时不触发（与 token 获取逻辑一致）

---

## 四、影响范围

### 4.1 修改文件

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/door_token_client.py` | 修改 | 新增 `get_message_list()` 方法 + 签名算法 |
| `backend/app/simulators/door_simulator.py` | 修改 | 新增定时消息列表拉取线程 |

### 4.2 不变的文件

- `base_simulator.py` — 不改动基类
- 前端文件 — 无改动
- 数据库/Schema — 无改动
- `__init__.py` — 无改动

---

## 五、依赖

- **pycryptodome**（已安装）— AES-CBC 加密签名
- **requests**（已安装）— HTTP 请求
- **DoorTokenClient.get_token()** 返回的 token 信息（已实现）

---

## 六、验收标准

| # | 验收项 | 验证方式 |
|---|--------|----------|
| 1 | 启动门口屏模拟器后，token 获取成功后立即调用一次消息列表接口 | 日志查看 |
| 2 | 之后每 N 秒（与上报间隔一致）调用一次 | 日志查看时间间隔 |
| 3 | HTTP 200 响应成功，不解密 result | 日志不报错即可 |
| 4 | 未配置 token_api_host 时不触发 | 日志无相关调用 |
| 5 | 设备停止时线程自动退出 | 日志确认 |