# MQTT 智能充电机器人系统 - 开发方案

## 1. 引言

本文档旨在为 "MQTT 智能充电机器人系统" 提供详细的开发方案。该方案基于已确认的需求文档 (`requirements.md`) 和技术选型，旨在指导后端平台的开发工作。项目目标是构建一个稳定、可扩展的后端服务，用于管理用户、调度充电机器人、处理充电业务逻辑，并通过 MQTT 与机器人硬件进行通信。

## 2. 系统架构概览

系统采用典型的 **前后端分离 + 物联网 (IoT)** 架构：

*   **后端平台 (本项目核心)**: 基于 Spring Boot，负责业务逻辑处理、数据持久化、用户认证授权、与 MQTT Broker 交互。
*   **MQTT Broker**: 外部公共服务（如 EMQX），作为消息中间件，负责平台与机器人之间的消息传递。**需要用户提供具体的连接信息和认证凭据。**
*   **充电机器人 (硬件)**: 基于 ESP32-CAM，负责执行指令、采集状态、通过 MQTT 与 Broker 通信。**本项目不涉及硬件的具体开发。**
*   **前端应用 (可选)**: Web 或 App，通过调用后端 API 与系统交互。**本项目不包含前端开发。**

**关键技术栈:**

*   **后端**: Spring Boot 2.7.0, Java 1.8
*   **数据访问**: MyBatis-Plus, MySQL
*   **缓存**: Redis (用于 Session 管理等)
*   **消息队列**: MQTT (使用 Eclipse Paho Java Client)
*   **API 文档**: Knife4j

## 3. 开发阶段与模块划分

我们将开发过程划分为以下几个主要阶段/模块：

**阶段一：基础架构与用户管理**

1.  **环境配置**: 确认 JDK, Maven, MySQL, Redis 环境。
2.  **数据库初始化**:
    *   根据 `requirements.md` 中的设计，创建数据库 `mqtt_charging_system` (或使用现有 `my_db` 并调整)。
    *   编写并执行 SQL DDL 脚本，创建 `user`, `charging_robot`, `parking_spot`, `charging_session`, `activation_code` 表（详细见第 4 节）。
3.  **用户模块实现**:
    *   创建 `User` 实体类 (`model/entity/User.java`)，包含余额、角色等字段。
    *   创建 `UserMapper` 接口 (`mapper/UserMapper.java`)。
    *   创建 `UserService` 接口及实现 (`service/UserService.java`, `service/impl/UserServiceImpl.java`)，包含用户注册、查询、余额更新等方法。
    *   实现密码存储：使用 Spring Security 的 `BCryptPasswordEncoder` 对密码进行加密。在 `config` 包下配置 `PasswordEncoder` Bean。
    *   创建 `UserController` (`controller/UserController.java`)，提供登录接口。
4.  **认证与授权**:
    *   实现登录接口 `/api/user/login`。
    *   登录成功后，使用 Spring Session (已配置为 Redis 存储) 存储用户会话信息。
    *   后续请求通过 Session 进行用户身份验证。
    *   实现简单的基于角色的访问控制：在需要权限的 Controller 方法或 Service 方法上，通过检查当前登录用户的 `role` 字段进行判断。可以创建一个简单的 AOP 切面或直接在方法内判断。

**阶段二：MQTT 集成**

1.  **配置 MQTT 连接**:
    *   在 `application.yml` 中添加 MQTT Broker 的 `url`, `username`, `password`, 以及用于命令和状态的 `commandTopic`, `statusTopic` 基础路径。**这些值需要用户提供。**
    *   创建 `MqttProperties.java` (`config/properties/MqttProperties.java`) 类，使用 `@ConfigurationProperties` 读取配置。
2.  **实现 MQTT 客户端**:
    *   创建 `MqttConfig.java` (`config/MqttConfig.java`)。
    *   配置 `MqttConnectOptions` Bean，设置用户名、密码、自动重连、清理会话等。
    *   配置 `MqttClient` Bean，并在应用启动后连接到 Broker。
    *   **重点**: 实现 `MqttCallbackExtended` 接口，处理连接成功 (`connectComplete`)、连接丢失 (`connectionLost`)、消息到达 (`messageArrived`) 事件。
    *   在 `connectComplete` 中，订阅状态 Topic (`robot/status/+`)。`+` 是通配符，用于接收所有机器人的状态。
    *   在 `connectionLost` 中，记录日志并依赖 Paho 客户端的自动重连机制。
    *   在 `messageArrived` 中，将接收到的消息（JSON 字符串）传递给专门的消息处理服务。
