# PRD 需求文档：无纸化模拟器 Token 获取

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

---

## 一、需求概述

### 1.1 背景

无纸化会议设备（华为平板 BAH3-W59，App 无纸化2.0）启动后的第一个动作是调用 HTTP 接口 `POST /api/api-android/token/getTokenInfoByToken` 获取 token。

当前模拟器缺失此行为，启动时直接连接 MQTT，导致：
- 缺少真实的 Authorization 认证信息
- `topic_params` 中的 `company_id` / `room_id` / `conference_id` / `conference_name` 依赖手动配置，而非从真实接口获取
- 模拟器行为与真实设备不一致，无法为后续定时 HTTP 接口调用（消息列表、人脸页面等）提供前置条件

### 1.2 目标

无纸化模拟器在启动时调用真实 token 接口，获取 Authorization 并回填 `topic_params`，使启动行为与真实设备一致。

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

```
08-20 09:05:33.577 21525 22510 I PL#无纸化: UbRxRetrofit -- POST https://192.168.5.240/api/api-android/token/getTokenInfoByToken
08-20 09:05:33.578 21525 22510 I PL#无纸化: UbRxRetrofit -- Body:{"appToken":"ANP-NRY-0022","regUdid":"54035332b33e1e30"}
08-20 09:05:39.501 21525 22510 I PL#无纸化: UbRxRetrofit -- Response Body:{"success":true,"code":"200","message":"OPERATION_SUCCESSFUL","result":{"Authorization":"BearereyJ...","picture":"","token":{"tokenId":206,"createUid":7,"companyNumber":"CN-P7J-UBAINS","appToken":"ANP-NRY-0022","regUdid":"54035332b33e1e30","regTime":1786670150000,"cnum":"248d49ca1c728232d0154f4ce5943148","state":1,"typeId":16,"startTime":"2026-07-27 14:13:40","endTime":"2099-12-31 12:59:59","createTime":"2026-07-27 16:07:23","annotation":"彭甘宇在2026-07-28 10:35:29绑定了设备","conference":{"conferenceId":4,"conferenceIp":"","conferenceName":"测试会议室1","conferenceNumber":"248d49ca1c728232d0154f4ce5943148",...},"company":{"companyId":444,"companyName":"招商局项目","companyNumber":"CN-P7J-UBAINS",...}}}}
```

---

## 二、功能需求

### 2.1 Token 接口调用

| 项目 | 说明 |
|------|------|
| 接口路径 | **`POST /api/api-android/token/getTokenInfoByToken`**（注意：与门口屏的 `/exapi/...` 路径不同） |
| 请求头 | `X-SIGN`（固定值，与门口屏共用）、`X-TIMESTAMP`（秒级）、`X-RANDOM`（16 位随机） |
| 请求体 | `{"appToken": "ANP-xxx-xxxx", "regUdid": "16位hex"}` |
| 响应解析 | 提取 `Authorization`、`token.companyNumber`、`token.cnum`、`token.conference`、`token.company` |

### 2.2 参数来源

| 参数 | 来源 |
|------|------|
| appToken | 设备导入时 Excel 提供的"授权码(app_token)"列（如 `ANP-NRY-0022`），**必须由用户提供，不自动生成** |
| regUdid | 由 `device_id` 确定性派生（格式：纯 16 位 hex，无 `reg_` 前缀，与无纸化真实日志格式一致） |
| X-SIGN | 复用门口屏已验证的固定值（`X_SIGN_FIXED`） |
| token_api_host | 环境配置 `EnvConfig.default_topic_params['token_api_host']`（如 `https://192.168.5.240`） |

### 2.3 regUdid 派生规则

**与门口屏的区别**：

| 对比项 | 门口屏 | 无纸化 |
|--------|--------|--------|
| regUdid 格式 | `reg_{md5(device_id)[:16]}` | `{md5(device_id)[:16]}`（纯 16 位 hex，无前缀） |
| 示例 | `reg_79f18c1a9a3bcfb1` | `54035332b33e1e30` |

无纸化 `regUdid` 派生：`md5(device_id)[:16]`（确定性，同设备多次启动结果一致）。

