From d933bbf92c046ba54da6ff79a82d53e1322c9240 Mon Sep 17 00:00:00 2001 From: lzh Date: Mon, 19 Jan 2026 14:50:09 +0800 Subject: [PATCH] feat(ops): add CleanOrderConfirmEventHandler --- .../CleanOrderConfirmEventHandler.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java new file mode 100644 index 0000000..b63cc3c --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java @@ -0,0 +1,161 @@ +package com.viewsh.module.ops.environment.integration.consumer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.viewsh.module.iot.api.device.IotDeviceControlApi; +import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO; +import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager; +import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.enums.OperatorTypeEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.environment.integration.dto.CleanOrderConfirmEventDTO; +import com.viewsh.module.ops.infrastructure.log.context.BusinessLogContext; +import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; +import com.viewsh.module.ops.infrastructure.log.publisher.BusinessLogPublisher; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 保洁工单确认事件消费者 + *

+ * 订阅 IoT 模块发布的工单确认事件(如:工牌按键确认) + * + * @author AI + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = "ops.order.confirm", + consumerGroup = "ops-clean-order-confirm-group", + consumeMode = ConsumeMode.CONCURRENTLY +) +public class CleanOrderConfirmEventHandler implements RocketMQListener { + + private static final String DEDUP_KEY_PATTERN = "ops:clean:dedup:confirm:%s"; + private static final int DEDUP_TTL_SECONDS = 300; + + @Resource + private ObjectMapper objectMapper; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private OrderLifecycleManager orderLifecycleManager; + + @Resource + private BusinessLogPublisher businessLogPublisher; + + @Resource + private IotDeviceControlApi iotDeviceControlApi; + + @Override + public void onMessage(String message) { + try { + // 1. JSON 反序列化 + CleanOrderConfirmEventDTO event = objectMapper.readValue(message, CleanOrderConfirmEventDTO.class); + String eventId = event.getEventId(); + + // 2. 幂等性检查 + String dedupKey = String.format(DEDUP_KEY_PATTERN, eventId); + Boolean firstTime = stringRedisTemplate.opsForValue() + .setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS); + + if (!Boolean.TRUE.equals(firstTime)) { + log.debug("[CleanOrderConfirmEventHandler] 重复事件,跳过处理: eventId={}", eventId); + return; + } + + // 3. 加载工单 + Long orderId = event.getOrderId(); + OpsOrderDO order = opsOrderMapper.selectById(orderId); + if (order == null) { + log.warn("[CleanOrderConfirmEventHandler] 工单不存在: orderId={}", orderId); + return; + } + + WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.fromStatus(order.getStatus()); + log.info("[CleanOrderConfirmEventHandler] 收到确认事件: eventId={}, orderId={}, currentStatus={}", + eventId, orderId, currentStatus); + + // 4. 状态检查 + // 如果已在进行中 (CONFIRMED or ARRIVED),提示"工单已在进行中" + if (currentStatus == WorkOrderStatusEnum.CONFIRMED || currentStatus == WorkOrderStatusEnum.ARRIVED) { + sendTTS(event.getDeviceId(), "工单已在进行中"); + return; + } + + // 检查是否可以确认 + if (!currentStatus.canConfirm()) { + log.warn("[CleanOrderConfirmEventHandler] 当前状态无法确认工单: orderId={}, status={}", orderId, currentStatus); + sendTTS(event.getDeviceId(), "当前状态无法确认工单"); + return; + } + + // 5. 状态流转 -> CONFIRMED + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.CONFIRMED) + .reason("工牌按键确认") + .operatorType(OperatorTypeEnum.CLEANER) + .operatorId(order.getAssigneeId()) + .build(); + + orderLifecycleManager.transition(request); + + // 6. 记录业务日志 + BusinessLogContext logContext = BusinessLogContext.forOrder( + LogType.TRANSITION, + "工单已确认 (工牌按键)", + orderId + ); + businessLogPublisher.publishSuccess(logContext); + + // 7. 发送 TTS 通知 + // "工单已确认,请前往{AreaName}开始作业" + String ttsText = "工单已确认,请前往作业区域开始作业"; + sendTTS(event.getDeviceId(), ttsText); + + } catch (Exception e) { + log.error("[CleanOrderConfirmEventHandler] 消息处理失败: message={}", message, e); + throw new RuntimeException("保洁工单确认事件处理失败", e); + } + } + + /** + * 发送 TTS 语音播报 + */ + private void sendTTS(Long deviceId, String text) { + if (deviceId == null) { + return; + } + try { + Map params = new HashMap<>(); + params.put("text", text); + + IotDeviceServiceInvokeReqDTO req = IotDeviceServiceInvokeReqDTO.builder() + .deviceId(deviceId) + .identifier("TTS") + .params(params) + .timeoutSeconds(10) + .build(); + + iotDeviceControlApi.invokeService(req); + } catch (Exception e) { + log.error("[CleanOrderConfirmEventHandler] TTS 发送失败: deviceId={}", deviceId, e); + } + } +}