3.  **实现消息发布与处理**:
    *   创建 `RobotTaskService.java` (`service/RobotTaskService.java`) 用于管理 `robot_task` 表的 CRUD 和状态检查。
    *   创建 `MqttService.java` (`service/MqttService.java`)。
    *   提供 `sendCommand(String robotId, String commandType, String payloadJson, Long sessionId)` 方法：
        *   **调用 `RobotTaskService` 检查 `robotId` 是否有状态为 `SENT` 的任务。如果有，则不允许发送，直接返回错误或特定状态。**
        *   如果允许发送，**调用 `RobotTaskService` 在 `robot_task` 表创建 `PENDING` 状态的任务记录。**
        *   构造实际的 MQTT Topic (`robot/command/{robotId}`)
        *   调用 Paho 客户端的 `publish` 方法发送 MQTT 消息。
        *   **发送成功后，立即调用 `RobotTaskService` 将对应任务状态更新为 `SENT` 并记录 `sent_time`。**
    *   创建 `MqttMessageHandler.java` (`service/MqttMessageHandler.java` 或类似名称)。
    *   提供 `handleStatusUpdate(String topic, String payload)` 方法，由 `MqttCallback` 的 `messageArrived` 调用。
    *   在此方法中：
        *   解析 `topic` 获取 `robotId`。
        *   使用 Gson (或 Jackson) 将 `payload` (JSON) 解析为 `RobotStatusDTO`。
        *   **根据收到的状态，调用 `RobotTaskService` 查找并更新对应的 `SENT` 任务状态为 `ACKNOWLEDGED_SUCCESS` 或 `ACKNOWLEDGED_FAILURE`，记录 `ack_time`。**
        *   **在任务状态更新成功后**，再根据 DTO 中的 `status` 字段，调用相应的业务逻辑（如 `ChargingService` 更新机器人状态、处理充电完成事件等）。

**阶段三：核心充电业务逻辑**

1.  **机器人与车位管理**:
    *   创建 `ChargingRobot` 和 `ParkingSpot` 实体、Mapper、Service、Controller。
    *   提供基础的 CRUD 接口供管理员使用（可选，根据 `requirements.md`）。
    *   `ChargingRobotService` 需要包含更新机器人状态（如 `idle`, `moving`, `charging`, `error`）和位置的方法。
2.  **充电会话管理**:
    *   创建 `ChargingSession` 实体、Mapper、Service。
3.  **充电流程实现**:
    *   创建 `ChargingController.java` (`controller/ChargingController.java`)。
    *   实现 `/api/charging/request` 接口：
        *   接收用户请求（包含 `spotId`）。
        *   验证用户登录状态和余额。
        *   调用 `ChargingRobotService` 查找可用机器人。
        *   **调用 `MqttService.sendCommand` 发送 `move_to_spot` 指令 (该方法内部会检查任务表并创建任务记录)。如果返回错误（机器人忙），则告知用户。**
        *   （可选）创建或更新 `ChargingSession` 记录为 `pending` 或 `robot_moving` 状态。
    *   在 `MqttMessageHandler` 中处理状态更新：
        *   **首先更新 `robot_task` 表状态。**
        *   `arrived_at_spot` (确认 `move_to_spot` 成功后): 更新机器人DB状态，**调用 `MqttService.sendCommand` 发送 `start_charge` 指令。**
        *   `charging` (确认 `start_charge` 可能隐含的响应，或只是状态更新): 记录开始时间，更新 `ChargingSession`。
        *   `charge_complete` (确认充电结束，可能是主动上报或响应 `stop_charge`): 记录结束时间、时长，**触发计费**，更新用户余额，更新 `ChargingSession`。
        *   `error`: 记录错误，更新机器人和 `ChargingSession` 状态。
    *   实现 `/api/charging/stop` 接口（用户手动停止）：
        *   **调用 `MqttService.sendCommand` 发送 `stop_charge` 指令。**
        *   后续处理逻辑依赖于 `charge_complete` 或 `error` 消息的处理。
4.  **计费逻辑**:
    *   在 `UserService` 或单独的 `BillingService` 中实现计费方法。
    *   根据 `ChargingSession` 的总时长和预设的单价计算费用。
    *   调用 `UserService` 更新用户余额 (注意并发安全，可使用数据库行锁或乐观锁)。

**阶段四：激活码与完善**

