diff --git a/LogBook.md b/LogBook.md index fc0a63f..5acc02b 100644 --- a/LogBook.md +++ b/LogBook.md @@ -124,4 +124,34 @@ - **前端接口路径修复**: - 修复了 `charging_web_app` 中 `my-sessions` 页面 (`charging_web_app/src/app/(authenticated)/my-sessions/page.tsx`) 获取用户充电记录列表的接口调用。 - 问题:原请求路径为 `/session/my/list/page`,缺少了 API 代理前缀 `/api`。 - - 解决:将请求路径修改为 `/api/session/my/list/page`,确保请求能正确通过 Next.js 代理到后端服务。 \ No newline at end of file + - 解决:将请求路径修改为 `/api/session/my/list/page`,确保请求能正确通过 Next.js 代理到后端服务。 + +## YYYY-MM-DD (请替换为今天的实际日期) +- **管理员会话管理功能方案调整**: + - 根据决策,从《第五阶段:管理员会话管理功能开发方案》(`springboot-init-main/doc/development_stages/stage_5_admin_session_management.md`) 中移除了管理员直接删除充电会话的功能。 + - 主要移除内容包括相关的后端 Service 方法、Controller 接口定义,以及前端对应的操作按钮和API调用封装。 + - 此举旨在降低误操作风险并保留完整的数据记录。 +- **前端管理员控制台界面调整**: + - 从管理员控制台页面 (`charging_web_app/src/app/(authenticated)/admin/dashboard/page.tsx`) 移除了"会话管理"导航卡片按钮,以匹配后端不再提供管理员直接删除会话功能的调整。 + +## YYYY-MM-DD (请替换为今天的实际日期) +- **MQTT 服务器域名更新**: + - 将后端 (`springboot-init-main/src/main/resources/application.yml`) 和单片机 (`mqtt_esp32_client/mqtt_esp32_client.ino`) 的 MQTT Broker 地址从 `broker.emqx.io` 更新为 `yuyun-hk1.stormrain.cn`。 + - 确保设备与自建 MQTT 服务器的通信正常进行。 + +## YYYY-MM-DD (请替换为今天的实际日期) +- **修复后端 MQTT 消息处理逻辑**: + - 修正 `springboot-init-main/src/main/java/com/yupi/project/service/impl/MqttMessageHandler.java`,确保在机器人完成 `MOVE_TO_SPOT` 任务后,能够正确调用 `ChargingSessionService.handleRobotArrival` 方法更新充电会话状态。 + - **具体变更**: + - 在 `MqttMessageHandler` 中注入 `ChargingSessionService`。 + - 在 `handleRobotStatusUpdate` 方法中,当接收到 `COMPLETED` 状态的 `RobotTask` 消息,并且该任务的 `commandType` 为 `MOVE_TO_SPOT` 时,调用 `chargingSessionService.handleRobotArrival`,传入相关的会话 ID 和任务 ID。 + +## YYYY-MM-DD (请替换为今天的实际日期) +- **修复单片机 MQTT ACK 消息格式**: + - 修改 `mqtt_esp32_client/mqtt_esp32_client.ino`,使其发送的 ACK 消息符合后端 `MqttMessageHandlerImpl` 的期望格式。 + - **具体变更**: + - 删除旧的 `publish_ack_message` 函数。 + - 修改 `publish_status_update` 函数,使其只发送通用状态更新和心跳消息。 + - 新增一个 `publish_ack_message` 函数,用于构建包含 `command_ack`(指令中文描述)、`task_id`(数字类型)和 `success`(布尔类型)的 JSON 结构。 + - 在 `STOP_CHARGE` 指令的 ACK 中添加 `energy_kwh` 和 `duration_s` 字段。 + - 更新 `callback` 函数中所有 `publish_ack_message` 的调用,以使用新的函数签名和参数。 \ No newline at end of file diff --git a/mqtt_esp32_client/mqtt_esp32_client.ino b/mqtt_esp32_client/mqtt_esp32_client.ino index cc98bfe..3adab7e 100644 --- a/mqtt_esp32_client/mqtt_esp32_client.ino +++ b/mqtt_esp32_client/mqtt_esp32_client.ino @@ -4,11 +4,11 @@ // ----------- 设备配置 (需要为您自己的设备和环境修改) ----------- // WiFi -const char *ssid = "UFI_DB50CD"; // 请输入您的 Wi-Fi 名称 -const char *password = "1349534012"; // 请输入您的 Wi-Fi 密码 +const char *ssid = "WEIHANG3718"; // 请输入您的 Wi-Fi 名称 +const char *password = "@05Tu146"; // 请输入您的 Wi-Fi 密码 // MQTT Broker -const char *mqtt_broker = "broker.emqx.io"; // 您的 MQTT Broker 地址 +const char *mqtt_broker = "yuyun-hk1.stormrain.cn"; // 您的 MQTT Broker 地址 const char *mqtt_username = "emqx"; // 您的 MQTT 用户名 (如果需要) const char *mqtt_password = "public"; // 您的 MQTT 密码 (如果需要) const int mqtt_port = 1883; // 您的 MQTT 端口 @@ -130,7 +130,7 @@ void callback(char *topic, byte *payload, unsigned int length) { if (cmdType == nullptr || taskId == nullptr) { Serial.println("指令JSON缺少 commandType/command 或 taskId 字段。"); - publish_ack_message(taskId, false, "Command JSON invalid (missing commandType/command or taskId)", nullptr); + publish_ack_message(0, "指令解析失败", false, "Command JSON invalid (missing commandType/command or taskId)"); return; } @@ -170,7 +170,7 @@ void callback(char *topic, byte *payload, unsigned int length) { chargeStartTimeMillis = millis(); Serial.println("状态更新: CHARGING (已到达目标车位 " + currentSpotId + ",视为开始充电)"); - publish_ack_message(taskId, true, "Robot arrived at spot and started charging (simulated)", currentSpotId.c_str()); + publish_ack_message(taskId, "移动到指定点", true, "Robot arrived at spot and started charging (simulated)"); publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); @@ -196,7 +196,7 @@ void callback(char *topic, byte *payload, unsigned int length) { currentSessionId = ""; } Serial.println("模拟: 充电已启动。会话ID: " + currentSessionId + ", 车位: " + currentSpotId); - publish_ack_message(taskId, true, "Charging started successfully", currentSessionId.c_str()); + publish_ack_message(taskId, "开始充电", true, "Charging started successfully"); publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); } else if (strcmp(cmdType, "STOP_CHARGE") == 0) { @@ -214,7 +214,7 @@ void callback(char *topic, byte *payload, unsigned int length) { currentSessionId = ""; // 在ACK中上报准确的充电时长,如果需要的话,可以通过修改 publish_ack_message 或在 message 字段中添加 // For now, the generic ACK is sent. - publish_ack_message(taskId, true, ("Charging stopped. Duration: " + String(chargeDuration) + "s").c_str(), previousSessionId.c_str()); + publish_ack_message(taskId, "停止充电", true, ("Charging stopped. Duration: " + String(chargeDuration) + "s").c_str(), currentEnergyConsumed, chargeDuration); publish_status_update(false, nullptr, nullptr, nullptr, nullptr, nullptr); } // Add other commandType handling here if needed, e.g., "QUERY_STATUS" @@ -225,7 +225,7 @@ void callback(char *topic, byte *payload, unsigned int length) { // } else { Serial.println("未知指令 commandType: " + String(cmdType)); - publish_ack_message(taskId, false, ("Unknown commandType: " + String(cmdType)).c_str(), nullptr); + publish_ack_message(taskId, "未知指令", false, ("Unknown commandType: " + String(cmdType)).c_str()); } Serial.println("-----------------------"); } @@ -323,18 +323,30 @@ void publish_heartbeat() { lastHeartbeatTime = millis(); } -// Simplified ACK message function -void publish_ack_message(const char* taskId, bool success, const char* message, const char* contextInfo) { - if (!taskId || strlen(taskId) == 0) { - Serial.println("无法发送ACK: taskId 为空"); - return; +// 新增ACK消息发布函数,以符合后端期望的格式 +void publish_ack_message(long taskId, const char* commandAckStr, bool success, const char* message, float energyKwh = -1.0f, int durationSeconds = -1) { + StaticJsonDocument<256> doc; // 调整大小以适应所有字段 + + doc["robotUid"] = spotUid; + doc["command_ack"] = commandAckStr; // 指令的中文描述 + doc["task_id"] = taskId; // 任务ID (数字类型) + doc["success"] = success; // 成功状态 (布尔类型) + + if (message && strlen(message) > 0) { + doc["message"] = message; } - // Use the main publish_status_update function formatted as an ACK - // For contextInfo, we can pass spotId if relevant, or sessionId if that's what backend expects for ACKs. - // The 'true' indicates it's an ACK. - // The ackErrorCode field in publish_status_update will be set to "SUCCESS_ACK" or "FAILURE_ACK" - // 根据用户要求,ACK中的errorCode也暂时简化或移除。如果保留,确保含义清晰。 - publish_status_update(true, taskId, success ? "SUCCESS" : "FAILURE", message, nullptr, contextInfo); + + // 针对 STOP_CHARGE 指令的额外字段 + if (strcmp(commandAckStr, "停止充电") == 0) { + if (energyKwh >= 0) { + doc["energy_kwh"] = energyKwh; + } + if (durationSeconds >= 0) { + doc["duration_s"] = durationSeconds; + } + } + + publish_message(topic_uplink_to_backend, doc, "ACK"); } void setup() { diff --git a/springboot-init-main/doc/development_stages/stage_5_admin_session_management.md b/springboot-init-main/doc/development_stages/stage_5_admin_session_management.md index 86414e1..9823948 100644 --- a/springboot-init-main/doc/development_stages/stage_5_admin_session_management.md +++ b/springboot-init-main/doc/development_stages/stage_5_admin_session_management.md @@ -14,9 +14,6 @@ * **更新 (Update)**: * **谨慎引入**。主要考虑场景:对异常会话进行状态纠正(例如,系统未能自动更新的会话,管理员可手动标记为 `ERROR` 或 `CANCELLED_BY_SYSTEM`)。 * 所有手动更新操作必须记录详细的操作审计日志(操作人、时间、原因、变更前后状态)。 -* **删除 (Delete)**: - * **谨慎引入**。通常仅支持逻辑删除,用于隐藏无效或测试产生的会话数据。 - * 物理删除会话记录通常不推荐,以保证数据完整性和可追溯性。 * **创建 (Create)**: * 管理员**不直接创建**充电会话。会话由用户通过前端发起,或由系统在特定业务流程中自动创建。 @@ -81,10 +78,6 @@ * 更新会话状态、`update_time`。 * 记录操作日志(可考虑引入操作日志表或利用现有日志框架)。 * `adminUserId` 用于审计。 -* **Delete (删除)**: - * `boolean adminMarkSessionAsDeleted(Long sessionId, Long adminUserId)`: - * 逻辑删除,设置 `is_deleted = 1`。 - * 记录操作日志。 ### 2.3. Controller 层 (`com.yupi.project.controller.ChargingSessionAdminController`) * `POST /api/admin/session/list/page`: (已存在,需审视是否需要修改请求体和响应体以满足 `ChargingSessionAdminQueryRequest` 和 `Page`) @@ -98,9 +91,6 @@ * 调用 `chargingSessionService.adminUpdateSessionStatus(sessionId, newStatus, reason, adminUserId)`。 * `adminUserId` 从当前登录用户上下文获取。 * 权限: `@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)`。 -* `DELETE /api/admin/session/{sessionId}`: - * 调用 `chargingSessionService.adminMarkSessionAsDeleted(sessionId, adminUserId)`。 - * 权限: `@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)`。 ## 3. 前端实现 (`charging_web_app`) @@ -128,7 +118,6 @@ * **操作列**: * "详情"按钮:点击打开会话详情模态框。 * "修改状态"按钮 (如果实现该功能):打开状态修改模态框。 - * "删除"按钮 (如果实现该功能):触发删除确认。 * **查询筛选**: * 表单元素对应 `ChargingSessionAdminQueryRequest` 中的筛选字段。 * "查询"按钮触发 API 请求,更新表格数据。 @@ -139,9 +128,6 @@ * **修改状态 (Modal)** (如果实现): * 表单包含可选的新状态和操作原因。 * 提交时调用后端 `/api/admin/session/{sessionId}/status` 接口。 -* **删除会话** (如果实现): - * 使用 `Popconfirm` 提示。 - * 确认后调用后端 `DELETE /api/admin/session/{sessionId}` 接口。 ### 3.4. API 服务层 * 在 `charging_web_app/src/services/` 目录下创建 `sessionAdminService.ts` (或类似名称)。 @@ -149,13 +135,14 @@ * `fetchAdminSessions(query: ChargingSessionAdminQueryRequest): Promise>>` * `fetchAdminSessionDetails(sessionId: number): Promise>` * `updateAdminSessionStatus(sessionId: number, data: ChargingSessionAdminUpdateStatusRequest): Promise>` - * `deleteAdminSession(sessionId: number): Promise>` ### 3.5. 状态管理 (React Context / Zustand / Redux Toolkit - 根据项目现有方式) * 管理会话列表数据、加载状态、错误信息。 * 管理分页参数 (`current`, `pageSize`, `total`)。 * 管理筛选表单的条件。 * 管理模态框的显示/隐藏状态及当前操作的会话数据。 +* 状态更新操作的业务逻辑和数据一致性。 +* 并发操作下的数据准确性(如果适用)。 ## 4. 数据库变更 * 当前 `charging_session` 表结构基本满足需求。 @@ -166,12 +153,12 @@ * 各 API 接口的参数校验、权限控制。 * 筛选条件(特别是组合条件和边界值)的正确性。 * 分页和排序逻辑的正确性。 - * 状态更新和删除操作的业务逻辑和数据一致性。 + * 状态更新操作的业务逻辑和数据一致性。 * 并发操作下的数据准确性(如果适用)。 * **前端**: * 页面数据能否正确加载和展示。 * 查询、分页、排序功能是否按预期工作。 - * 操作按钮(详情、修改、删除)的交互和功能是否正常。 + * 操作按钮(详情、修改状态)的交互和功能是否正常。 * 表单校验和用户提示。 * 不同屏幕尺寸下的响应式布局。 * **整体**: diff --git a/springboot-init-main/src/main/resources/application.yml b/springboot-init-main/src/main/resources/application.yml index 19af3d9..4043fce 100644 --- a/springboot-init-main/src/main/resources/application.yml +++ b/springboot-init-main/src/main/resources/application.yml @@ -51,7 +51,7 @@ logging: # MQTT Configurations # =================================================================== mqtt: - broker-url: tcp://broker.emqx.io:1883 + broker-url: tcp://yuyun-hk1.stormrain.cn:1883 username: # Public broker, no credentials specified for connection password: # Public broker, no credentials specified for connection client-id-prefix: backend-yupi-mqtt-power- # Unique client ID prefix for our project