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);
+ }
+ }
+}