1.  **激活码模块**:
    *   创建 `ActivationCode` 实体、Mapper、Service。
    *   实现激活码生成逻辑（管理员功能，可在 Service 中提供）。
    *   创建 `ActivationCodeController.java` (`controller/ActivationCodeController.java`)。
    *   实现 `/api/codes/redeem` 接口：接收激活码，验证有效性，调用 `UserService` 更新用户余额，将激活码标记为已使用。
2.  **API 文档**:
    *   在所有 Controller 和 DTO 上添加 Knife4j (Swagger) 注解 (`@Api`, `@ApiOperation`, `@ApiModelProperty` 等)。
3.  **测试**:
    *   编写单元测试 (JUnit) 覆盖 Service 层核心逻辑。
    *   进行接口测试（使用 Postman 或类似工具）。
    *   **难点**: MQTT 交互的测试，可能需要 Mock `MqttClient` 或搭建本地 MQTT Broker 进行集成测试。
4.  **错误处理与日志**:
    *   完善全局异常处理 (`GlobalExceptionHandler.java`)。
    *   在关键业务点添加详细日志（使用 SLF4j）。
    *   **新增：实现任务超时处理逻辑。**
        *   创建 `TaskTimeoutHandler.java` (`service/TaskTimeoutHandler.java` 或类似名称)。
        *   使用 `@Scheduled` 注解创建一个定时任务，定期执行。
        *   在定时任务中，调用 `RobotTaskService` 查找状态为 `SENT` 且超时的任务。
        *   对于超时的任务，更新其状态为 `TIMED_OUT`，记录错误信息。
        *   根据业务需要，可能需要同步更新关联的 `charging_robot` 和 `charging_session` 的状态。

### 阶段 3: 管理端核心功能完善 与 前后端数据对接 (进行中)

**目标**: 搭建核心管理页面框架，并将部分用户端和管理端页面与真实后端数据对接。

**进行中 / 即将进行:**

1.  **用户仪表盘 (`/dashboard`) 数据对接 (规划中)**
    *   **目的**: 将用户仪表盘的模拟数据替换为真实的后端数据，包括"当前充电状态"和"用户统计数据"（本月充电次数、本月总消费）。
    *   **后端接口需求**:
        *   `GET /api/session/my/active`: 获取用户当前激活的充电会话。若无激活会话，返回 null 或明确的空状态。
            *   **Controller**: `ChargingSessionController`
            *   **Service**: 查询用户状态为进行中（如 `CHARGING_IN_PROGRESS`）的会话。
        *   `GET /api/user/stats/mine`: 获取当前登录用户的统计数据。
            *   **响应示例**: `{ "monthlyCharges": 15, "monthlySpending": 250.75 }`
            *   **Controller**: `UserController` 或新的 `UserStatsController`
            *   **Service**: 计算用户本月已完成/已支付的充电次数和总消费。
    *   **前端修改 (`dashboard/page.tsx`)**:
        *   移除相关模拟数据加载逻辑。
        *   定义与后端响应匹配的数据接口。
        *   使用 `axios` (api 实例) 并行调用上述两个接口。
        *   根据真实数据更新UI组件的显示。
        *   处理API错误和加载状态。
    *   **状态**: 等待后端接口实现。

2.  **"我的充电记录" (`/my-sessions`) 页面数据对接 (已完成)**
    *   前端已修改为调用 `POST /api/session/my/list/page` 接口。
    *   后端 `ChargingSessionController` 和 `ChargingSessionVO` 已确认基本满足需求。
    *   **状态**: 已完成，等待用户测试确认。

3.  **管理员仪表盘 (`/admin/dashboard`) 框架 (已完成)**
    *   包含系统概览统计卡片（模拟数据）和管理模块导航卡片。
    *   **状态**: 初步完成，后续需对接真实统计数据和启用所有导航链接。

4.  **机器人管理页面 (`/admin/robots`) 框架 (已完成)**
    *   包含列表展示、搜索、筛选、分页（模拟数据）。
    *   模拟的添加、编辑、删除按钮。
    *   **状态**: 初步完成，后续需对接真实数据和实现CRUD操作。

## 4. 数据库 Schema (初步 DDL)

