# 执行计划 — 消息记录架构重构与实时消息流

> **文档类型**: 执行计划文档
> **创建日期**: 2026-08-05
> **关联 PRD**: `_PRD_需求优化_消息记录架构重构与实时消息流.md` v1.0
> **负责人**: czj
> **优先级**: P0
> **状态**: 待评审

---

## 一、执行概述

### 1.1 需求概要

重构设备模拟模块的消息记录机制，引入消息队列解耦模拟器线程和数据库/推送操作：
- 模拟器线程：只负责 MQTT 上报 + 消息入队（非阻塞）
- 主线程异步循环：消费队列 + 写入数据库 + WebSocket 推送

### 1.2 技术变更范围

| 层级 | 影响文件 | 变更类型 |
|------|----------|----------|
| 服务层 | `backend/app/services/device_sim_service.py` | 重构消息记录机制 |
| 路由层 | `backend/app/routers/device_sim.py` | WebSocket 推送调整 |
| 入口层 | `backend/app/main.py` | 启动消费循环 |

### 1.3 预计工期

| 阶段 | 内容 | 预计工时 |
|------|------|---------|
| 后端重构 | 消息队列 + 消费循环 | 2 小时 |
| 测试验证 | 功能测试 + 边界测试 | 1 小时 |
| 文档更新 | HANDOFF + 部署 | 0.5 小时 |

---

## 二、任务分解

### 2.1 消息队列实现

#### 任务清单

| 任务ID | 任务名称 | 文件 | 说明 | 优先级 |
|--------|----------|------|------|--------|
| T1.1 | 定义消息数据结构 | `device_sim_service.py` | `QueueMessage` dataclass | P0 |
| T1.2 | 创建全局消息队列 | `device_sim_service.py` | `asyncio.Queue(maxsize=10000)` | P0 |
| T1.3 | 修改模拟器回调 | `device_sim_service.py` | 入队替代直接写入 | P0 |

#### 实施代码

```python
# device_sim_service.py

from dataclasses import dataclass
from datetime import datetime
import asyncio

@dataclass
class QueueMessage:
    device_id: str
    device_name: str
    device_type: str
    topic: str
    payload: dict
    direction: str
    status: str
    error_message: str
    timestamp: datetime

# 全局消息队列
_message_queue: asyncio.Queue = asyncio.Queue(maxsize=10000)

def _report_callback(self, device_id: str, topic: str, payload: dict,
                     direction: str = "publish", status: str = "success",
                     error_message: str = None):
    """模拟器回调（在模拟器线程中执行）— 只入队，不阻塞"""
    msg = QueueMessage(
        device_id=device_id,
        device_name=self.device_name,
        device_type=self.device_type,
        topic=topic,
        payload=payload,
        direction=direction,
        status=status,
        error_message=error_message,
        timestamp=datetime.now(),
    )
    try:
        _message_queue.put_nowait(msg)
    except asyncio.QueueFull:
        logger.warning(f"消息队列已满，丢弃消息: {device_id}")
```

---

### 2.2 消费循环实现

#### 任务清单

| 任务ID | 任务名称 | 文件 | 说明 | 优先级 |
|--------|----------|------|------|--------|
| T2.1 | 实现消费循环 | `device_sim_service.py` | 从队列取消息写入数据库 | P0 |
| T2.2 | WebSocket 推送集成 | `device_sim_service.py` | 消费时触发推送 | P0 |
| T2.3 | 启动消费循环 | `main.py` | lifespan 中 create_task | P0 |

#### 实施代码

```python
# device_sim_service.py

async def _message_consumer():
    """消息消费循环（在主线程异步循环中运行）"""
    while True:
        try:
            msg: QueueMessage = await _message_queue.get()
            
            # 写入数据库
            log = ReportLog(
                id=generate_id("log"),
                device_id=msg.device_id,
                topic=msg.topic,
                payload=msg.payload,
                direction=msg.direction,
                status=msg.status,
                error_message=msg.error_message,
            )
            # 需要获取数据库 session
            
            # WebSocket 推送
            await _broadcast_device_message({
                "event": "device_message",
                "data": {...}
            })
            
        except Exception as e:
            logger.error(f"消息消费异常: {e}")
            await asyncio.sleep(1)
```

```python
# main.py

@asynccontextmanager
async def lifespan(app: FastAPI):
    # ... 现有初始化代码 ...
    
    # 启动消息消费循环
    from app.services.device_sim_service import _message_consumer
    consumer_task = asyncio.create_task(_message_consumer())
    
    yield
    
    # 清理
    consumer_task.cancel()
```

---

### 2.3 WebSocket 推送修复

#### 任务清单

| 任务ID | 任务名称 | 文件 | 说明 | 优先级 |
|--------|----------|------|------|--------|
| T3.1 | 调整推送逻辑 | `device_sim.py` | 移除线程不安全的回调 | P0 |
| T3.2 | 保留连接管理 | `device_sim.py` | WebSocket 端点和广播函数 | P0 |

---

## 三、技术要点

### 3.1 数据库 Session 问题

**问题**：消费循环需要数据库 session，但 `asyncio.Queue` 的消费者在不同上下文中。

**方案**：消费循环中使用 `async for` 从队列获取消息，然后通过 `get_db()` 获取新 session：

```python
async def _message_consumer():
    async with get_db_session() as db:
        while True:
            msg = await _message_queue.get()
            service = DeviceSimService(db)
            await service._create_report_log_from_queue(msg)
```

### 3.2 异常处理

消费循环中的异常不应中断循环，需捕获后继续：

```python
except Exception as e:
    logger.error(f"消息消费异常: {e}")
    await asyncio.sleep(1)  # 避免快速失败循环
    continue
```

---

## 四、部署方案

### 4.1 本地开发

```bash
cd backend
uvicorn app.main:app --reload --port 8001
```

### 4.2 服务器部署

```bash
# 本地构建
cd frontend && npm run build

# 上传到服务器
python deploy_script.py

# 重启容器
docker restart plat-auto-test-app
```

---

## 五、验收检查清单

| 验收项 | 检查方式 | 预期结果 |
|--------|---------|---------|
| 启动设备，查看消息流 | 打开门口屏模拟页面 | 消息实时显示 |
| 检查上报记录列表 | 刷新记录列表 | 显示新记录 |
| 检查 `totalReports` | 查看设备详情 | 数值正确 |
| 停止设备 | 点击停止按钮 | 消息流停止更新 |
| 查看服务器日志 | `docker logs` | 无 `Session's transaction` 错误 |

---

## 六、风险与应对

| 风险 | 影响 | 概率 | 应对方案 |
|------|------|------|---------|
| 消费循环异常 | 消息积压 | 低 | 捕获异常 + 队列长度限制 |
| 性能下降 | 延迟增加 | 低 | 监控队列长度，优化消费速度 |

---

*本文档为消息记录架构重构与实时消息流的执行计划，需在评审后实施。*