### 2.4 响应字段回填

token 获取成功后，将以下字段回填到 `topic_params`：

| topic_params 字段 | 来源 |
|-------------------|------|
| `company_id` | `token.companyNumber`（如 `CN-P7J-UBAINS`） |
| `room_id` | `token.cnum`（如 `248d49ca1c728232d0154f4ce5943148`） |
| `conference_id` | `token.conference.conferenceId`（如 `4`） |
| `conference_name` | `token.conference.conferenceName`（如 `测试会议室1`） |
| `app_token` | 导入时提供的 appToken（如 `ANP-NRY-0022`） |

### 2.5 appToken 登记池（复用门口屏机制）

- appToken **不自动生成**，来自设备导入时 Excel 提供的授权码
- 同一 appToken 在同一进程内只能被一台设备使用（去重机制）
- 未提供 appToken → 报错拒绝启动
- 重复使用 → 报错拒绝启动

### 2.6 错误处理

| 场景 | 行为 |
|------|------|
| HTTP 200 + 业务成功 | 解析响应、回填 topic_params、继续启动流程 |
| 网络错误（ConnectionError/Timeout） | 指数退避重试 3 次（1s, 2s, 4s） |
| 业务错误（ValueError） | 不重试，记录错误日志 |
| 未配置 `token_api_host` | 仅 warning 跳过，不影响启动（向后兼容） |

---

## 三、非功能需求

### 3.1 性能

- 单次调用超时 5 秒
- 重试间隔指数退避（1s, 2s, 4s）

### 3.2 兼容性

- 仅无纸化（paperless）设备需要此行为
- 未配置 `token_api_host` 时不触发（向后兼容，现有环境配置不受影响）
- 现有环境配置不需要新增字段（沿用门口屏的 `token_api_host` 配置）

---

## 四、影响范围

### 4.1 修改文件

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/simulators/door_token_client.py` | 修改 | 新增 `PaperlessTokenClient` 类或使 `DoorTokenClient` 支持无纸化配置（API 路径 + regUdid 格式） |
| `backend/app/simulators/paperless_simulator.py` | 修改 | `start()` 接入 token 获取流程 + 回填 topic_params |
| `backend/app/simulators/base_simulator.py` | 无需修改 | 已支持 `token_api_host` 参数 |
| `backend/app/simulators/__init__.py` | 无需修改 | 已透传 `token_api_host` |
| `backend/app/services/device_sim_service.py` | 无需修改 | 已从环境配置读取 `token_api_host` |

### 4.2 不变的文件

- 前端文件 — 无改动
- 数据库/Schema — 无改动（复用门口屏的 `token_api_host` 配置）

---

## 五、依赖

- **requests**（已安装）— HTTP 请求
- **DoorTokenClient**（已实现）— 可复用其固定签名、请求会话、响应解析逻辑
- **TokenPool**（已实现）— 可复用其 appToken 登记池

---

## 六、验收标准

| # | 验收项 | 验证方式 |
|---|--------|----------|
| 1 | 配置 token_api_host 后，无纸化启动时调用 token 接口成功 | 日志查看 `Token 获取成功` |
| 2 | topic_params 正确回填（company_id / room_id / conference_id / conference_name） | 日志查看回填后的参数 |
| 3 | 未提供 appToken 时报错拒绝自动生成 | 日志查看 `未配置 appToken` |
| 4 | 重复使用 appToken 时报错拒绝 | 日志查看 `已被其他设备使用` |
| 5 | 未配置 token_api_host 时跳过 token 获取，不影响启动 | 日志 warning + 设备正常启动 |
| 6 | regUdid 格式为纯 16 位 hex（无 `reg_` 前缀），确定性派生 | 同 device_id 多次启动结果一致 |

---

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

无纸化模拟器在 token 获取成功后，还有一个**定期查询会议消息接口** `POST /api/message/getMessageByAppToken`（真实设备日志已提供），见：
- `_PRD_需求优化_无纸化模拟器消息定时拉取.md`
- `_执行计划_无纸化模拟器消息定时拉取.md`