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

15 KiB
Raw Blame History

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 的任务。如果有,则不允许发送,直接返回错误或特定状态。
      • 如果允许发送,调用 RobotTaskServicerobot_task 表创建 PENDING 状态的任务记录。
      • 构造实际的 MQTT Topic (robot/command/{robotId})
      • 调用 Paho 客户端的 publish 方法发送 MQTT 消息。
      • 发送成功后,立即调用 RobotTaskService 将对应任务状态更新为 SENT 并记录 sent_time
    • 创建 MqttMessageHandler.java (service/MqttMessageHandler.java 或类似名称)。
    • 提供 handleStatusUpdate(String topic, String payload) 方法,由 MqttCallbackmessageArrived 调用。
    • 在此方法中:
      • 解析 topic 获取 robotId
      • 使用 Gson (或 Jackson) 将 payload (JSON) 解析为 RobotStatusDTO
      • 根据收到的状态,调用 RobotTaskService 查找并更新对应的 SENT 任务状态为 ACKNOWLEDGED_SUCCESSACKNOWLEDGED_FAILURE,记录 ack_time
      • 在任务状态更新成功后,再根据 DTO 中的 status 字段,调用相应的业务逻辑(如 ChargingService 更新机器人状态、处理充电完成事件等)。

阶段三:核心充电业务逻辑

  1. 机器人与车位管理:
    • 创建 ChargingRobotParkingSpot 实体、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 记录为 pendingrobot_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_completeerror 消息的处理。
  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_robotcharging_session 的状态。

4. 数据库 Schema (初步 DDL)

-- 用户表
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. 按照开发阶段逐步推进。