```sql
-- 用户表
CREATE TABLE `user` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
  `username` VARCHAR(255) NOT NULL UNIQUE COMMENT '用户名',
  `password` VARCHAR(255) NOT NULL COMMENT '密码 (加密存储)',
  `role` VARCHAR(50) NOT NULL DEFAULT 'user' COMMENT '角色 (user/admin)',
  `balance` DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '账户余额',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除标志 (0:未删, 1:已删)'
) COMMENT='用户表';

-- 充电机器人表
CREATE TABLE `charging_robot` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
  `robot_id` VARCHAR(100) NOT NULL UNIQUE COMMENT '机器人唯一标识符 (对应MQTT clientId)',
  `status` VARCHAR(50) DEFAULT 'idle' COMMENT '状态 (idle, moving, charging, error, offline)',
  `location` VARCHAR(255) COMMENT '当前位置描述 (如 base, near_P001, P001)',
  `battery_level` INT COMMENT '电量百分比',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除标志'
) COMMENT='充电机器人表';

-- 车位表
CREATE TABLE `parking_spot` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
  `spot_id` VARCHAR(100) NOT NULL UNIQUE COMMENT '车位唯一标识符',
  `location_desc` VARCHAR(255) COMMENT '位置描述',
  `status` VARCHAR(50) DEFAULT 'available' COMMENT '状态 (available, occupied, maintenance)',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除标志'
) COMMENT='车位表';

-- 充电记录表
CREATE TABLE `charging_session` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `robot_id` VARCHAR(100) NOT NULL COMMENT '机器人ID',
  `spot_id` VARCHAR(100) NOT NULL COMMENT '车位ID',
  `start_time` DATETIME COMMENT '充电开始时间',
  `end_time` DATETIME COMMENT '充电结束时间',
  `duration_seconds` INT COMMENT '总充电时长 (秒)',
  `cost` DECIMAL(10, 2) COMMENT '本次消费金额',
  `status` VARCHAR(50) COMMENT '会话状态 (pending, robot_moving, charging, completed, error, cancelled)',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间'
) COMMENT='充电记录表';

-- 激活码表
CREATE TABLE `activation_code` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
  `code` VARCHAR(100) NOT NULL UNIQUE COMMENT '激活码字符串',
  `value` DECIMAL(10, 2) NOT NULL COMMENT '充值金额',
  `is_used` TINYINT(1) DEFAULT 0 COMMENT '是否已使用 (0:未使用, 1:已使用)',
  `user_id` BIGINT COMMENT '使用者ID (使用后)',
  `use_time` DATETIME COMMENT '使用时间',
  `expire_time` DATETIME COMMENT '过期时间 (可选)',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT='激活码表';

```
*注：以上 DDL 仅为初步设计，字段类型、长度、索引等可能需要根据实际情况调整。*

## 5. API 设计 (高层规划)

*   **认证**:
    *   `POST /api/user/login`
    *   `POST /api/user/logout` (可选, 使 Session 失效)
*   **用户**:
    *   `GET /api/user/current` (获取当前用户信息，包括余额)
    *   `GET /api/users` (管理员 - 获取用户列表)
*   **充电**:
    *   `POST /api/charging/request` (用户 - 发起充电请求，参数: `spotId`)
    *   `POST /api/charging/stop` (用户 - 请求停止当前充电)
    *   `GET /api/charging/history` (用户 - 查看自己的充电记录)
    *   `GET /api/charging/sessions` (管理员 - 查看所有充电记录)
*   **激活码**:
    *   `POST /api/codes/redeem` (用户 - 使用激活码充值，参数: `code`)
    *   `POST /api/codes` (管理员 - 生成激活码，可选)
*   **管理 (可选)**:
    *   `GET /api/robots`
    *   `POST /api/robots`
    *   `GET /api/spots`
    *   `POST /api/spots`

## 6. MQTT 消息契约

再次强调，`requirements.md` 中定义的 MQTT Topic 和 Payload 结构为 **关键接口**。必须与硬件开发团队 **最终确认并严格遵守** 此约定。任何变动都需要双方同步更新。

## 7. 后续步骤

1.  **等待用户提供**:
    *   MQTT Broker 的详细连接信息 (URL, Port)。
    *   用于指令 (`robot/command/{clientId}`) 和状态 (`robot/status/{clientId}`) Topic 的用户名和密码。
    *   与硬件团队确认最终的 MQTT Topic 和 Payload 结构。
2.  **开始开发**: 在获取 MQTT 信息后，可以并行开始：
    *   **阶段一**: 数据库初始化、用户模块开发、`robot_task` 相关基础 Service 开发。
    *   **阶段二**: MQTT 配置、基础连接、订阅实现、集成 `robot_task` 检查与更新逻辑、任务超时处理实现。
3.  按照开发阶段逐步推进。 