# 开发阶段二：MQTT 集成

## 1. 目标

*   在后端配置并建立与 MQTT Broker 的稳定连接。
*   实现 MQTT 消息的订阅 (接收机器人状态) 和发布 (发送控制指令)。
*   集成 `robot_task` 表逻辑，确保命令按顺序发送并跟踪状态。
*   实现任务超时处理机制。
*   (前端暂无重大变更，主要为后端集成工作)。

## 2. 先决条件

*   **MQTT Broker 信息**: 
    *   **地址**: 本项目将使用公共 MQTT Broker: `broker.emqx.io`
    *   **TCP 端口**: `1883`
    *   **连接认证**: 此公共 Broker 通常允许匿名连接，即连接时不需要提供用户名和密码。
*   **Topic 与 Payload 约定**: 必须与硬件团队最终确认 MQTT Topic 结构和 JSON Payload 格式。
    *   **Topic 唯一性**: 由于使用公共 Broker，Topic 必须包含项目唯一标识前缀 (例如: `yupi_mqtt_power_project/`)，以避免与其他用户冲突。最终结构如: `[项目唯一前缀]/robot/command/{clientId}` 和 `[项目唯一前缀]/robot/status/{clientId}`。
    *   **应用层鉴权**: 鉴于公共 Broker 的特性，强烈建议在消息 Payload层面实现应用层鉴权机制，以确保消息的合法性和安全性。例如，机器人上报状态时携带特定令牌，后端发送指令时也包含可供机器人验证的令牌或签名。

## 3. 后端开发详解

