refactor(ops): 设备状态管理重构

- 删除 BadgeDeviceStatusEventHandler(职责不清)
- 新建 BadgeDeviceStatusEventListener 专门处理设备状态变更
- 使用 BEFORE_COMMIT 阶段同步更新设备工单关联
- 修复 CANCELLED 处理:仅当取消当前工单时才更新设备状态
- 修复 P0 打断场景:检测 urgentOrderId,不修改设备状态

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-01-25 18:22:52 +08:00
parent f4d2b0a5de
commit 116c1ec773
3 changed files with 47 additions and 214 deletions

View File

@@ -1,206 +0,0 @@
package com.viewsh.module.ops.environment.integration.listener;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* 工牌设备状态事件处理器
* <p>
* 监听工单状态变更事件,同步更新工牌设备状态
* <p>
* 设计说明:
* - 使用 Spring EventListener 监听工单状态变更事件
* - 根据工单状态转换规则更新工牌设备状态
* - 支持工单分配、开始、暂停、完成等场景
*
* @author lzh
*/
@Slf4j
@Component
public class BadgeDeviceStatusEventHandler {
@Resource
private BadgeDeviceStatusService badgeDeviceStatusService;
/**
* 处理工单状态变更事件
* <p>
* 状态映射规则:
* - PENDING → ASSIGNED: 不影响设备状态(设备可能还在处理其他任务)
* - ASSIGNED → ARRIVED: 设备状态 → BUSY开始执行任务
* - ARRIVED → PAUSED: 设备状态 → PAUSED任务暂停
* - PAUSED → ARRIVED/BUSY: 设备状态 → BUSY任务恢复
* - ARRIVED → COMPLETED: 设备状态 → IDLE任务完成检查是否还有等待任务
* - 任意状态 → CANCELLED: 清除设备当前工单,检查是否还有等待任务
*
* @param event 工单状态变更事件
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async("ops-task-executor")
public void onOrderStateChanged(OrderStateChangedEvent event) {
try {
// 只处理保洁类型的工单
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
log.debug("[BadgeDeviceStatusEventHandler] 收到工单状态变更事件: orderId={}, {} → {}, operatorId={}",
event.getOrderId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId());
// 获取设备ID从 payload 或 operatorId 获取)
Long deviceId = getDeviceIdFromEvent(event);
if (deviceId == null) {
log.debug("[BadgeDeviceStatusEventHandler] 无法获取设备ID跳过处理: orderId={}", event.getOrderId());
return;
}
// 根据状态转换更新设备状态
handleStatusTransition(deviceId, event);
} catch (Exception e) {
log.error("[BadgeDeviceStatusEventHandler] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e);
}
}
/**
* 根据状态转换更新设备状态
*/
private void handleStatusTransition(Long deviceId, OrderStateChangedEvent event) {
WorkOrderStatusEnum newStatus = event.getNewStatus();
switch (newStatus) {
case PENDING:
// 工单已分配,不影响设备状态(设备可能还在处理其他任务)
// 设置设备当前工单
badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId());
break;
case ARRIVED:
// 保洁员已到岗,开始执行任务
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
event.getOperatorId(), "工单开始执行: " + event.getOrderCode());
badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId());
break;
case PAUSED:
// 任务暂停
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED,
event.getOperatorId(), event.getRemark());
break;
case COMPLETED:
// 任务完成
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否还有等待任务,如果有则保持 BUSY否则转为 IDLE
checkAndUpdateDeviceStatusAfterCompletion(deviceId, event);
break;
case CANCELLED:
// 工单取消
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否还有等待任务,如果有则保持 BUSY否则转为 IDLE
checkAndUpdateDeviceStatusAfterCompletion(deviceId, event);
break;
default:
log.debug("[BadgeDeviceStatusEventHandler] 不处理的状态: {}", newStatus);
break;
}
}
/**
* 任务完成或取消后,检查并更新设备状态
* <p>
* 如果有等待任务,保持 BUSY 状态
* 如果没有等待任务,转为 IDLE 状态
*/
private void checkAndUpdateDeviceStatusAfterCompletion(Long deviceId, OrderStateChangedEvent event) {
// 检查是否还有等待任务(通过查询队列)
// 这里简化处理,直接转为 IDLE
// 实际业务中可能需要查询队列判断是否还有等待任务
// 检查是否是被P0打断的任务恢复
boolean isResumeFromInterrupt = event.hasPayload("interruptReason")
&& "RESUME_FROM_INTERRUPT".equals(event.getPayloadString("interruptReason"));
if (isResumeFromInterrupt) {
// 恢复被中断的任务,保持 BUSY
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
event.getOperatorId(), "恢复被中断的任务");
} else {
// 正常完成,转为 IDLE
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE,
event.getOperatorId(), "任务完成,转为空闲");
}
}
/**
* 从事件中获取设备ID
* <p>
* 优先级:
* 1. payload.deviceId
* 2. operatorId如果 operatorType 是 DEVICE
* 3. 通过工单查询(需要额外的查询,暂不实现)
*/
private Long getDeviceIdFromEvent(OrderStateChangedEvent event) {
// 优先从 payload 获取
Long deviceId = event.getPayloadLong("deviceId");
if (deviceId != null) {
return deviceId;
}
// TODO: 可以通过工单ID查询获取设备ID
// 这需要注入 OrderService 或 OrderMapper
return null;
}
/**
* 处理 P0 紧急任务打断事件
* <p>
* 当 P0 任务需要打断当前任务时:
* 1. 将被打断的设备状态转为 PAUSED
* 2. 记录被打断的工单ID
*
* @param deviceId 被打断的设备ID
* @param interruptedOrderId 被打断的工单ID
* @param urgentOrderId P0紧急工单ID
*/
public void handleP0Interrupt(Long deviceId, Long interruptedOrderId, Long urgentOrderId) {
log.info("[BadgeDeviceStatusEventHandler] P0紧急任务打断: deviceId={}, interruptedOrderId={}, urgentOrderId={}",
deviceId, interruptedOrderId, urgentOrderId);
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED,
null, "被P0紧急任务打断: " + urgentOrderId);
}
/**
* 处理 P0 任务完成后的恢复事件
* <p>
* 当 P0 任务完成后,恢复被打断的任务:
* 1. 将设备状态转为 BUSY
* 2. 设置当前工单为被打断的工单
*
* @param deviceId 设备ID
* @param interruptedOrderId 被打断的工单ID
*/
public void handleP0Resume(Long deviceId, Long interruptedOrderId) {
log.info("[BadgeDeviceStatusEventHandler] P0任务完成恢复被中断任务: deviceId={}, interruptedOrderId={}",
deviceId, interruptedOrderId);
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY,
null, "P0任务完成恢复被中断的任务");
badgeDeviceStatusService.setCurrentOrder(deviceId, interruptedOrderId);
}
}

