# HANDOFF — 设备模拟模块

> **生成时间**: 2026-08-05
> **当前分支**: `platform-auto-test`
> **最近提交**: `11ddf7e9` fix(device-sim): 消息记录架构重构 - 消息队列解耦模拟器线程与数据库

---

## 一、本次会话完成的工作

### 1. ✅ 无纸化设备消息体修复

**需求**: 无纸化设备上报消息格式与真实设备一致。

**实现**:
- 新增 `build_reboot_response_payload()` 方法（无纸化专属字段：appName="无纸化2.0", deviceModel="Pad-10Pro", buildInfo="" 等）
- 新增 `build_heartbeat_payload()` 方法
- `deviceModel`: 门口屏 `YT-10`，无纸化 `Pad-10Pro`
- 授权码逻辑: 无纸化使用 `paperless_token_prefix` 或将门口屏 `AND-` 替换为 `ANP-`

**修改文件**:
- `backend/app/simulators/paperless_simulator.py`
- `backend/app/simulators/base_simulator.py`
- `backend/app/simulators/topic_templates.py`
- `frontend/src/views/device-sim/EnvConfig.vue`
- `frontend/src/types/device.ts`

### 2. ✅ 设备类型过滤修复

**问题**: 前端 API 参数使用 camelCase（`deviceType`），后端期望 snake_case（`device_type`）。

**修复**: 在 `frontend/src/api/deviceSim.ts` 中转换参数名。

### 3. ✅ 批量操作 API

**新增 API**:
- `POST /api/device-sim/devices/batch/start` — 批量启动
- `POST /api/device-sim/devices/batch/stop` — 批量停止
- `DELETE /api/device-sim/devices/batch` — 批量删除

**修改文件**:
- `backend/app/routers/device_sim.py`
- `backend/app/services/device_sim_service.py`
- `backend/app/schemas/device_sim.py`

### 4. ✅ WebSocket 实时消息推送

**新增端点**: `WS /api/device-sim/ws/messages`

**实现**: 消息队列消费成功后，通过回调推送消息到前端。

**修改文件**:
- `backend/app/routers/device_sim.py` — WebSocket 端点 + 回调注册
- `backend/app/services/device_sim_service.py` — 推送回调接口

### 5. ✅ 消息记录架构重构（消息队列）

**实现方案**:
- 引入 `asyncio.Queue(maxsize=10000)` 消息队列
- 模拟器线程只做入队（`enqueue_message()`），不涉及数据库
- 主线程异步消费循环（`message_consumer_loop()`）写入数据库 + WebSocket 推送
- 消费循环在 `main.py` 的 `lifespan` 中启动

**修改文件**:
- `backend/app/services/device_sim_service.py` — 消息队列 + 消费循环
- `backend/app/main.py` — lifespan 启动消费循环

### 6. ✅ 前端页面优化

**新组件**:
- `MessageStream.vue` — 实时消息流组件（终端风格，支持方向过滤/暂停/搜索）

**增强组件**:
- `DeviceList.vue` — 批量勾选和批量操作按钮

**页面重构**:
- `DoorSim.vue` — 左右布局（设备列表 + 消息流）
- `PaperlessSim.vue` — 同上
- `CentralSim.vue` — 同上
- `ClientSim.vue` — 同上

### 7. ✅ 文档

**新增文档**:
- `_PRD_需求优化_消息记录架构重构与实时消息流.md` — PRD 需求文档
- `_执行计划_消息记录架构重构与实时消息流.md` — 执行计划文档

**更新文档**:
- `HANDOFF_设备模拟.md` — 本文件

### 8. ✅ 代码提交

| 提交 | 说明 |
|------|------|
| `e4223169` | 无纸化设备消息体修复 + 环境配置支持无纸化授权码 |
| `63547417` | 页面优化 - 批量操作 + 实时消息流 + 环境切换 |
| `11ddf7e9` | 消息记录架构重构 - 消息队列解耦模拟器线程与数据库 |
| `22eb5e22` | 消息记录架构重构 PRD + 执行计划 + HANDOFF 更新 |

**已推送到远程**: `origin/platform-auto-test`

---

## 二、已知问题

### P0 — 消息流页面显示"暂无消息"

**现象**: 设备启动后显示 `running`，但 `totalReports: 0`，`lastReportedAt: null`，消息流页面无消息显示。

**排查过程**:
1. 确认新代码已部署到容器（`enqueue_message` 存在于容器内）
2. 确认 MQTT 连接正常，主题订阅成功
3. 确认 `_sync_report_log` 被重写为入队操作
4. 确认 `_message_queue` 在消费循环中运行
5. 确认 `_message_callback` 已注册到 WebSocket 路由
6. 发现 `message_consumer_loop` 日志"消息消费循环已启动"正常输出
7. 但 `_queue_stats` 显示 `enqueued: 0`，说明 `_sync_report_log` 没有被调用