1.  **数据库初始化**:
    *   **创建 `robot_task` 表**: 执行 `development_plan.md` 中定义的 `robot_task` 表 DDL 语句。
        ```sql
        CREATE TABLE `robot_task` (
          `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
          `robot_id` VARCHAR(100) NOT NULL COMMENT '机器人ID',
          `command_type` VARCHAR(50) NOT NULL COMMENT '命令类型 (MOVE_TO_SPOT, START_CHARGE, STOP_CHARGE, QUERY_STATUS)',
          `command_payload` TEXT COMMENT '命令参数 (JSON格式)',
          `status` VARCHAR(50) NOT NULL DEFAULT 'PENDING' COMMENT '任务状态 (PENDING, SENT, ACKNOWLEDGED_SUCCESS, ACKNOWLEDGED_FAILURE, TIMED_OUT)',
          `sent_time` DATETIME COMMENT '命令发送时间',
          `ack_time` DATETIME COMMENT '命令确认时间',
          `related_session_id` BIGINT COMMENT '关联的充电会话ID (可选)',
          `error_message` TEXT COMMENT '失败或超时的错误信息',
          `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
          `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
          INDEX `idx_robot_status` (`robot_id`, `status`) COMMENT '机器人和状态索引，便于查询'
        ) COMMENT='机器人指令任务表';
        ```
2.  **配置 MQTT 连接 (`application.yml`)**: 
    *   添加 MQTT 相关配置项 (根据用户提供的信息填写):
      ```yaml
      mqtt:
        broker-url: tcp://broker.emqx.io:1883 # 公共 Broker 地址
        username: # 通常为空，此公共 Broker 无需连接认证
        password: # 通常为空
        client-id-prefix: backend-yupi-mqtt-power- # 项目唯一客户端ID前缀
        default-qos: 1 
        connection-timeout: 30
        keep-alive-interval: 60
        command-topic-base: yupi_mqtt_power_project/robot/command # 包含项目唯一前缀的指令Topic基础路径
        status-topic-base: yupi_mqtt_power_project/robot/status   # 包含项目唯一前缀的状态Topic基础路径
      ```
    *   创建 `src/main/java/com/yupi/project/config/properties/MqttProperties.java` 类，使用 `@ConfigurationProperties(prefix = "mqtt")` 和 Lombok (`@Data`) 来映射这些配置。
3.  **实现 MQTT 客户端 (`MqttConfig.java`)**:
    *   创建 `src/main/java/com/yupi/project/config/MqttConfig.java`。
    *   **注入 `MqttProperties`**。
    *   **创建 `MqttConnectOptions` Bean**:
        *   设置 `userName`, `password` (如果配置了)。
        *   设置 `isAutomaticReconnect(true)`。
        *   设置 `isCleanSession(true)` (或根据需要设置为 `false` 以接收离线消息)。
        *   设置 `connectionTimeout`, `keepAliveInterval`。
    *   **创建 `MqttClient` Bean**:
        *   生成唯一的 `clientId` (例如: `properties.getClientIdPrefix() + UUID.randomUUID().toString()`)。
        *   创建 `MqttClient` 实例 (`new MqttClient(properties.getBrokerUrl(), clientId, new MemoryPersistence())`)。
    *   **创建 `MqttCallbackExtended` Bean (核心)**:
        *   创建一个类实现 `MqttCallbackExtended` (例如, `MqttCallbackHandler`)。
        *   **注入 `MqttMessageHandler` 服务** (将在下一步创建)。
        *   实现 `connectComplete(boolean reconnect, String serverURI)` 方法:
            *   在此方法中订阅状态主题: `client.subscribe(properties.getStatusTopicBase() + "/+", properties.getDefaultQos());` (`+` 是单层通配符)。记录订阅成功日志。
            *   如果是重连 (`reconnect == true`)，可能需要执行一些状态同步逻辑（例如查询所有机器人的状态）。
        *   实现 `connectionLost(Throwable cause)` 方法:
            *   记录连接丢失的错误日志。
            *   Paho 客户端的 `automaticReconnect` 会尝试重连。
        *   实现 `messageArrived(String topic, MqttMessage message)` 方法:
            *   获取 `payload` (`new String(message.getPayload())`)。
            *   **应用层鉴权**: 在此步骤或 `mqttMessageHandler.handleStatusUpdate` 内部，应校验 `payload` 中携带的机器人身份标识或令牌，确保消息来源可靠。
            *   调用 `mqttMessageHandler.handleStatusUpdate(topic, payload)` 处理消息。
            *   **注意**: 此方法应快速完成，避免阻塞回调线程。复杂的处理应异步进行或在 `handleStatusUpdate` 内部处理。
        *   实现 `deliveryComplete(IMqttDeliveryToken token)` 方法 (可选): 消息发布完成后的回调，通常用于 QoS 1 或 2 的确认。
    *   **连接 MQTT Broker**: 
        *   为 `MqttClient` Bean 设置初始化方法 (`initMethod="connectBroker"`) 或使用 `@PostConstruct`。
        *   在连接方法中：
            *   设置 `MqttCallback` (`client.setCallback(mqttCallbackHandlerBean)`)。
            *   调用 `client.connect(mqttConnectOptionsBean)`。
            *   添加适当的异常处理 (如 `MqttException`)。
4.  **实现 `RobotTask` 管理 (`RobotTaskService`)**:
    *   创建 `src/main/java/com/yupi/project/model/entity/RobotTask.java`。
    *   创建 `src/main/java/com/yupi/project/mapper/RobotTaskMapper.java`。
    *   创建 `src/main/java/com/yupi/project/service/RobotTaskService.java` 接口，定义方法如：
        *   `hasPendingSentTask(String robotId)`: 检查是否有 `SENT` 状态的任务。
        *   `createTask(String robotId, String commandType, String payloadJson, Long sessionId)`: 创建 `PENDING` 任务。
        *   `markTaskAsSent(Long taskId)`: 更新任务状态为 `SENT`。
        *   `findSentTask(String robotId, String expectedResponseType)`: 根据机器人响应查找对应的 `SENT` 任务 (需要逻辑判断响应类型对应哪个命令)。
        *   `markTaskAsAcknowledged(Long taskId, boolean success, String errorMessage)`: 更新任务状态为 `ACKNOWLEDGED_SUCCESS` 或 `ACKNOWLEDGED_FAILURE`。
        *   `findAndMarkTimedOutTasks(int timeoutSeconds)`: 查找并标记超时任务。
    *   创建 `src/main/java/com/yupi/project/service/impl/RobotTaskServiceImpl.java` 实现类。
5.  **实现消息发布 (`MqttService`)**:
    *   创建 `src/main/java/com/yupi/project/service/MqttService.java` 接口，定义 `sendCommand(...)` 方法。
    *   创建 `src/main/java/com/yupi/project/service/impl/MqttServiceImpl.java` 实现类。
    *   注入 `MqttClient`, `MqttProperties`, `RobotTaskService`。
    *   实现 `sendCommand(...)` 方法:
        *   调用 `robotTaskService.hasPendingSentTask(robotId)` 检查。
        *   调用 `robotTaskService.createTask(...)` 创建任务。
        *   构造 Topic (`properties.getCommandTopicBase() + "/" + robotId`)。
        *   **应用层鉴权**: 构造 `payloadJson` 时，应包含可供机器人验证的令牌或签名，确保指令的合法性。
        *   调用 `mqttClient.publish(topic, payloadJson.getBytes(), properties.getDefaultQos(), false)` 发送。
        *   发送成功后，调用 `robotTaskService.markTaskAsSent(taskId)`。
        *   添加 `MqttException` 处理。
6.  **实现消息处理 (`MqttMessageHandler`)**:
    *   创建 `src/main/java/com/yupi/project/service/MqttMessageHandler.java` (接口和实现)。
    *   注入 `RobotTaskService`, `Gson` (或其他 JSON 库), 以及后续阶段需要的 Service (如 `ChargingService`, `ChargingRobotService`)。
    *   实现 `handleStatusUpdate(String topic, String payload)` 方法:
        *   解析 `topic` 提取 `robotId`。
        *   解析 `payload` (JSON) 为 `RobotStatusDTO` (需要创建此 DTO)。
        *   **核心**: 根据 `RobotStatusDTO` 的内容判断这是对哪个命令的响应 (或者是一个主动状态上报)。调用 `robotTaskService.findSentTask(...)` 查找对应的 `SENT` 任务。
        *   如果找到任务，调用 `robotTaskService.markTaskAsAcknowledged(...)` 更新任务状态。
        *   **只有在任务确认后**，才根据 `RobotStatusDTO.status` 调用后续业务逻辑 Service 的方法 (例如: `chargingService.handleRobotArrival(robotId, spotId)`, `chargingService.handleChargeCompletion(robotId, duration)`)。
        *   处理 JSON 解析异常、任务未找到等情况。
7.  **实现任务超时处理 (`TaskTimeoutHandler`)**: 
    *   创建 `src/main/java/com/yupi/project/schedule/TaskTimeoutHandler.java` (或放在 service 包)。
    *   注入 `RobotTaskService`。
    *   添加 `@Component` 和 `@EnableScheduling` (在主启动类或配置类上)。
    *   创建一个方法，使用 `@Scheduled(fixedRate = 60000)` (例如每分钟执行一次)。
    *   在该方法中：
        *   调用 `robotTaskService.findAndMarkTimedOutTasks(timeoutSeconds)` (例如超时设为 120 秒)。
        *   获取超时的任务列表。
        *   **注意**: 对每个超时任务，根据业务逻辑更新关联的 `charging_robot` 和 `charging_session` 状态的逻辑，将依赖于第三阶段实现的 `ChargingRobotService` 和 `ChargingSessionService`。本阶段仅标记 `RobotTask` 自身状态。
        *   添加日志记录。

## 4. 前端 (移动端 App) 开发详解

*   **本阶段前端无核心功能变更。**
*   可以考虑在界面上预留位置，用于未来展示机器人的连接状态或基本信息，但这依赖于后端是否通过 API 提供这些聚合信息，或者前端是否也直接订阅 MQTT (通常不推荐前端直接连 Broker)。
*   主要工作是等待后端完成 MQTT 集成，为下一阶段的核心业务功能提供基础。

## 5. 本阶段交付物

*   包含 `robot_task` 表的数据库。
*   配置完成并能在启动时自动连接 MQTT Broker 的后端服务。
*   实现 MQTT 状态消息订阅和命令消息发布的功能。
*   集成 `robot_task` 表的命令发送检查和状态跟踪逻辑。
*   实现任务超时检查和处理的定时任务。
*   必要的单元测试 (例如 `RobotTaskService` 的逻辑)。
*   描述 MQTT 配置和运行状态的简要文档或日志。

## 6. 注意事项

*   MQTT 连接信息 (特别是密码，如果未来使用私有 Broker) 应妥善保管，避免硬编码在代码中，使用配置文件管理。
*   **Topic 设计与唯一性**: 在公共 Broker 环境下，Topic 设计必须包含项目唯一标识前缀，以防止命名冲突。所有参与方（后端、机器人）都必须使用此约定。
*   **Payload 格式**: 必须严格遵守与硬件端的约定。
*   **应用层鉴权**: 由于使用公共 Broker，强烈建议在消息 Payload 中实现双向的应用层鉴权机制。后端需验证机器人状态的来源，机器人需验证指令的来源。
*   `MqttCallback` 中的 `messageArrived` 必须快速返回，避免阻塞。
*   超时时间的设置需要根据实际网络情况和机器人响应时间进行调整。
*   健壮的错误处理对于 MQTT 集成至关重要。 