refactor(ops): 工单事件监听器重构
- handleDispatched: 记录下发时间��处理暂停结束 - handlePaused: 记录暂停开始时间 - handleArrived: 记录到岗时间,计算响应时长 - handleConfirmed/handleCompleted: 使用 VoiceBuilder 播报 - 新增 recordDispatchedTime/handlePauseEnd/updateResponseSeconds 方法 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
package com.viewsh.module.ops.environment.integration.listener;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.viewsh.module.iot.api.device.IotDeviceControlApi;
|
||||
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO;
|
||||
import com.viewsh.module.iot.api.device.dto.ResetTrafficCounterReqDTO;
|
||||
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
@@ -15,9 +14,11 @@ import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import com.viewsh.module.ops.environment.constants.CleanNotificationConstants;
|
||||
import com.viewsh.module.ops.environment.dal.dataobject.workorder.OpsOrderCleanExtDO;
|
||||
import com.viewsh.module.ops.environment.dal.mysql.workorder.OpsOrderCleanExtMapper;
|
||||
import com.viewsh.module.ops.environment.service.cleaner.CleanerStatusService;
|
||||
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
|
||||
import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService;
|
||||
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 com.viewsh.module.system.api.notify.NotifyMessageSendApi;
|
||||
import com.viewsh.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 保洁工单事件监听器(统一入口)
|
||||
@@ -36,12 +38,19 @@ import java.time.LocalDateTime;
|
||||
* 1. 监听工单创建事件,触发自动调度
|
||||
* 2. 监听工单状态变更事件,处理:
|
||||
* - 扩展表时间记录(到岗、完成、暂停)
|
||||
* - 保洁员状态同步
|
||||
* - 通知发送(语音、震动、站内信)
|
||||
* - 通知发送(语音、站内信)
|
||||
* - 业务日志记录
|
||||
* 3. 监听工单完成事件,触发自动派单下一个任务
|
||||
* <p>
|
||||
* 职责划分:
|
||||
* - 设备状态管理:由 BadgeDeviceStatusServiceImpl 处理
|
||||
* - 扩展表时间记录:由 CleanOrderEventListener 处理
|
||||
* - 通知发送:由 CleanOrderEventListener 处理
|
||||
* - 业务日志记录:由 CleanOrderEventListener 处理
|
||||
* - 自动调度:由 CleanOrderEventListener 触发
|
||||
* <p>
|
||||
* 设计说明:
|
||||
* - 整合了 OrderCreatedEventListener、CleanerStateChangeListener、CleanOrderEventHandler 的功能
|
||||
* - assigneeId 存储的是工牌设备ID(非保洁员ID)
|
||||
* - 使用异步处理,避免阻塞主流程
|
||||
* - 使用 @TransactionalEventListener(AFTER_COMMIT) 确保事务提交后再处理
|
||||
*
|
||||
@@ -60,9 +69,6 @@ public class CleanOrderEventListener {
|
||||
@Resource
|
||||
private OpsOrderCleanExtMapper cleanExtMapper;
|
||||
|
||||
@Resource
|
||||
private CleanerStatusService cleanerStatusService;
|
||||
|
||||
@Resource
|
||||
private CleanOrderService cleanOrderService;
|
||||
|
||||
@@ -75,13 +81,13 @@ public class CleanOrderEventListener {
|
||||
@Resource
|
||||
private IotDeviceControlApi iotDeviceControlApi;
|
||||
|
||||
@Resource
|
||||
private EventLogRecorder eventLogRecorder;
|
||||
|
||||
// ==================== 工单创建事件 ====================
|
||||
|
||||
/**
|
||||
* 监听工单创建事件,触发自动调度
|
||||
* <p>
|
||||
* 使用 @TransactionalEventListener 确保在事务提交后才执行调度
|
||||
* 避免调度失败导致工单创建回滚
|
||||
*/
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void onOrderCreated(OrderCreatedEvent event) {
|
||||
@@ -94,6 +100,11 @@ public class CleanOrderEventListener {
|
||||
|
||||
// 异步触发调度
|
||||
asyncDispatchAfterCreated(event);
|
||||
|
||||
// 如果是客流触发的工单,重置客流计数器
|
||||
if ("IOT_TRAFFIC".equals(event.getPayload().get("triggerSource"))) {
|
||||
asyncResetTrafficCounter(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +133,7 @@ public class CleanOrderEventListener {
|
||||
DispatchResult result = dispatchEngine.dispatch(context);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
log.info("[CleanOrderEventListener] 自动调度成功: orderId={}, assigneeId={}",
|
||||
log.info("[CleanOrderEventListener] 自动调度成功: orderId={}, deviceId={}",
|
||||
event.getOrderId(), result.getAssigneeId());
|
||||
} else {
|
||||
log.warn("[CleanOrderEventListener] 自动调度失败: orderId={}, reason={}",
|
||||
@@ -134,10 +145,63 @@ public class CleanOrderEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步重置客流计数器
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void asyncResetTrafficCounter(OrderCreatedEvent event) {
|
||||
try {
|
||||
Long deviceId = (Long) event.getPayload().get("triggerDeviceId");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> triggerData = (Map<String, Object>) event.getPayload().get("triggerData");
|
||||
|
||||
if (deviceId == null || triggerData == null) {
|
||||
log.warn("[CleanOrderEventListener] 缺少客流数据,跳过重置: orderId={}, deviceId={}, triggerData={}",
|
||||
event.getOrderId(), deviceId, triggerData);
|
||||
return;
|
||||
}
|
||||
|
||||
Object actualCountObj = triggerData.get("actualCount");
|
||||
if (actualCountObj == null) {
|
||||
log.warn("[CleanOrderEventListener] 缺少客流值,跳过重置: orderId={}", event.getOrderId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 将当前客流值设为新的基准值
|
||||
Long newBaseValue = ((Number) actualCountObj).longValue();
|
||||
|
||||
// 构建重置请求
|
||||
ResetTrafficCounterReqDTO reqDTO = ResetTrafficCounterReqDTO.builder()
|
||||
.deviceId(deviceId)
|
||||
.newBaseValue(newBaseValue)
|
||||
.orderId(event.getOrderId())
|
||||
.remark("工单创建后重置计数器")
|
||||
.build();
|
||||
|
||||
// 调用 IoT 模块 RPC 接口
|
||||
var result = iotDeviceControlApi.resetTrafficCounter(reqDTO);
|
||||
|
||||
if (result.getData() != null && result.getData()) {
|
||||
log.info("[CleanOrderEventListener] 客流计数器重置成功: orderId={}, deviceId={}, newBaseValue={}",
|
||||
event.getOrderId(), deviceId, newBaseValue);
|
||||
} else {
|
||||
log.warn("[CleanOrderEventListener] 客流计数器重置失败: orderId={}, deviceId={}",
|
||||
event.getOrderId(), deviceId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 重置失败不应影响主流程
|
||||
log.error("[CleanOrderEventListener] 客流计数器重置异常: orderId={}", event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 状态变更事件 ====================
|
||||
|
||||
/**
|
||||
* 监听状态变更事件
|
||||
* <p>
|
||||
* 注意:设备状态由 BadgeDeviceStatusServiceImpl 统一管理
|
||||
* 这里只处理扩展表时间记录和通知发送
|
||||
*/
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void onOrderStateChanged(OrderStateChangedEvent event) {
|
||||
@@ -145,8 +209,8 @@ public class CleanOrderEventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[CleanOrderEventListener] 状态变更: orderId={}, {} -> {}, operatorId={}",
|
||||
event.getOrderId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId());
|
||||
log.info("[CleanOrderEventListener] 状态变更: orderId={}, {} -> {}",
|
||||
event.getOrderId(), event.getOldStatus(), event.getNewStatus());
|
||||
|
||||
switch (event.getNewStatus()) {
|
||||
case DISPATCHED:
|
||||
@@ -165,7 +229,8 @@ public class CleanOrderEventListener {
|
||||
handleCompleted(event);
|
||||
break;
|
||||
case CANCELLED:
|
||||
handleCancelled(event);
|
||||
// 设备状态由 BadgeDeviceStatusEventListener 统一处理
|
||||
log.debug("[CleanOrderEventListener] CANCELLED 状态已处理: orderId={}", event.getOrderId());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -177,8 +242,28 @@ public class CleanOrderEventListener {
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void handleDispatched(OrderStateChangedEvent event) {
|
||||
// 发送新工单通知(语音+震动+站内信)
|
||||
sendNewOrderNotification(event.getOperatorId(), event.getOrderId());
|
||||
Long orderId = event.getOrderId();
|
||||
|
||||
// 1. 记录下发时间到扩展表(用于计算响应时长)
|
||||
recordDispatchedTime(orderId);
|
||||
|
||||
// 2. 如果是从 PAUSED 恢复,处理暂停结束逻辑
|
||||
if (event.getOldStatus() == com.viewsh.module.ops.enums.WorkOrderStatusEnum.PAUSED) {
|
||||
handlePauseEnd(orderId);
|
||||
}
|
||||
|
||||
// 3. 优先使用 assigneeId(自动调度场景),其次使用 operatorId(手动派单场景)
|
||||
Long deviceId = event.getPayloadLong("assigneeId");
|
||||
if (deviceId == null) {
|
||||
deviceId = event.getOperatorId();
|
||||
}
|
||||
if (deviceId != null) {
|
||||
// 发送新工单通知(语音+站内信)
|
||||
sendNewOrderNotification(deviceId, orderId);
|
||||
} else {
|
||||
log.warn("[CleanOrderEventListener] DISPATCHED 事件缺少 assigneeId 和 operatorId: orderId={}",
|
||||
orderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,21 +271,25 @@ public class CleanOrderEventListener {
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void handleConfirmed(OrderStateChangedEvent event) {
|
||||
Long cleanerId = event.getOperatorId();
|
||||
if (cleanerId == null) {
|
||||
return;
|
||||
Long orderId = event.getOrderId();
|
||||
|
||||
// 获取 deviceId(优先从 payload 获取,其次使用 operatorId)
|
||||
Long deviceId = event.getPayloadLong("deviceId");
|
||||
if (deviceId == null) {
|
||||
deviceId = event.getOperatorId();
|
||||
}
|
||||
|
||||
// 更新保洁员状态为 BUSY
|
||||
cleanerStatusService.updateStatus(cleanerId,
|
||||
com.viewsh.module.ops.enums.CleanerStatusEnum.BUSY,
|
||||
"确认工单");
|
||||
// 记录业务日志
|
||||
recordOrderConfirmedLog(orderId, deviceId, event);
|
||||
|
||||
// 设置当前工单
|
||||
cleanerStatusService.setCurrentWorkOrder(cleanerId, event.getOrderId(),
|
||||
opsOrderMapper.selectById(event.getOrderId()).getOrderCode());
|
||||
if (deviceId != null) {
|
||||
// 发送确认成功语音播报(使用统一模板)
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
String confirmMessage = CleanNotificationConstants.VoiceBuilder.buildOrderConfirmed(order);
|
||||
playVoice(deviceId, confirmMessage);
|
||||
}
|
||||
|
||||
log.info("[CleanOrderEventListener] 保洁员确认工单,状态更新为BUSY: cleanerId={}", cleanerId);
|
||||
log.info("[CleanOrderEventListener] 工单已确认: orderId={}, deviceId={}", orderId, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,35 +305,28 @@ public class CleanOrderEventListener {
|
||||
updateObj.setArrivedTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
|
||||
log.info("[CleanOrderEventListener] 保洁员已到岗: orderId={}", orderId);
|
||||
// 2. 计算并更新响应时长(下发→到岗,排除暂停时间)
|
||||
updateResponseSeconds(orderId);
|
||||
|
||||
log.info("[CleanOrderEventListener] 到岗时间已记录: orderId={}", orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理暂停状态
|
||||
* <p>
|
||||
* 注意:PAUSED 状态不是由 IoT 消息触发的,而是通过服务层 (REST API) 调用:
|
||||
* - OrderLifecycleManager.pauseOrder() - 用户主动暂停
|
||||
* - OrderLifecycleManager.interruptOrder() - P0任务打断
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void handlePaused(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
Long operatorId = event.getOperatorId();
|
||||
if (operatorId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是被 P0 任务打断
|
||||
String interruptReason = event.getPayloadString("interruptReason");
|
||||
if ("P0_TASK_INTERRUPT".equals(interruptReason)) {
|
||||
log.warn("[CleanOrderEventListener] 保洁任务被P0任务打断: orderId={}", orderId);
|
||||
// 释放保洁员资源
|
||||
cleanerStatusService.clearCurrentWorkOrder(operatorId);
|
||||
} else {
|
||||
// 普通暂停:更新保洁员状态为 PAUSED
|
||||
cleanerStatusService.updateStatus(operatorId,
|
||||
com.viewsh.module.ops.enums.CleanerStatusEnum.PAUSED,
|
||||
event.getRemark() != null ? event.getRemark() : "任务暂停");
|
||||
}
|
||||
|
||||
// 记录暂停开始时间
|
||||
// 记录暂停开始时间到扩展表
|
||||
recordPauseStartTime(orderId);
|
||||
|
||||
// 设备状态由 BadgeDeviceStatusEventListener 统一处理
|
||||
log.info("[CleanOrderEventListener] 暂停时间已记录: orderId={}", orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,35 +336,27 @@ public class CleanOrderEventListener {
|
||||
public void handleCompleted(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
|
||||
// 1. 计算作业时长
|
||||
Integer actualDuration = cleanOrderService.calculateActualDuration(orderId);
|
||||
// 获取 deviceId(优先从 payload 获取,其次从工单获取)
|
||||
Long deviceId = event.getPayloadLong("deviceId");
|
||||
|
||||
// 2. 记录完成时间到扩展表
|
||||
// 记录完成时间到扩展表
|
||||
OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO();
|
||||
updateObj.setOpsOrderId(orderId);
|
||||
updateObj.setCompletedTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
|
||||
log.info("[CleanOrderEventListener] 保洁作业完成: orderId={}, actualDuration={}秒", orderId, actualDuration);
|
||||
}
|
||||
// 记录业务日志
|
||||
recordOrderCompletedLog(orderId, deviceId, event);
|
||||
|
||||
/**
|
||||
* 处理取消状态
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void handleCancelled(OrderStateChangedEvent event) {
|
||||
Long operatorId = event.getOperatorId();
|
||||
if (operatorId != null) {
|
||||
// 清理保洁员当前工单
|
||||
cleanerStatusService.clearCurrentWorkOrder(operatorId);
|
||||
}
|
||||
log.info("[CleanOrderEventListener] 保洁工单已取消: orderId={}", event.getOrderId());
|
||||
log.info("[CleanOrderEventListener] 完成时间已记录: orderId={}, deviceId={}", orderId, deviceId);
|
||||
}
|
||||
|
||||
// ==================== 工单完成事件 ====================
|
||||
|
||||
/**
|
||||
* 监听工单完成事件
|
||||
* <p>
|
||||
* 触发自动调度下一个任务
|
||||
*/
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void onOrderCompleted(OrderCompletedEvent event) {
|
||||
@@ -290,19 +364,16 @@ public class CleanOrderEventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[CleanOrderEventListener] 工单完成: orderId={}, assigneeId={}",
|
||||
log.info("[CleanOrderEventListener] 工单完成: orderId={}, deviceId={}",
|
||||
event.getOrderId(), event.getAssigneeId());
|
||||
|
||||
Long assigneeId = event.getAssigneeId();
|
||||
if (assigneeId != null) {
|
||||
// 1. 清理当前工单
|
||||
cleanerStatusService.clearCurrentWorkOrder(assigneeId);
|
||||
|
||||
// 2. 自动推送下一个任务(异步)
|
||||
asyncDispatchNext(assigneeId, event.getOrderId());
|
||||
Long deviceId = event.getAssigneeId();
|
||||
if (deviceId != null) {
|
||||
// 自动推送下一个任务(异步)
|
||||
asyncDispatchNext(deviceId, event.getOrderId());
|
||||
}
|
||||
|
||||
// 3. 发送完成通知(异步)
|
||||
// 发送完成通知(异步)
|
||||
asyncSendOrderCompletedNotification(event.getOrderId());
|
||||
}
|
||||
|
||||
@@ -310,8 +381,8 @@ public class CleanOrderEventListener {
|
||||
* 异步推送下一个任务
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void asyncDispatchNext(Long cleanerId, Long completedOrderId) {
|
||||
cleanOrderService.autoDispatchNextOrder(completedOrderId, cleanerId);
|
||||
public void asyncDispatchNext(Long deviceId, Long completedOrderId) {
|
||||
cleanOrderService.autoDispatchNextOrder(completedOrderId, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,10 +396,10 @@ public class CleanOrderEventListener {
|
||||
// ==================== 通知方法 ====================
|
||||
|
||||
/**
|
||||
* 发送新工单通知(语音播报 + 震动提醒 + 站内信)
|
||||
* 发送新工单通知(语音播报 + 站内信)
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendNewOrderNotification(Long cleanerId, Long orderId) {
|
||||
public void sendNewOrderNotification(Long deviceId, Long orderId) {
|
||||
try {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
@@ -336,25 +407,23 @@ public class CleanOrderEventListener {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[新工单通知] cleanerId={}, orderId={}", cleanerId, orderId);
|
||||
log.info("[新工单通知] deviceId={}, orderId={}", deviceId, orderId);
|
||||
|
||||
// 1. 语音播报
|
||||
playVoice(cleanerId, CleanNotificationConstants.VoiceMessage.NEW_ORDER);
|
||||
// 1. 语音播报(使用统一模板构建器)
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildNewOrder(order, true);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
// 2. 发送站内信(暂时发送到管理员)
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.NEW_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.newOrderParams(
|
||||
order.getOrderCode(),
|
||||
order.getTitle(),
|
||||
getAreaName(order.getAreaId())
|
||||
CleanNotificationConstants.VoiceBuilder.getAreaName(order.getLocation())
|
||||
));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[新工单通知] 发送失败: cleanerId={}, orderId={}", cleanerId, orderId, e);
|
||||
log.error("[新工单通知] 发送失败: deviceId={}, orderId={}", deviceId, orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,26 +431,23 @@ public class CleanOrderEventListener {
|
||||
* 发送待办增加通知
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendQueuedOrderNotification(Long cleanerId, int queueCount) {
|
||||
public void sendQueuedOrderNotification(Long deviceId, int queueCount) {
|
||||
try {
|
||||
log.info("[待办增加通知] cleanerId={}, queueCount={}", cleanerId, queueCount);
|
||||
log.info("[待办增加通知] deviceId={}, queueCount={}", deviceId, queueCount);
|
||||
|
||||
// 1. 语音播报
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatQueuedOrder(queueCount, 1);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
// 1. 语音播报(使用统一模板构建器)
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildQueuedOrder(queueCount);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.LIGHT);
|
||||
|
||||
// 3. 发送站内信(待办数量较多时)
|
||||
// 2. 发送站内信(待办数量较多时)
|
||||
if (queueCount >= 3) {
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.QUEUED_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.queuedOrderParams(queueCount, 1));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[待办增加通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
log.error("[待办增加通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,24 +455,21 @@ public class CleanOrderEventListener {
|
||||
* 发送下一个任务通知
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendNextTaskNotification(Long cleanerId, int queueCount, String orderTitle) {
|
||||
public void sendNextTaskNotification(Long deviceId, int queueCount, String orderTitle) {
|
||||
try {
|
||||
log.info("[下一任务通知] cleanerId={}, queueCount={}, title={}", cleanerId, queueCount, orderTitle);
|
||||
log.info("[下一任务通知] deviceId={}, queueCount={}, title={}", deviceId, queueCount, orderTitle);
|
||||
|
||||
// 1. 语音播报
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatNextTask(queueCount, orderTitle);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
// 1. 语音播报(使用统一模板构建器)
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildNextTask(orderTitle);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
// 2. 发送站内信
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.NEXT_TASK,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.nextTaskParams(queueCount, orderTitle));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[下一任务通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
log.error("[下一任务通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,24 +477,21 @@ public class CleanOrderEventListener {
|
||||
* 发送P0紧急任务插队通知
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendPriorityUpgradeNotification(Long cleanerId, String orderCode) {
|
||||
public void sendPriorityUpgradeNotification(Long deviceId, String orderCode) {
|
||||
try {
|
||||
log.warn("[P0紧急通知] cleanerId={}, orderCode={}", cleanerId, orderCode);
|
||||
log.warn("[P0紧急通知] deviceId={}, orderCode={}", deviceId, orderCode);
|
||||
|
||||
// 1. 语音播报
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatPriorityUpgrade(orderCode);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
// 1. 语音播报(使用统一模板构建器)
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildPriorityUpgrade(orderCode);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
// 2. 强烈震动提醒
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.STRONG);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
// 2. 发送站内信
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.PRIORITY_UPGRADE,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.priorityUpgradeParams(orderCode, "P0紧急任务"));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[P0紧急通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
log.error("[P0紧急通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,24 +499,21 @@ public class CleanOrderEventListener {
|
||||
* 发送任务恢复通知
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendTaskResumedNotification(Long cleanerId, String areaName) {
|
||||
public void sendTaskResumedNotification(Long deviceId, String areaName) {
|
||||
try {
|
||||
log.info("[任务恢复通知] cleanerId={}, areaName={}", cleanerId, areaName);
|
||||
log.info("[任务恢复通知] deviceId={}, areaName={}", deviceId, areaName);
|
||||
|
||||
// 1. 语音播报
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatTaskResumed(areaName);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
// 1. 语音播报(使用统一模板构建器)
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildTaskResumed(areaName);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
// 2. 发送站内信
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.TASK_RESUMED,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.taskResumedParams(areaName));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[任务恢复通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
log.error("[任务恢复通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,73 +542,31 @@ public class CleanOrderEventListener {
|
||||
* 播放语音(供外部调用)
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void playVoiceForNewOrder(Long cleanerId) {
|
||||
playVoice(cleanerId, CleanNotificationConstants.VoiceMessage.NEW_ORDER);
|
||||
public void playVoiceForNewOrder(Long deviceId) {
|
||||
playVoice(deviceId, CleanNotificationConstants.VoiceTemplate.NEW_ORDER_SHORT);
|
||||
}
|
||||
|
||||
// ==================== IoT 设备操作<EFBFBD><EFBFBD><EFBFBD>法 ====================
|
||||
// ==================== 设备操作方法 ====================
|
||||
|
||||
/**
|
||||
* 语音播报
|
||||
*/
|
||||
private void playVoice(Long cleanerId, String message) {
|
||||
private void playVoice(Long deviceId, String message) {
|
||||
try {
|
||||
Long deviceId = getBadgeDeviceId(cleanerId);
|
||||
if (deviceId == null) {
|
||||
log.warn("[语音播报] 保洁员无关联工牌设备: cleanerId={}", cleanerId);
|
||||
return;
|
||||
}
|
||||
|
||||
voiceBroadcastService.broadcast(deviceId, message);
|
||||
log.debug("[语音播报] 调用成功: cleanerId={}, deviceId={}, message={}", cleanerId, deviceId, message);
|
||||
log.debug("[语音播报] 调用成功: deviceId={}, message={}", deviceId, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[语音播报] 调用失败: cleanerId={}, message={}", cleanerId, message, e);
|
||||
log.error("[语音播报] 调用失败: deviceId={}, message={}", deviceId, message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 震动提醒
|
||||
*/
|
||||
private void vibrate(Long cleanerId, int durationMs) {
|
||||
try {
|
||||
Long deviceId = getBadgeDeviceId(cleanerId);
|
||||
if (deviceId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
IotDeviceServiceInvokeReqDTO reqDTO = new IotDeviceServiceInvokeReqDTO();
|
||||
reqDTO.setDeviceId(deviceId);
|
||||
reqDTO.setIdentifier("vibrate");
|
||||
reqDTO.setParams(MapUtil.<String, Object>builder()
|
||||
.put("duration", durationMs)
|
||||
.put("intensity", 50)
|
||||
.build());
|
||||
|
||||
iotDeviceControlApi.invokeService(reqDTO);
|
||||
log.debug("[震动提醒] 调用成功: cleanerId={}, durationMs={}", cleanerId, durationMs);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[震动提醒] 调用失败: cleanerId={}, durationMs={}", cleanerId, durationMs, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保洁员关联的工牌设备ID
|
||||
* TODO: 需要实现
|
||||
*/
|
||||
private Long getBadgeDeviceId(Long cleanerId) {
|
||||
// TODO: 实现获取保洁员关联的工牌设备ID
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== 站内信发送方法 ====================
|
||||
|
||||
/**
|
||||
* 发送站内信给保洁员
|
||||
* 发送站内信
|
||||
*/
|
||||
private void sendNotifyMessageToMember(Long userId, String templateCode,
|
||||
java.util.Map<String, Object> templateParams) {
|
||||
private void sendNotifyMessage(Long userId, String templateCode, Map<String, Object> templateParams) {
|
||||
try {
|
||||
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO();
|
||||
reqDTO.setUserId(userId);
|
||||
@@ -566,25 +581,6 @@ public class CleanOrderEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送站内信给管理员
|
||||
*/
|
||||
private void sendNotifyMessageToAdmin(Long userId, String templateCode,
|
||||
java.util.Map<String, Object> templateParams) {
|
||||
try {
|
||||
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO();
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setTemplateCode(templateCode);
|
||||
reqDTO.setTemplateParams(templateParams);
|
||||
|
||||
notifyMessageSendApi.sendSingleMessageToAdmin(reqDTO);
|
||||
log.debug("[站内信发送成功] userId={}, templateCode={}", userId, templateCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[站内信发送失败] userId={}, templateCode={}", userId, templateCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
/**
|
||||
@@ -604,4 +600,167 @@ public class CleanOrderEventListener {
|
||||
updateObj.setPauseStartTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录下发时间
|
||||
*/
|
||||
private void recordDispatchedTime(Long orderId) {
|
||||
// 先查询现有记录
|
||||
OpsOrderCleanExtDO existing = cleanExtMapper.selectByOpsOrderId(orderId);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (existing == null || existing.getFirstDispatchedTime() == null) {
|
||||
// 首次下发,记录首次下发时间
|
||||
OpsOrderCleanExtDO updateObj = OpsOrderCleanExtDO.builder()
|
||||
.opsOrderId(orderId)
|
||||
.dispatchedTime(now)
|
||||
.firstDispatchedTime(now)
|
||||
.build();
|
||||
cleanExtMapper.insertOrUpdateSelective(updateObj);
|
||||
log.info("[CleanOrderEventListener] 首次下发时间已记录: orderId={}", orderId);
|
||||
} else {
|
||||
// 非首次下发,只更新最近下发时间
|
||||
OpsOrderCleanExtDO updateObj = OpsOrderCleanExtDO.builder()
|
||||
.opsOrderId(orderId)
|
||||
.dispatchedTime(now)
|
||||
.firstDispatchedTime(existing.getFirstDispatchedTime()) // 保留首次时间
|
||||
.build();
|
||||
cleanExtMapper.insertOrUpdateSelective(updateObj);
|
||||
log.debug("[CleanOrderEventListener] 下发时间已更新: orderId={}", orderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理暂停结束
|
||||
* <p>
|
||||
* 计算暂停时长并累加到 totalPauseSeconds
|
||||
*/
|
||||
private void handlePauseEnd(Long orderId) {
|
||||
try {
|
||||
OpsOrderCleanExtDO ext = cleanExtMapper.selectByOpsOrderId(orderId);
|
||||
if (ext == null || ext.getPauseStartTime() == null) {
|
||||
log.warn("[CleanOrderEventListener] 暂停开始时间不存在,无法计算暂停时长: orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算暂停时长(秒)
|
||||
LocalDateTime pauseEnd = LocalDateTime.now();
|
||||
long pauseSeconds = java.time.Duration.between(ext.getPauseStartTime(), pauseEnd).getSeconds();
|
||||
|
||||
// 获取现有累计暂停时长
|
||||
int totalPauseSeconds = ext.getTotalPauseSeconds() != null ? ext.getTotalPauseSeconds() : 0;
|
||||
totalPauseSeconds += (int) pauseSeconds;
|
||||
|
||||
// 更新暂停结束时间和累计暂停时长
|
||||
OpsOrderCleanExtDO updateObj = OpsOrderCleanExtDO.builder()
|
||||
.opsOrderId(orderId)
|
||||
.pauseEndTime(pauseEnd)
|
||||
.totalPauseSeconds(totalPauseSeconds)
|
||||
.firstDispatchedTime(ext.getFirstDispatchedTime()) // 保留首次下发时间
|
||||
.build();
|
||||
cleanExtMapper.insertOrUpdateSelective(updateObj);
|
||||
|
||||
log.info("[CleanOrderEventListener] 暂停时长已累加: orderId={}, 本次={}秒, 累计={}秒",
|
||||
orderId, pauseSeconds, totalPauseSeconds);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[CleanOrderEventListener] 处理暂停结束失败: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并更新响应时长
|
||||
* <p>
|
||||
* 响应时长 = 到岗时间 - 首次下发时间 - 累计暂停时长
|
||||
*/
|
||||
private void updateResponseSeconds(Long orderId) {
|
||||
try {
|
||||
OpsOrderCleanExtDO ext = cleanExtMapper.selectByOpsOrderId(orderId);
|
||||
if (ext == null || ext.getFirstDispatchedTime() == null || ext.getArrivedTime() == null) {
|
||||
log.warn("[CleanOrderEventListener] 缺少必要时间数据,无法计算响应时长: orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算响应时长(秒):到岗 - 首次下发 - 暂停时长
|
||||
long responseSeconds = java.time.Duration.between(ext.getFirstDispatchedTime(), ext.getArrivedTime()).getSeconds();
|
||||
int totalPauseSeconds = ext.getTotalPauseSeconds() != null ? ext.getTotalPauseSeconds() : 0;
|
||||
responseSeconds = responseSeconds - totalPauseSeconds;
|
||||
|
||||
// 确保不为负数
|
||||
responseSeconds = Math.max(responseSeconds, 0);
|
||||
|
||||
// 更新工单主表的响应时长
|
||||
OpsOrderDO orderUpdate = new OpsOrderDO();
|
||||
orderUpdate.setId(orderId);
|
||||
orderUpdate.setResponseSeconds((int) responseSeconds);
|
||||
opsOrderMapper.updateById(orderUpdate);
|
||||
|
||||
log.info("[CleanOrderEventListener] 响应时长已更新: orderId={}, 响应时长={}秒", orderId, responseSeconds);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[CleanOrderEventListener] 更新响应时长失败: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工单确认业务日志
|
||||
*/
|
||||
private void recordOrderConfirmedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) {
|
||||
try {
|
||||
eventLogRecorder.record(EventLogRecord.builder()
|
||||
.module("clean")
|
||||
.domain(EventDomain.DEVICE)
|
||||
.eventType("ORDER_CONFIRM")
|
||||
.message("工单已确认 (工牌按键)")
|
||||
.targetId(orderId)
|
||||
.targetType("order")
|
||||
.deviceId(deviceId)
|
||||
.personId(deviceId)
|
||||
.build());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("[CleanOrderEventListener] 记录确认业务日志失败: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工单完成业务日志
|
||||
*/
|
||||
private void recordOrderCompletedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) {
|
||||
try {
|
||||
// 从 payload 中提取完成原因和作业时长
|
||||
String triggerSource = (String) event.getPayload().get("triggerSource");
|
||||
String deviceKey = (String) event.getPayload().get("deviceKey");
|
||||
|
||||
// 构建日志消息
|
||||
String message = "工单已完成";
|
||||
if ("SIGNAL_LOSS_TIMEOUT".equals(triggerSource)) {
|
||||
Object durationMs = event.getPayload().get("durationMs");
|
||||
String durationInfo = "";
|
||||
if (durationMs != null) {
|
||||
long durationMinutes = ((Number) durationMs).longValue() / 60000;
|
||||
durationInfo = String.format(",作业时长: %d分钟", durationMinutes);
|
||||
}
|
||||
message = "信号丢失超时自动完成 [设备:" + deviceKey + durationInfo + "]";
|
||||
}
|
||||
|
||||
EventLogRecord.EventLogRecordBuilder builder = EventLogRecord.builder()
|
||||
.module("clean")
|
||||
.domain(EventDomain.BEACON)
|
||||
.eventType("ORDER_COMPLETED")
|
||||
.message(message)
|
||||
.targetId(orderId)
|
||||
.targetType("order");
|
||||
|
||||
if (deviceId != null) {
|
||||
builder.deviceId(deviceId).personId(deviceId);
|
||||
}
|
||||
|
||||
eventLogRecorder.record(builder.build());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("[CleanOrderEventListener] 记录完成业务日志失败: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user