# PRD 需求文档：门口屏模拟器全局配置定时拉取

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

---

## 一、需求概述

### 1.1 背景

在门口屏模拟器获取 token 成功后，真实设备除了定时调用消息列表接口（`getMsgPageList`）和今日会议人脸页面接口（`getTodayMeetingFacePage`），还会定时调用 `GET /exapi/systemConfiguration/exGlobalConfig/encrypt` 接口拉取全局配置数据。

当前模拟器已实现 token 获取（会话 E）、消息列表定时拉取（会话 F）和今日会议人脸页面定时拉取（本次会话），但缺失此全局配置接口的定时调用。

### 1.2 目标

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

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

```
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- ----------Request Start----------
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- GET https://192.168.5.48/exapi/systemConfiguration/exGlobalConfig/encrypt?companyNumber=CN-34H-UBAINS
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- Content-Type: application/json
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- Authorization: BearereyJ...
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- X-SIGN: azYIfdNhtzt6opqItgub4FeZHV5rmu4cmjJ6ym7a+N4t5IeJsbgmC5oIocBB5aFdcqCky1WObDTtjC7OnLfqLz7DmsQT8VEedV3E2U+/e1Y=
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- X-TIMESTAMP: 1787135916
08-19 18:38:36.949 16621 12295 I UbGview : UbRxRetrofit -- X-RANDOM: 8tMFhCcJpylaN0Jo
08-19 18:38:37.332 16621 12295 I UbGview : UbRxRetrofit -- Response Body: {"success":true,"code":"200","message":"OPERATION_SUCCESSFUL","result":"VkjgBfDWAHNOGT...（密文, 较长）"}
08-19 18:38:37.333 16621 12295 I UbGview : UbRxRetrofit -- Time:377 ms
```

---

## 二、功能需求

### 2.1 接口调用

| 项目 | 说明 |
|------|------|
| 接口路径 | `GET /exapi/systemConfiguration/exGlobalConfig/encrypt` |
| 请求方式 | GET |
| 请求头 | `Authorization: Bearer {token}`（无空格格式 `BearereyJ...`）、`Content-Type: application/json`、`X-SIGN`/`X-TIMESTAMP`/`X-RANDOM`（动态签名，与消息列表算法一致） |
| 查询参数 | `companyNumber`（来自 token 响应的 `token.companyNumber`，已回填到 `topic_params.company_id`） |
| 响应处理 | 仅校验 HTTP 200 + `success=true`，**不解密 result 字段** |

### 2.2 定时周期

- 调用间隔 = **设备导入时填写的上报间隔**（`report_config.interval`，默认 30 秒）
- **与消息列表/人脸页面共用同一定时循环**，每次循环依次调用三个接口
- 首次调用在 token 获取成功后立即执行一次

### 2.3 参数来源

| 参数 | 来源 |
|------|------|
| Bearer token | `DoorTokenClient.get_token()` 返回的 `authorization` 字段（无空格格式） |
| companyNumber | token 响应中 `token.companyNumber`（已回填到 `topic_params.company_id`） |
| 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 + success=true | 日志记录成功，不处理 result |
| HTTP 非 200 | 日志警告，不影响下次定时调用 |
| 网络错误/超时 | 日志警告，下次定时继续尝试 |
| 请求异常 | 捕获异常，不影响设备运行 |

---

## 三、非功能需求

### 3.1 性能

- 与消息列表/人脸页面共用同一定时线程，不额外增加线程数
- 每次调用超时 5 秒

### 3.2 兼容性

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

---

## 四、影响范围

### 4.1 修改文件

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/door_token_client.py` | 修改 | 新增 `EX_GLOBAL_CONFIG_API_PATH` 常量 + `get_ex_global_config()` 方法 |
| `backend/app/simulators/door_simulator.py` | 修改 | 在定时循环中追加全局配置接口调用 |

### 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 响应成功，不解密 result | 日志不报错即可 |
| 4 | 未配置 token_api_host 时不触发 | 日志无相关调用 |
| 5 | 设备停止时线程自动退出 | 日志确认 |
| 6 | 真实调用验证：token → 全局配置接口 HTTP 200 + success=true | 本地脚本验证 |