View File

@@ -1,5 +1,6 @@
package com.viewsh.module.ops.environment.integration.listener;
import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO;
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;
@@ -100,6 +101,7 @@ public class BadgeDeviceStatusEventListener {
* 根据工单状态更新设备工牌关联
*/
private void handleOrderStatusTransition(Long deviceId, Long orderId, WorkOrderStatusEnum newStatus, OrderStateChangedEvent event) {
var waitingTasks = orderQueueService.getWaitingTasksByUserId(deviceId);
switch (newStatus) {
case DISPATCHED:
// 工单已推送到工牌,设置工单关联,设备状态转为 BUSY
@@ -122,9 +124,18 @@ public class BadgeDeviceStatusEventListener {
break;
case PAUSED:
// 任务暂停,设备状态转为 PAUSED
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停");
log.info("[BadgeDeviceStatusEventListener] 任务暂停,设备状态转为 PAUSED: deviceId={}", deviceId);
// 检查是否是 P0 打断场景
Long urgentOrderId = event.getPayloadLong("urgentOrderId");
if (urgentOrderId != null) {
// P0 打断场景:不修改设备状态,保持当前状态
// 紧接着会有 P0 工单的 DISPATCHED 事件,将当前工单更新为 P0 工单
log.info("[BadgeDeviceStatusEventListener] P0打断场景工单已暂停等待P0工单派发: pausedOrderId={}, urgentOrderId={}, deviceId={}",
orderId, urgentOrderId, deviceId);
} else {
// 普通暂停场景:设备状态转为 PAUSED
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停");
log.info("[BadgeDeviceStatusEventListener] 任务暂停,设备状态转为 PAUSED: deviceId={}", deviceId);
}
break;
case COMPLETED:
@@ -132,7 +143,6 @@ public class BadgeDeviceStatusEventListener {
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否有等待任务,决定设备状态
var waitingTasks = orderQueueService.getWaitingTasksByUserId(deviceId);
if (waitingTasks != null && !waitingTasks.isEmpty()) {
// 有等待任务,设备状态保持 BUSY由后续 DISPATCHED 事件更新)
log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}",
@@ -145,10 +155,29 @@ public class BadgeDeviceStatusEventListener {
break;
case CANCELLED:
// 工单取消,清除工单关联,设备状态转为 IDLE
badgeDeviceStatusService.clearCurrentOrder(deviceId);
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消");
log.info("[BadgeDeviceStatusEventListener] 工单已取消,清除设备工单关联: deviceId={}", deviceId);
// 检查被取消的工单是否是设备当前正在执行的工单
BadgeDeviceStatusDTO deviceStatus = badgeDeviceStatusService.getBadgeStatus(deviceId);
Long currentOrderId = deviceStatus != null ? deviceStatus.getCurrentOpsOrderId() : null;
if (orderId.equals(currentOrderId)) {
// 取消的是当前正在执行的工单,清除工单关联
badgeDeviceStatusService.clearCurrentOrder(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);
}
} else {
// 取消的不是当前工单(可能是队列中的等待任务),不需要修改设备状态
log.debug("[BadgeDeviceStatusEventListener] 取消的工单非当前执行工单,跳过设备状态更新: cancelledOrderId={}, currentOrderId={}, deviceId={}",
orderId, currentOrderId, deviceId);
}
break;
default:

View File

@@ -19,6 +19,16 @@ import java.util.stream.Collectors;
* 工牌设备状态服务实现
* <p>
* 基于 Redis Hash 存储设备状态Set 维护区域设备索引
* <p>
* 职责:
* 1. 设备状态管理IDLE/BUSY/PAUSED/OFFLINE
* 2. 设备与工单关联管理
* 3. 区域设备索引管理
* 4. 心跳超时检查
* <p>
* 设计说明:
* - 状态变更事件由 {@link com.viewsh.module.ops.environment.integration.listener.BadgeDeviceStatusEventListener} 处理
* - 本类只提供基础的服务方法
*
* @author lzh
*/