Files
mqtt_power/springboot-init-main/doc/development_plan.md
2025-05-13 21:30:06 +08:00

233 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 的状态。
## 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. 按照开发阶段逐步推进。