diff --git a/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/dal/redis/clean/BadgeDeviceStatusRedisDAO.java b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/dal/redis/clean/BadgeDeviceStatusRedisDAO.java new file mode 100644 index 0000000..4715c02 --- /dev/null +++ b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/dal/redis/clean/BadgeDeviceStatusRedisDAO.java @@ -0,0 +1,152 @@ +package com.viewsh.module.iot.dal.redis.clean; + +import jakarta.annotation.Resource; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * 工牌设备状态 Redis DAO(只读) + *

+ * 从 Redis 读取由 Ops 模块写入的工牌设备状态信息 + * Key: ops:badge:status:{deviceId} + * 数据结构: Hash + *

+ * IoT 模块只读取此数据,��操作由 Ops 模块的 BadgeDeviceStatusService 处理 + * + * @author AI + */ +@Repository +public class BadgeDeviceStatusRedisDAO { + + /** + * 工牌状态 Key 模式 + *

+ * 格式:ops:badge:status:{deviceId} + */ + private static final String BADGE_STATUS_KEY_PATTERN = "ops:badge:status:%s"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 获取设备当前工单信息 + * + * @param deviceId 设备ID + * @return 工单信息,如果不存在返回 null + */ + public OrderInfo getCurrentOrder(Long deviceId) { + String key = formatKey(deviceId); + Map map = stringRedisTemplate.opsForHash().entries(key); + + if (map == null || map.isEmpty()) { + return null; + } + + // 提取工单相关字段 + OrderInfo orderInfo = new OrderInfo(); + orderInfo.setOrderId(getLong(map.get("currentOpsOrderId"))); + orderInfo.setStatus((String) map.get("currentOrderStatus")); + orderInfo.setAreaId(getLong(map.get("currentAreaId"))); + orderInfo.setBeaconMac((String) map.get("beaconMac")); + + // 如果没有工单ID,返回 null + if (orderInfo.getOrderId() == null) { + return null; + } + + return orderInfo; + } + + /** + * 获取设备完整状态信息(原始 Map) + * + * @param deviceId 设备ID + * @return 状态信息 Map,如果不存在返回空 Map + */ + public Map getBadgeStatus(Long deviceId) { + String key = formatKey(deviceId); + Map map = stringRedisTemplate.opsForHash().entries(key); + + if (map == null || map.isEmpty()) { + return new HashMap<>(); + } + + Map result = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + result.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); + } + return result; + } + + /** + * 检查设备是否有当前工单 + * + * @param deviceId 设备ID + * @return 是否有工单 + */ + public boolean hasCurrentOrder(Long deviceId) { + String key = formatKey(deviceId); + return Boolean.TRUE.equals(stringRedisTemplate.opsForHash().hasKey(key, "currentOpsOrderId")); + } + + /** + * 获取 Hash 中的 Long 字段值 + */ + private Long getLong(Object value) { + if (value == null) { + return null; + } + if (value instanceof Long) { + return (Long) value; + } + try { + return Long.parseLong(value.toString()); + } catch (Exception e) { + return null; + } + } + + /** + * 格式化 Redis Key + */ + private static String formatKey(Long deviceId) { + return String.format(BADGE_STATUS_KEY_PATTERN, deviceId); + } + + /** + * 工单信息(精简 DTO) + *

+ * 只包含 IoT 模块需要的工单字段 + */ + @Getter + @Setter + public static class OrderInfo { + /** + * 工单ID + */ + private Long orderId; + + /** + * 工单状态 + *

+ * DISPATCHED/ARRIVED/PAUSED/COMPLETED + */ + private String status; + + /** + * 区域ID + */ + private Long areaId; + + /** + * 信标 MAC 地址 + */ + private String beaconMac; + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/constants/CleanNotificationConstants.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/constants/CleanNotificationConstants.java index 03a025f..ce548d0 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/constants/CleanNotificationConstants.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/constants/CleanNotificationConstants.java @@ -1,10 +1,12 @@ package com.viewsh.module.ops.environment.constants; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; + /** * 保洁业务通知消息常量 *

* 统一管理所有通知相关的消息内容: - * - 语音播报内容 + * - 语音播报内容(带参数的模板) * - 站内信模板代码 * - 站内信模板参数 *

@@ -12,6 +14,7 @@ package com.viewsh.module.ops.environment.constants; * - 集中管理便于国际化 * - 避免硬编码散落在各处 * - 修改消息只需改此处 + * - 语音播报提供简短版和详细版,根据场景选择 * * @author lzh */ @@ -62,49 +65,129 @@ public class CleanNotificationConstants { } /** - * 语音播报内容 + * 语音播报模板 + *

+ * 所有模板支持参数化,使用 %s, %d 等占位符 + * 提供简短版(适合快速通知)和详细版(包含更多信息) */ - public static class VoiceMessage { + public static class VoiceTemplate { + + // ==================== 新工单播报 ==================== /** - * 新工单播报 + * 新工单播报(简短版) */ - public static final String NEW_ORDER = "您有1条新的待办工单"; + public static final String NEW_ORDER_SHORT = "您有新的待办工单"; /** - * 待办增加播报 - * 参数: {queueCount} - 总待办数 + * 新工单播报(详细版) + * 参数: {areaName} - 区域名称 */ - public static final String QUEUED_ORDER_FORMAT = "新增%d项待办,您共有%d个待办工单"; + public static final String NEW_ORDER_DETAIL = "新工单已派发,作业区域:%s"; /** - * 简化的待办播报(用于快速通知) + * 新工单播报(完整版) + * 参数: {areaName} - 区域名称, {orderTitle} - 工单标题(截断) */ - public static final String QUEUED_ORDER_SIMPLE = "您有新的待办工单"; + public static final String NEW_ORDER_FULL = "新工单:%s,作业区域:%s"; + + // ==================== 工单确认播报 ==================== + + /** + * 工单确认播报(简短版) + */ + public static final String ORDER_CONFIRMED_SHORT = "工单已确认"; + + /** + * 工单确认播报(标准版) + * 参数: {areaName} - 区域名称 + */ + public static final String ORDER_CONFIRMED = "工单已确认,请前往%s开始作业"; + + // ==================== 工单到岗播报 ==================== + + /** + * 工单到岗播报(简短版) + */ + public static final String ORDER_ARRIVED_SHORT = "已到达作业区域"; + + /** + * 工单到岗播报(标准版) + */ + public static final String ORDER_ARRIVED = "已到达作业区域,请开始清洁作业"; + + // ==================== 工单完成播报 ==================== + + /** + * 工单完成播报 + */ + public static final String ORDER_COMPLETED = "工单已完成,请拍照上传"; + + // ==================== 待办增加播报 ==================== + + /** + * 待办增加播报(简短版) + */ + public static final String QUEUED_ORDER_SHORT = "您有新的待办工单"; + + /** + * 待办增加播报(详细版) + * 参数: {totalCount} - 总待办数 + */ + public static final String QUEUED_ORDER_DETAIL = "您现在共有%d个待办工单"; + + // ==================== 下一任务播报 ==================== /** * 下一任务播报 - * 参数: {queueCount} - 总待办数, {orderTitle} - 第一个任务标题 + * 参数: {orderTitle} - 任务标题 */ - public static final String NEXT_TASK_FORMAT = "待办工单总数%d个,第一位待办工单%s"; + public static final String NEXT_TASK = "下一任务:%s"; + + // ==================== P0紧急任务播报 ==================== /** - * P0紧急任务播报 + * P0紧急任务播报(简短版) + */ + public static final String P0_SHORT = "紧急任务!请立即处理"; + + /** + * P0紧急任务播报(详细版) * 参数: {orderCode} - 工单编号 */ - public static final String PRIORITY_UPGRADE_FORMAT = "紧急任务!工单%s已升级为P0,请立即处理"; + public static final String P0_DETAIL = "紧急任务!工单%s请立即处理"; + + // ==================== 任务恢复播报 ==================== /** - * 任务恢复播报 + * 任务恢复播报(简短版) + */ + public static final String TASK_RESUMED_SHORT = "任务已恢复"; + + /** + * 任务恢复播报(详细版) * 参数: {areaName} - 区域名称 */ - public static final String TASK_RESUMED_FORMAT = "任务恢复,请继续完成%s的清洁"; + public static final String TASK_RESUMED = "任务已恢复,请继续完成%s的清洁"; + + // ==================== 按���查询播报 ==================== /** - * 工单完成播报(巡检员) - * 参数: {orderCode} - 工单编号 + * 按键查询播报(有工单时) + * 参数: {orderTitle} - 当前工单标题 */ - public static final String ORDER_COMPLETED_FORMAT = "保洁工单%s已完成,请前往验收"; + public static final String QUERY_HAS_ORDER = "当前工单:%s"; + + /** + * 按键查询播报(无工单时) + */ + public static final String QUERY_NO_ORDER = "暂无待办工单"; + + /** + * 按键查询播报(待办数量提示) + * 参数: {count} - 待办数量 + */ + public static final String QUEUE_COUNT = "您还有%d个待办工单"; } /** @@ -142,63 +225,157 @@ public class CleanNotificationConstants { } /** - * 语音播报辅助方法 + * 语音播报构建器 + *

+ * 根据工单信息智能生成语音播报内容 */ - public static class VoiceHelper { + public static class VoiceBuilder { /** - * 格式化待办增加播报 + * 默认区域名称 + */ + private static final String DEFAULT_AREA_NAME = "作业区域"; + + /** + * 标题最大长度(超长会截断) + */ + private static final int MAX_TITLE_LENGTH = 10; + + /** + * 构建新工单播报 * - * @param newCount 新增数量 - * @param totalCount 总数量 + * @param order 工单信息 + * @param useDetail 是否使用详细版 * @return 播报内容 */ - public static String formatQueuedOrder(int newCount, int totalCount) { - if (totalCount <= 1) { - return VoiceMessage.QUEUED_ORDER_SIMPLE; + public static String buildNewOrder(OpsOrderDO order, boolean useDetail) { + if (order == null) { + return VoiceTemplate.NEW_ORDER_SHORT; } - return String.format(VoiceMessage.QUEUED_ORDER_FORMAT, newCount, totalCount); + + if (!useDetail) { + return VoiceTemplate.NEW_ORDER_SHORT; + } + + String areaName = order.getLocation() != null ? order.getLocation() : DEFAULT_AREA_NAME; + String title = truncateTitle(order.getTitle()); + + return String.format(VoiceTemplate.NEW_ORDER_FULL, title, areaName); } /** - * 格式化下一任务播报 + * 构建工单确认播报 + * + * @param order 工单信息 + * @return 播报内容 + */ + public static String buildOrderConfirmed(OpsOrderDO order) { + if (order == null) { + return VoiceTemplate.ORDER_CONFIRMED_SHORT; + } + + String areaName = order.getLocation() != null ? order.getLocation() : DEFAULT_AREA_NAME; + return String.format(VoiceTemplate.ORDER_CONFIRMED, areaName); + } + + /** + * 构建待办增加播报 * * @param totalCount 总待办数 - * @param orderTitle 第一个任务标题 * @return 播报内容 */ - public static String formatNextTask(int totalCount, String orderTitle) { - return String.format(VoiceMessage.NEXT_TASK_FORMAT, totalCount, orderTitle); + public static String buildQueuedOrder(int totalCount) { + if (totalCount <= 1) { + return VoiceTemplate.QUEUED_ORDER_SHORT; + } + return String.format(VoiceTemplate.QUEUED_ORDER_DETAIL, totalCount); } /** - * 格式化P0紧急任务��报 + * 构建下一任务播报 + * + * @param orderTitle 任务标题 + * @return 播报内容 + */ + public static String buildNextTask(String orderTitle) { + if (orderTitle == null || orderTitle.isEmpty()) { + return "请继续处理下一任务"; + } + String title = truncateTitle(orderTitle); + return String.format(VoiceTemplate.NEXT_TASK, title); + } + + /** + * 构建P0紧急任务播报 * * @param orderCode 工单编号 * @return 播报内容 */ - public static String formatPriorityUpgrade(String orderCode) { - return String.format(VoiceMessage.PRIORITY_UPGRADE_FORMAT, orderCode); + public static String buildPriorityUpgrade(String orderCode) { + if (orderCode != null && !orderCode.isEmpty()) { + return String.format(VoiceTemplate.P0_DETAIL, orderCode); + } + return VoiceTemplate.P0_SHORT; } /** - * 格式化任务恢复播报 + * 构建任务恢复播报 * * @param areaName 区域名称 * @return 播报内容 */ - public static String formatTaskResumed(String areaName) { - return String.format(VoiceMessage.TASK_RESUMED_FORMAT, areaName); + public static String buildTaskResumed(String areaName) { + if (areaName == null || areaName.isEmpty()) { + return VoiceTemplate.TASK_RESUMED_SHORT; + } + return String.format(VoiceTemplate.TASK_RESUMED, areaName); } /** - * 格式化工单完成播报 + * 构建按键查询播报 * - * @param orderCode 工单编号 + * @param currentOrderTitle 当前工单标题 + * @param pendingCount 待办数量(不含当前工单) * @return 播报内容 */ - public static String formatOrderCompleted(String orderCode) { - return String.format(VoiceMessage.ORDER_COMPLETED_FORMAT, orderCode); + public static String buildQuery(String currentOrderTitle, int pendingCount) { + // 优先显示当前正在处理的工单 + if (currentOrderTitle != null && !currentOrderTitle.isEmpty()) { + String title = truncateTitle(currentOrderTitle); + return String.format(VoiceTemplate.QUERY_HAS_ORDER, title); + } + // 没有当前工单,显示待办数量 + if (pendingCount > 0) { + return String.format(VoiceTemplate.QUEUE_COUNT, pendingCount); + } + // 完全没有工单 + return VoiceTemplate.QUERY_NO_ORDER; + } + + /** + * 截断标题到指定长度 + * + * @param title 原标题 + * @return 截断后的标题 + */ + private static String truncateTitle(String title) { + if (title == null || title.isEmpty()) { + return "保洁工单"; + } + if (title.length() <= MAX_TITLE_LENGTH) { + return title; + } + return title.substring(0, MAX_TITLE_LENGTH); + } + + /** + * 获取默认区域名称 + * + * @param location 原位置信息 + * @return 区域名称 + */ + public static String getAreaName(String location) { + return (location != null && !location.isEmpty()) ? location : DEFAULT_AREA_NAME; } } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java index 5010e32..664afea 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java @@ -1,6 +1,7 @@ package com.viewsh.module.ops.environment.integration.consumer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.viewsh.module.ops.environment.constants.CleanNotificationConstants; import com.viewsh.module.ops.environment.integration.dto.CleanOrderAuditEventDTO; import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; @@ -19,7 +20,6 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.Arrays; -import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -55,16 +55,6 @@ public class CleanOrderAuditEventHandler implements RocketMQListener { private static final int DEDUP_TTL_SECONDS = 300; private static final String TRIGGER_SOURCE_QUERY = "IOT_BUTTON_QUERY"; - private static final String DEFAULT_AREA_NAME = "当前区域"; - private static final String TTS_TEMPLATE_QUERY = "当前位置:%s。待办工单:%d个"; - - private static final List ACTIVE_STATUS_LIST = Arrays.asList( - WorkOrderStatusEnum.QUEUED.getStatus(), - WorkOrderStatusEnum.DISPATCHED.getStatus(), - WorkOrderStatusEnum.CONFIRMED.getStatus(), - WorkOrderStatusEnum.ARRIVED.getStatus(), - WorkOrderStatusEnum.PAUSED.getStatus() - ); @Resource private ObjectMapper objectMapper; @@ -173,23 +163,28 @@ public class CleanOrderAuditEventHandler implements RocketMQListener { return; } - // 1. 获取当前区域名称 - String areaName = DEFAULT_AREA_NAME; - if (event.getOrderId() != null) { - OpsOrderDO order = opsOrderMapper.selectById(event.getOrderId()); - if (order != null && order.getLocation() != null) { - areaName = order.getLocation(); - } + // 1. 获取当前正在处理的工单(DISPATCHED, CONFIRMED, ARRIVED 状态) + String currentOrderTitle = null; + OpsOrderDO currentOrder = opsOrderMapper.selectOne(new LambdaQueryWrapperX() + .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) + .in(OpsOrderDO::getStatus, Arrays.asList( + WorkOrderStatusEnum.DISPATCHED.getStatus(), + WorkOrderStatusEnum.CONFIRMED.getStatus(), + WorkOrderStatusEnum.ARRIVED.getStatus())) + .orderByAsc(OpsOrderDO::getId) + .last("LIMIT 1")); + + if (currentOrder != null && currentOrder.getTitle() != null) { + currentOrderTitle = currentOrder.getTitle(); } - // 2. 查询待办工单数量 - // status IN (QUEUED, DISPATCHED, CONFIRMED, ARRIVED, PAUSED) - Long count = opsOrderMapper.selectCount(new LambdaQueryWrapperX() + // 2. 查询待办工单数量(QUEUED 状态,不含当前处理中工单) + Long pendingCount = opsOrderMapper.selectCount(new LambdaQueryWrapperX() .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) - .in(OpsOrderDO::getStatus, ACTIVE_STATUS_LIST)); + .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.QUEUED.getStatus())); - // 3. 构建 TTS 文本 - String ttsText = String.format(TTS_TEMPLATE_QUERY, areaName, count); + // 3. 构建 TTS 文本(使用统一模板构建器) + String ttsText = CleanNotificationConstants.VoiceBuilder.buildQuery(currentOrderTitle, pendingCount.intValue()); // 4. 下发 TTS sendTts(deviceId, ttsText); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java new file mode 100644 index 0000000..e6a4b15 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java @@ -0,0 +1,217 @@ +package com.viewsh.module.ops.environment.integration.listener; + +import com.viewsh.module.ops.api.queue.OrderQueueService; +import com.viewsh.module.ops.core.event.OrderStateChangedEvent; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +/** + * 工牌设备状态事件监听器 + *

+ * 职责:监听工单状态变更事件,同步更新设备状态和工单关联 + *

+ * 设计说明: + * - 使用 BEFORE_COMMIT 阶段,在事务提交前同步执行 + * - 确保 IoT 模块能实时查询到正确的设备工单信息 + * - 只处理保洁类型的工单 + *

+ * 状态处理: + * + * + * + * + * + * + * + * + *
状态设备状态工单关联
DISPATCHEDBUSY设置
CONFIRMED保持BUSY设置
ARRIVED保持BUSY设置完整信息
PAUSEDPAUSED保持
COMPLETED检查等待任务清除
CANCELLEDIDLE清除
+ * + * @author lzh + */ +@Slf4j +@Component +public class BadgeDeviceStatusEventListener { + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private BadgeDeviceStatusService badgeDeviceStatusService; + + @Resource + private OrderQueueService orderQueueService; + + @Resource + private EventLogRecorder eventLogRecorder; + + /** + * 监听工单状态变更事件,同步更新设备工单关联 + *

+ * 使用 BEFORE_COMMIT 阶段,在事务提交前同步执行 + * 确保 IoT 模块能实时查询到正确的设备工单信息 + */ + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void onOrderStateChanged(OrderStateChangedEvent event) { + try { + // 只处理保洁类型的工单 + if (!"CLEAN".equals(event.getOrderType())) { + return; + } + + WorkOrderStatusEnum newStatus = event.getNewStatus(); + Long orderId = event.getOrderId(); + + log.debug("[BadgeDeviceStatusEventListener] 状态变更: orderId={}, status={}", orderId, newStatus); + + // 查询工单获取设备ID + OpsOrderDO order = opsOrderMapper.selectById(orderId); + if (order == null) { + log.debug("[BadgeDeviceStatusEventListener] 工单不存在,跳过处理: orderId={}", orderId); + return; + } + + // assigneeId 存储的是工牌设备ID + Long deviceId = order.getAssigneeId(); + if (deviceId == null) { + log.debug("[BadgeDeviceStatusEventListener] 工单未关联设备,跳过处理: orderId={}", orderId); + return; + } + + // 根据状态更新设备工单关联 + handleOrderStatusTransition(deviceId, orderId, newStatus, event); + + } catch (Exception e) { + log.error("[BadgeDeviceStatusEventListener] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e); + } + } + + /** + * 根据工单状态更新设备工牌关联 + */ + private void handleOrderStatusTransition(Long deviceId, Long orderId, WorkOrderStatusEnum newStatus, OrderStateChangedEvent event) { + switch (newStatus) { + case DISPATCHED: + // 工单已推送到工牌,设置工单关联,设备状态转为 BUSY + badgeDeviceStatusService.setCurrentOrder(deviceId, orderId); + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, null, "新工单已推送"); + log.info("[BadgeDeviceStatusEventListener] 工单已推送,设备状态转为 BUSY: deviceId={}, orderId={}", deviceId, orderId); + break; + + case CONFIRMED: + // 设备按键确认,设备保持 BUSY + badgeDeviceStatusService.setCurrentOrder(deviceId, orderId); + log.debug("[BadgeDeviceStatusEventListener] 工单已确认: deviceId={}, orderId={}", deviceId, orderId); + break; + + case ARRIVED: + // 设备已到岗,设置完整的工单信息(areaId, beaconMac) + updateDeviceOrderInfo(deviceId, orderId, event); + recordOrderArrivedLog(orderId, deviceId, event); + log.info("[BadgeDeviceStatusEventListener] 工单已到岗,更新设备工单信息: deviceId={}, orderId={}", deviceId, orderId); + break; + + case PAUSED: + // 任务暂停,设备状态转为 PAUSED + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停"); + log.info("[BadgeDeviceStatusEventListener] 任务暂停,设备状态转为 PAUSED: deviceId={}", deviceId); + break; + + case COMPLETED: + // 任务完成,清除工单关联 + badgeDeviceStatusService.clearCurrentOrder(deviceId); + + // 检查是否有等待任务,决定设备状态 + var waitingTasks = orderQueueService.getWaitingTasksByUserId(deviceId); + if (waitingTasks != null && !waitingTasks.isEmpty()) { + // 有等待任务,设备状态保持 BUSY(由后续 DISPATCHED 事件更新) + log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}", + waitingTasks.size(), deviceId); + } else { + // 无等待任务,设备状态转为 IDLE + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "任务完成,无新任务"); + log.info("[BadgeDeviceStatusEventListener] 任务完成,无等待任务,设备转为 IDLE: deviceId={}", deviceId); + } + break; + + case CANCELLED: + // 工单取消,清除工单关联,设备状态转为 IDLE + badgeDeviceStatusService.clearCurrentOrder(deviceId); + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消"); + log.info("[BadgeDeviceStatusEventListener] 工单已取消,清除设备工单关联: deviceId={}", deviceId); + break; + + default: + // 其他状态不处理 + break; + } + } + + /** + * 更新设备工单信息(ARRIVED 状态专用) + *

+ * 设置完整的工单信息:工单ID、工单状态、区域ID、信标MAC + */ + private void updateDeviceOrderInfo(Long deviceId, Long orderId, OrderStateChangedEvent event) { + try { + // 从 payload 中提取信息 + Long areaId = event.getPayloadLong("areaId"); + + String beaconMac = null; + Object beaconMacObj = event.getPayload().get("beaconMac"); + if (beaconMacObj != null) { + beaconMac = String.valueOf(beaconMacObj); + } + + // 使用 BadgeDeviceStatusService 设置完整工单信息 + badgeDeviceStatusService.setCurrentOrderInfo( + deviceId, + orderId, + event.getNewStatus().getStatus(), + areaId, + beaconMac + ); + + log.debug("[BadgeDeviceStatusEventListener] 设备工单信息已更新: deviceId={}, orderId={}, areaId={}, beaconMac={}", + deviceId, orderId, areaId, beaconMac); + + } catch (Exception e) { + log.error("[BadgeDeviceStatusEventListener] 更新设备工单信息失败: deviceId={}, orderId={}", deviceId, orderId, e); + } + } + + /** + * 记录工单到岗业务日志 + */ + private void recordOrderArrivedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) { + try { + Long areaId = event.getPayloadLong("areaId"); + String deviceKey = (String) event.getPayload().get("deviceKey"); + + eventLogRecorder.record(EventLogRecord.builder() + .module("clean") + .domain(EventDomain.BEACON) + .eventType("ORDER_ARRIVED") + .message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d]", deviceKey, areaId)) + .targetId(orderId) + .targetType("order") + .deviceId(deviceId) + .personId(deviceId) + .build()); + + } catch (Exception e) { + log.warn("[BadgeDeviceStatusEventListener] 记录到岗业务日志失败: orderId={}", orderId, e); + } + } +} +