**可能根因**（待验证）:
- 模拟器的 `_on_report_callback` 没有被正确设置？但 `set_report_callback` 在 `start_simulator` 中被调用
- 回调函数中，旧代码 `report_callback(**kwargs)` 还存在（第 579-583 行），但它是 `async` 函数且在线程中调用，可能被忽略
- `_notify_report` 中的 `self._on_report_callback` 可能为 `None`

**建议排查方向**:
1. 在 `_notify_report` 方法中添加日志，确认是否被调用
2. 在 `_sync_report_log` 方法中添加日志，确认是否被触发
3. 检查 `set_report_callback` 中的 lambda 闭包是否正确传参
4. 删除旧的 `report_callback` 异步函数（第 579-583 行），避免混淆

### P1 — 中控/集控客户端主题确认

**说明**: 这两类设备使用的旧格式主题，需要确认真实 MQTT 主题。

---

## 三、修改文件清单

### 已提交

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `backend/app/main.py` | 修改 | lifespan 启动消息消费循环 |
| `backend/app/routers/device_sim.py` | 修改 | 批量操作 API + WebSocket 端点 + 回调注册 |
| `backend/app/services/device_sim_service.py` | 修改 | 消息队列 + 消费循环 + 批量操作 |
| `backend/app/schemas/device_sim.py` | 修改 | BatchOperationRequest/Response |
| `backend/app/simulators/paperless_simulator.py` | 修改 | 无纸化专属消息体 |
| `backend/app/simulators/base_simulator.py` | 修改 | deviceModel 修正 |
| `backend/app/simulators/topic_templates.py` | 修改 | 无纸化新增主题 |
| `frontend/src/api/deviceSim.ts` | 修改 | 批量操作 API + WebSocket 连接 |
| `frontend/src/components/device/DeviceList.vue` | 修改 | 批量勾选和操作 |
| `frontend/src/components/device/MessageStream.vue` | 新增 | 实时消息流组件 |
| `frontend/src/types/device.ts` | 修改 | 无纸化授权码字段 |
| `frontend/src/views/device-sim/DoorSim.vue` | 修改 | 左右布局重构 |
| `frontend/src/views/device-sim/PaperlessSim.vue` | 修改 | 同上 |
| `frontend/src/views/device-sim/CentralSim.vue` | 修改 | 同上 |
| `frontend/src/views/device-sim/ClientSim.vue` | 修改 | 同上 |
| `frontend/src/views/device-sim/EnvConfig.vue` | 修改 | 无纸化授权码配置 |

---

## 四、后续待办

| 优先级 | 任务 | 关联文档 | 说明 |
|--------|------|----------|------|
| **P0** | 排查消息流"暂无消息"问题 | `_PRD_需求优化_消息记录架构重构与实时消息流.md` | 见已知问题分析 |
| P1 | 中控/集控客户端主题确认 | — | 确认真实上报/订阅主题 |
| P2 | 环境配置统计功能 | — | 统计各环境下的设备数量 |

---

## 五、相关文档索引

| 文档 | 路径 |
|------|------|
| 设备模拟 PRD | `Docs/PRD/需求文档/设备模拟/_PRD_需求文档_设备模拟模块.md` |
| 消息记录重构 PRD | `Docs/PRD/需求文档/设备模拟/_PRD_需求优化_消息记录架构重构与实时消息流.md` |
| 消息记录重构执行计划 | `Docs/PRD/需求文档/设备模拟/_执行计划_消息记录架构重构与实时消息流.md` |
| 环境配置多主题 PRD | `Docs/PRD/需求文档/设备模拟/_PRD_需求优化_环境配置多主题与设备接收记录.md` |

---

## 六、部署信息

| 项目 | 值 |
|------|-----|
| 服务器 IP | `192.168.5.60` |
| SSH 用户/密码 | `ubains / Ubains@123` |
| 部署目录 | `/data/third_party/plat-auto-test/` |
| Docker 容器名 | `plat-auto-test-app` |
| MySQL 容器名 | `plat-auto-test-mysql` |
| 前端地址 | http://192.168.5.60 |

---

## 七、技术备注

### 消息队列架构

```
模拟器线程（入队）                 主线程异步循环（消费）
┌──────────────────┐            ┌──────────────────────────┐
│ MQTT 上报         │            │ 消息队列 (asyncio.Queue)  │
│ → 回调触发        │ ────────▶ │ → 写入数据库              │
│ → 入队(非阻塞)    │            │ → WebSocket 推送          │
└──────────────────┘            └──────────────────────────┘
```

### 关键代码位置

- 消息队列定义: `device_sim_service.py` 第 34-60 行
- 入队函数: `device_sim_service.py` 第 62-76 行 (`enqueue_message`)
- 消费循环: `device_sim_service.py` 末尾 (`message_consumer_loop`)
- 消费循环启动: `main.py` lifespan 中
- WebSocket 回调注册: `device_sim.py` 第 557-577 行 (`_register_ws_callback`)

### 消息流组件使用

```vue
<MessageStream 
  device-type="door"           <!-- 按设备类型过滤 -->
  :selected-device-ids="ids"    <!-- 按设备ID过滤（可选） -->
/>
```

---

*本文档记录设备模拟模块开发状态，供下次会话快速恢复上下文。*