# PRD 需求文档：无纸化模拟器消息定时拉取

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

---

## 一、需求概述

### 1.1 背景

无纸化模拟器在 token 获取成功后，真实设备（无纸化2.0 应用，华为平板 BAH3-W59）还会定期调用 `POST /api/message/getMessageByAppToken` 接口拉取会议消息。

当前模拟器缺失此定时调用行为，与真实设备行为不一致。

### 1.2 目标

无纸化模拟器在 token 获取成功后，按照设备导入时配置的**上报间隔**（默认 30 秒）定时调用消息接口，仅确保**调用可达**（HTTP 200 即视为调用成功），解析并记录响应中的业务状态码（如 `A0034 会议不存在` 是正常业务响应，不代表调用失败）。

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

```
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- POST https://192.168.5.240/api/message/getMessageByAppToken
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- Authorization: BearereyJhbGciOiJIUzUxMiJ9...
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- X-SIGN: uVQIsUeJ/jsFPrvvtl1dmMYS+8X8KXYe9AEga1w6tWVN7AveMzbh/2bvNzXqsxHPnZlnbN+JAX/TOlaWMWyEC3l0YRz1wU4gNLiyAO3oUeA=
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- X-TIMESTAMP: 1787187939
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- X-RANDOM: BBw2q1hXIKdBM2TX
08-20 09:05:39.546 21525 22510 I PL#无纸化: UbRxRetrofit -- Params:Print content-type is not supported:application/x-www-form-urlencoded
08-20 09:05:44.656 21525 22510 I PL#无纸化: UbRxRetrofit -- Response Body:{"success":false,"code":"A0034","message":"会议不存在"}
```

**日志时间线**（token 获取成功后立即调用一次）：

```
09:05:33.577  → POST getTokenInfoByToken（token 获取）
09:05:39.501  → token 响应成功（耗时 5919ms）
09:05:39.546  → POST getMessageByAppToken（消息查询，token 成功后立即触发）
09:05:44.656  → 响应 {"success":false,"code":"A0034","message":"会议不存在"}
```

---

## 二、功能需求

### 2.1 消息查询接口调用

| 项目 | 说明 |
|------|------|
| 接口路径 | `POST /api/message/getMessageByAppToken`（**注意：`/api/` 前缀，非 `/exapi/`**） |
| 请求方式 | POST |
| 请求头 | `Authorization: Bearer {token}`（无空格格式 `BearereyJ...`）、`X-SIGN`/`X-TIMESTAMP`/`X-RANDOM`（动态签名，与门口屏算法一致） |
| Content-Type | `application/x-www-form-urlencoded`（日志确认） |
| 请求体 | form-urlencoded 格式，参数待实测确认（接口名暗示按 `appToken` 查询，可能含 `appToken`/`conferenceId`） |
| 响应处理 | **HTTP 200 即视为调用成功**；业务状态码（如 A0034 会议不存在）记录为正常业务响应，不影响后续定时调用 |

### 2.2 定时周期

- 调用间隔 = **设备导入时填写的上报间隔**（`report_config.interval`，默认 30 秒）
- 独立线程执行（与门口屏消息拉取同模式）
- 首次调用在 token 获取成功后**立即执行一次**（与真实设备日志时间线一致）

### 2.3 参数来源

| 参数 | 来源 |
|------|------|
| Authorization | `DoorTokenClient.get_token()` 返回的 `authorization` 字段（无空格格式 `BearereyJ...`） |
| appToken | token 响应 `token.appToken` 或导入时提供的授权码（已回填 `topic_params.app_token`） |
| X-SIGN/X-TIMESTAMP/X-RANDOM | 动态签名算法（复用 `_generate_dynamic_sign()`） |

### 2.4 签名算法

与门口屏完全一致（复用 `DoorTokenClient._generate_dynamic_sign()`）：

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 字符串）
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（无论业务码是否 200） | 记录响应日志（含 code/message），**业务码非 200 视为正常业务响应**（如 A0034 会议不存在 = 当前无会议，真实设备同样如此） |
| HTTP 非 200 | 日志警告，不影响下次定时调用 |
| 网络错误/超时 | 日志警告，下次定时继续尝试 |
| 请求异常 | 捕获异常，不影响设备运行 |

---

## 三、非功能需求

### 3.1 性能

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

### 3.2 兼容性

- 仅无纸化（paperless）设备需要此行为
- 未配置 `token_api_host` 时不触发（与 token 获取逻辑一致）
- 未获取到 `authorization` 时不触发

---

## 四、影响范围

### 4.1 修改文件

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/door_token_client.py` | 修改 | 新增 `PAPERLESS_MESSAGE_API_PATH = "/api/message/getMessageByAppToken"` 常量 + `get_message_by_app_token()` 方法 |
| `backend/app/simulators/paperless_simulator.py` | 修改 | 新增定时拉取线程（`_start_message_polling()` / `_call_message_once()` / `_message_loop()` / `stop()` 覆盖），启动流程四步化 |

### 4.2 不变的文件

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

---

## 五、依赖

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

---

## 六、验收标准

| # | 验收项 | 验证方式 |
|---|--------|----------|
| 1 | 无纸化启动 token 获取成功后，立即调用一次消息接口 | 日志查看 |
| 2 | 之后每 N 秒（与上报间隔一致）调用一次 | 日志查看时间间隔 |
| 3 | HTTP 200 + 业务码 A0034（会议不存在）记录为正常响应，不影响后续调用 | 日志不报错、定时循环持续 |
| 4 | 未配置 token_api_host 时不触发 | 日志无相关调用 |
| 5 | 设备停止时线程自动退出 | 日志确认 |