改用自建emqx
This commit is contained in:
32
LogBook.md
32
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 代理到后端服务。
|
||||
- 解决:将请求路径修改为 `/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` 的调用,以使用新的函数签名和参数。
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<ChargingSessionAdminVO>`)
|
||||
@@ -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<BaseResponse<Page<ChargingSessionAdminVO>>>`
|
||||
* `fetchAdminSessionDetails(sessionId: number): Promise<BaseResponse<ChargingSessionAdminVO>>`
|
||||
* `updateAdminSessionStatus(sessionId: number, data: ChargingSessionAdminUpdateStatusRequest): Promise<BaseResponse<any>>`
|
||||
* `deleteAdminSession(sessionId: number): Promise<BaseResponse<any>>`
|
||||
|
||||
### 3.5. 状态管理 (React Context / Zustand / Redux Toolkit - 根据项目现有方式)
|
||||
* 管理会话列表数据、加载状态、错误信息。
|
||||
* 管理分页参数 (`current`, `pageSize`, `total`)。
|
||||
* 管理筛选表单的条件。
|
||||
* 管理模态框的显示/隐藏状态及当前操作的会话数据。
|
||||
* 状态更新操作的业务逻辑和数据一致性。
|
||||
* 并发操作下的数据准确性(如果适用)。
|
||||
|
||||
## 4. 数据库变更
|
||||
* 当前 `charging_session` 表结构基本满足需求。
|
||||
@@ -166,12 +153,12 @@
|
||||
* 各 API 接口的参数校验、权限控制。
|
||||
* 筛选条件(特别是组合条件和边界值)的正确性。
|
||||
* 分页和排序逻辑的正确性。
|
||||
* 状态更新和删除操作的业务逻辑和数据一致性。
|
||||
* 状态更新操作的业务逻辑和数据一致性。
|
||||
* 并发操作下的数据准确性(如果适用)。
|
||||
* **前端**:
|
||||
* 页面数据能否正确加载和展示。
|
||||
* 查询、分页、排序功能是否按预期工作。
|
||||
* 操作按钮(详情、修改、删除)的交互和功能是否正常。
|
||||
* 操作按钮(详情、修改状态)的交互和功能是否正常。
|
||||
* 表单校验和用户提示。
|
||||
* 不同屏幕尺寸下的响应式布局。
|
||||
* **整体**:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user