chore: 【ops】状态机实现/状态切换事件发布/保洁监听处理事件
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
package com.viewsh.module.ops.environment.handler;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.viewsh.module.ops.core.event.OrderCompletedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderCleanExtDO;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderCleanExtMapper;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.environment.constants.CleanNotificationConstants;
|
||||
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.VoiceBroadcastDeduplicationService;
|
||||
import com.viewsh.module.iot.api.device.IotDeviceControlApi;
|
||||
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO;
|
||||
import com.viewsh.module.system.api.notify.NotifyMessageSendApi;
|
||||
import com.viewsh.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
||||
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 java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 保洁工单事件处理器
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 订阅工单状态变更事件,处理保洁业务特定逻辑
|
||||
* 2. 记录保洁扩展信息(到岗时间、完成时间、暂停时间等)
|
||||
* 3. 更新保洁员状态
|
||||
* 4. 发送通知(语音播报、站内信、IoT震动)
|
||||
* <p>
|
||||
* 设计说明:
|
||||
* - 使用 @EventListener 订阅领域事件
|
||||
* - 业务逻辑同步执行
|
||||
* - 通知逻辑异步执行(@Async)
|
||||
* - 消息内容使用 {@link CleanNotificationConstants} 统一管理
|
||||
* <p>
|
||||
* - 未来迁移 RocketMQ:只需修改 @EventListener 为 @RocketMQMessageListener
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CleanOrderEventHandler {
|
||||
|
||||
@Resource
|
||||
private CleanerStatusService cleanerStatusService;
|
||||
|
||||
@Resource
|
||||
private OpsOrderCleanExtMapper cleanExtMapper;
|
||||
|
||||
@Resource
|
||||
private CleanOrderService cleanOrderService;
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private VoiceBroadcastDeduplicationService voiceBroadcastDeduplicationService;
|
||||
|
||||
/**
|
||||
* 站内信发送 API(Feign 客户端,微服务架构下调用 system 服务)
|
||||
*/
|
||||
@Resource
|
||||
private NotifyMessageSendApi notifyMessageSendApi;
|
||||
|
||||
/**
|
||||
* IoT 设备控制 API(Feign 客户端,微服务架构下调用 IoT 服务)
|
||||
*/
|
||||
@Resource
|
||||
private IotDeviceControlApi iotDeviceControlApi;
|
||||
|
||||
/**
|
||||
* 订阅状态变更事件
|
||||
* <p>
|
||||
* 只处理保洁类型的工单(orderType = "CLEAN")
|
||||
*/
|
||||
@EventListener
|
||||
public void onStateChanged(OrderStateChangedEvent event) {
|
||||
// 只处理保洁类型的工单
|
||||
if (!"CLEAN".equals(event.getOrderType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("保洁工单状态变更: orderId={}, {} -> {}, operatorId={}",
|
||||
event.getOrderId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId());
|
||||
|
||||
switch (event.getNewStatus()) {
|
||||
case DISPATCHED:
|
||||
handleDispatched(event);
|
||||
break;
|
||||
case CONFIRMED:
|
||||
handleConfirmed(event);
|
||||
break;
|
||||
case ARRIVED:
|
||||
handleArrived(event);
|
||||
break;
|
||||
case PAUSED:
|
||||
handlePaused(event);
|
||||
break;
|
||||
case COMPLETED:
|
||||
handleCompleted(event);
|
||||
break;
|
||||
case CANCELLED:
|
||||
handleCancelled(event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅工单创建事件
|
||||
*/
|
||||
@EventListener
|
||||
public void onOrderCreated(OrderCreatedEvent event) {
|
||||
if (!"CLEAN".equals(event.getOrderType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("保洁工单已创建: orderId={}, orderCode={}, priority={}",
|
||||
event.getOrderId(), event.getOrderCode(), event.getPriority());
|
||||
|
||||
// 可以在这里触发自动派单逻辑
|
||||
// 但通常派单逻辑在工单创建时直接调用
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅工单完成事件
|
||||
*/
|
||||
@EventListener
|
||||
public void onOrderCompleted(OrderCompletedEvent event) {
|
||||
if (!"CLEAN".equals(event.getOrderType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("保洁工单已完成: orderId={}, assigneeId={}, workDuration={}秒",
|
||||
event.getOrderId(), event.getAssigneeId(), event.getWorkDuration());
|
||||
|
||||
// 自动推送下一个任务
|
||||
if (event.getAssigneeId() != null) {
|
||||
cleanOrderService.autoDispatchNextOrder(event.getOrderId(), event.getAssigneeId());
|
||||
}
|
||||
|
||||
// 发送完成通知(异步)
|
||||
sendOrderCompletedNotification(event.getOrderId());
|
||||
}
|
||||
|
||||
// ==================== 状态处理方法 ====================
|
||||
|
||||
/**
|
||||
* 处理已推送状态(工单已推送到工牌)
|
||||
*/
|
||||
private void handleDispatched(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
Long cleanerId = event.getOperatorId();
|
||||
|
||||
// 发送新工单通知(语音+震动+站内信)
|
||||
sendNewOrderNotification(cleanerId, orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已确认状态(保洁员按下确认按钮)
|
||||
*/
|
||||
private void handleConfirmed(OrderStateChangedEvent event) {
|
||||
Long cleanerId = event.getOperatorId();
|
||||
|
||||
// 更新保洁员状态为忙碌
|
||||
cleanerStatusService.updateStatus(cleanerId,
|
||||
com.viewsh.module.ops.enums.CleanerStatusEnum.BUSY,
|
||||
"确认工单");
|
||||
|
||||
log.info("保洁员已确认工单,状态更新为BUSY: cleanerId={}", cleanerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理到岗事件
|
||||
*/
|
||||
private void handleArrived(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
|
||||
// 1. 记录到岗时间到保洁扩展表
|
||||
OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO();
|
||||
updateObj.setOpsOrderId(orderId);
|
||||
updateObj.setArrivedTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
|
||||
log.info("保洁员已到岗: orderId={}", orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理暂停事件
|
||||
*/
|
||||
private void handlePaused(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
Long operatorId = event.getOperatorId();
|
||||
|
||||
// 检查是否是被P0任务打断
|
||||
String interruptReason = event.getPayloadString("interruptReason");
|
||||
if ("P0_TASK_INTERRUPT".equals(interruptReason)) {
|
||||
Long urgentOrderId = event.getPayloadLong("urgentOrderId");
|
||||
|
||||
log.warn("保洁任务被P0任务打断: orderId={}, urgentOrderId={}", orderId, urgentOrderId);
|
||||
|
||||
// 释放保洁员资源
|
||||
cleanerStatusService.clearCurrentWorkOrder(operatorId);
|
||||
|
||||
// 记录暂停开始时间
|
||||
recordPauseStartTime(orderId);
|
||||
} else {
|
||||
// 普通暂停
|
||||
log.info("保洁任务已暂停: orderId={}, operatorId={}", orderId, operatorId);
|
||||
recordPauseStartTime(orderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完成事件
|
||||
*/
|
||||
private void handleCompleted(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
|
||||
// 计算作业时长
|
||||
Integer actualDuration = cleanOrderService.calculateActualDuration(orderId);
|
||||
log.info("保洁作业完成: orderId={}, actualDuration={}秒", orderId, actualDuration);
|
||||
|
||||
// 记录完成时间和时长到保洁扩展表
|
||||
OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO();
|
||||
updateObj.setOpsOrderId(orderId);
|
||||
updateObj.setCompletedTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
|
||||
// 保洁员状态恢复(由 autoDispatchNextOrder 处理)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理取消事件
|
||||
*/
|
||||
private void handleCancelled(OrderStateChangedEvent event) {
|
||||
Long orderId = event.getOrderId();
|
||||
Long operatorId = event.getOperatorId();
|
||||
|
||||
log.info("保洁工单已取消: orderId={}, operatorId={}", orderId, operatorId);
|
||||
|
||||
// 清理保洁员当前工单
|
||||
if (operatorId != null) {
|
||||
cleanerStatusService.clearCurrentWorkOrder(operatorId);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 通知方法(异步)====================
|
||||
|
||||
/**
|
||||
* 发送新工单通知(语音播报 + 震动提醒 + 站内信)
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendNewOrderNotification(Long cleanerId, Long orderId) {
|
||||
try {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
log.warn("[新工单通知] 工单不存在: orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[新工单通知] cleanerId={}, orderId={}", cleanerId, orderId);
|
||||
|
||||
// 1. 语音播报:使用常量
|
||||
playVoice(cleanerId, CleanNotificationConstants.VoiceMessage.NEW_ORDER);
|
||||
|
||||
// 2. 震动提醒:使用常量
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
CleanNotificationConstants.TemplateCode.NEW_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.newOrderParams(
|
||||
order.getOrderCode(),
|
||||
order.getTitle(),
|
||||
getAreaName(order.getAreaId())
|
||||
));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[新工单通知] 发送失败: cleanerId={}, orderId={}", cleanerId, orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送待办增加通知(支持去重合并)
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param queueCount 当前待办数量
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendQueuedOrderNotification(Long cleanerId, int queueCount) {
|
||||
try {
|
||||
log.info("[待办增加通知] cleanerId={}, queueCount={}", cleanerId, queueCount);
|
||||
|
||||
// 1. 使用去重服务合并播报
|
||||
voiceBroadcastDeduplicationService.recordAndBroadcast(cleanerId, 1, false);
|
||||
|
||||
// 2. 震动提醒(轻量震动):使用常量
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.LIGHT);
|
||||
|
||||
// 3. 发送站内信(可选,避免频繁打扰)
|
||||
// 如果待办数量较多时才发送站内信
|
||||
if (queueCount >= 3) {
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
CleanNotificationConstants.TemplateCode.QUEUED_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.queuedOrderParams(queueCount, 1));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[待办增加通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送下一个任务通知
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param queueCount 待办数量
|
||||
* @param orderTitle 任务标题
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendNextTaskNotification(Long cleanerId, int queueCount, String orderTitle) {
|
||||
try {
|
||||
log.info("[下一任务通知] cleanerId={}, queueCount={}, title={}", cleanerId, queueCount, orderTitle);
|
||||
|
||||
// 1. 语音播报:使用常量类格式化
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatNextTask(queueCount, orderTitle);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒:使用常量
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
CleanNotificationConstants.TemplateCode.NEXT_TASK,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.nextTaskParams(queueCount, orderTitle));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[下一任务通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送P0紧急任务插队通知
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param orderCode 工单编号
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendPriorityUpgradeNotification(Long cleanerId, String orderCode) {
|
||||
try {
|
||||
log.warn("[P0紧急通知] cleanerId={}, orderCode={}", cleanerId, orderCode);
|
||||
|
||||
// 1. 语音播报:使用常量类格式化
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatPriorityUpgrade(orderCode);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
|
||||
// 2. 强烈震动提醒:使用常量
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.STRONG);
|
||||
|
||||
// 3. 发送站内信(高优先级)
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
CleanNotificationConstants.TemplateCode.PRIORITY_UPGRADE,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.priorityUpgradeParams(orderCode, "P0紧急任务"));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[P0紧急通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送任务恢复通知
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param areaName 区域名称
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendTaskResumedNotification(Long cleanerId, String areaName) {
|
||||
try {
|
||||
log.info("[任务恢复通知] cleanerId={}, areaName={}", cleanerId, areaName);
|
||||
|
||||
// 1. 语音播报:使用常量类格式化
|
||||
String voiceMessage = CleanNotificationConstants.VoiceHelper.formatTaskResumed(areaName);
|
||||
playVoice(cleanerId, voiceMessage);
|
||||
|
||||
// 2. 震动提醒:使用常量
|
||||
vibrate(cleanerId, CleanNotificationConstants.VibrationDuration.NORMAL);
|
||||
|
||||
// 3. 发送站内信
|
||||
sendNotifyMessageToMember(cleanerId,
|
||||
CleanNotificationConstants.TemplateCode.TASK_RESUMED,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.taskResumedParams(areaName));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[任务恢复通知] 发送失败: cleanerId={}", cleanerId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工单完成通知(通知巡检员验收)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
@Async("ops-task-executor")
|
||||
public void sendOrderCompletedNotification(Long orderId) {
|
||||
try {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[工单完成通知] orderId={}, orderCode={}, areaId={}",
|
||||
orderId, order.getOrderCode(), order.getAreaId());
|
||||
|
||||
// TODO: 查询该区域的巡检员列表
|
||||
// List<Long> inspectorIds = inspectorService.listInspectorsByArea(order.getAreaId());
|
||||
// for (Long inspectorId : inspectorIds) {
|
||||
// // 发送站内信给巡检员
|
||||
// sendNotifyMessageToAdmin(inspectorId,
|
||||
// CleanNotificationConstants.TemplateCode.ORDER_COMPLETED,
|
||||
// CleanNotificationConstants.NotifyParamsBuilder.orderCompletedParams(
|
||||
// order.getOrderCode(), getAreaName(order.getAreaId()), order.getTitle()));
|
||||
// }
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[工单完成通知] 发送失败: orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== IoT 设备操作方法 ====================
|
||||
|
||||
/**
|
||||
* 语音播报
|
||||
* <p>
|
||||
* 通过 RPC 调用 IoT 服务的设备控制接口
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param message 播报内容
|
||||
*/
|
||||
private void playVoice(Long cleanerId, String message) {
|
||||
try {
|
||||
// 获取保洁员关联的工牌设备ID
|
||||
Long deviceId = getBadgeDeviceId(cleanerId);
|
||||
if (deviceId == null) {
|
||||
log.warn("[语音播报] 保洁员无关联工牌设备: cleanerId={}", cleanerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建服务调用请求
|
||||
IotDeviceServiceInvokeReqDTO reqDTO = new IotDeviceServiceInvokeReqDTO();
|
||||
reqDTO.setDeviceId(deviceId);
|
||||
reqDTO.setIdentifier("playVoice");
|
||||
reqDTO.setParams(MapUtil.<String, Object>builder()
|
||||
.put("text", message)
|
||||
.put("volume", 80)
|
||||
.build());
|
||||
|
||||
// 调用 IoT 服务
|
||||
iotDeviceControlApi.invokeService(reqDTO);
|
||||
log.info("[语音播报] 调用成功: cleanerId={}, deviceId={}, message={}", cleanerId, deviceId, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[语音播报] 调用失败: cleanerId={}, message={}", cleanerId, message, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 震动提醒
|
||||
* <p>
|
||||
* 通过 RPC 调用 IoT 服务的设备控制接口
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param durationMs 震动时长(毫秒)
|
||||
*/
|
||||
private void vibrate(Long cleanerId, int durationMs) {
|
||||
try {
|
||||
// 获取保洁员关联的工牌设备ID
|
||||
Long deviceId = getBadgeDeviceId(cleanerId);
|
||||
if (deviceId == null) {
|
||||
log.warn("[震动提醒] 保洁员无关联工牌设备: cleanerId={}", cleanerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建服务调用请求
|
||||
IotDeviceServiceInvokeReqDTO reqDTO = new IotDeviceServiceInvokeReqDTO();
|
||||
reqDTO.setDeviceId(deviceId);
|
||||
reqDTO.setIdentifier("vibrate");
|
||||
reqDTO.setParams(MapUtil.<String, Object>builder()
|
||||
.put("duration", durationMs)
|
||||
.put("intensity", 50)
|
||||
.build());
|
||||
|
||||
// 调用 IoT 服务
|
||||
iotDeviceControlApi.invokeService(reqDTO);
|
||||
log.info("[震动提醒] 调用成功: cleanerId={}, deviceId={}, durationMs={}", cleanerId, deviceId, durationMs);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[震动提醒] 调用失败: cleanerId={}, durationMs={}", cleanerId, durationMs, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保洁员关联的工牌设备ID
|
||||
* <p>
|
||||
* TODO: 需要根据实际业务逻辑实现
|
||||
* - 方案1:在保洁员表维护关联的设备ID
|
||||
* - 方案2:通过用户ID查询设备表(设备表有userId字段)
|
||||
* - 方案3:通过中间表维护保洁员与设备的关系
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @return 工牌设备ID,无关联返回null
|
||||
*/
|
||||
private Long getBadgeDeviceId(Long cleanerId) {
|
||||
// TODO: 实现获取保洁员关联的工牌设备ID
|
||||
// 当前返回null,实际使用时需要实现具体逻辑
|
||||
// 示例:
|
||||
// OpsCleanerDeviceDO device = cleanerDeviceMapper.selectByCleanerId(cleanerId);
|
||||
// return device != null ? device.getDeviceId() : null;
|
||||
|
||||
log.debug("[getBadgeDeviceId] 查询保洁员设备: cleanerId={}", cleanerId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== 站内信发送方法 ====================
|
||||
|
||||
/**
|
||||
* 发送站内信给 Member 用户(保洁员)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param templateCode 模板代码
|
||||
* @param templateParams 模板参数
|
||||
*/
|
||||
private void sendNotifyMessageToMember(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.sendSingleMessageToMember(reqDTO);
|
||||
log.info("[站内信发送成功] userId={}, templateCode={}", userId, templateCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[站内信发送失败] userId={}, templateCode={}", userId, templateCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送站内信给 Admin 用户(巡检员)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param templateCode 模板代码
|
||||
* @param templateParams 模板参数
|
||||
*/
|
||||
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.info("[站内信发送成功] userId={}, templateCode={}", userId, templateCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[站内信发送失败] userId={}, templateCode={}", userId, templateCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 获取区域名称
|
||||
*/
|
||||
private String getAreaName(Long areaId) {
|
||||
// TODO: 从区域服务获取区域名称
|
||||
return "某区域";
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录暂停开始时间
|
||||
*/
|
||||
private void recordPauseStartTime(Long orderId) {
|
||||
OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO();
|
||||
updateObj.setOpsOrderId(orderId);
|
||||
updateObj.setPauseStartTime(LocalDateTime.now());
|
||||
cleanExtMapper.insertOnDuplicateKeyUpdate(updateObj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.viewsh.module.ops.core.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 工单完成领域事件
|
||||
* <p>
|
||||
* 当工单完成时发布此事件
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OrderCompletedEvent {
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单类型
|
||||
*/
|
||||
private String orderType;
|
||||
|
||||
/**
|
||||
* 工单编号
|
||||
*/
|
||||
private String orderCode;
|
||||
|
||||
/**
|
||||
* 执行人ID
|
||||
*/
|
||||
private Long assigneeId;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
private LocalDateTime completedTime;
|
||||
|
||||
/**
|
||||
* 作业时长(秒)
|
||||
*/
|
||||
private Integer workDuration;
|
||||
|
||||
/**
|
||||
* 完成备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 扩展参数
|
||||
*/
|
||||
@Builder.Default
|
||||
private java.util.Map<String, Object> payload = new java.util.HashMap<>();
|
||||
|
||||
public OrderCompletedEvent addPayload(String key, Object value) {
|
||||
if (this.payload == null) {
|
||||
this.payload = new java.util.HashMap<>();
|
||||
}
|
||||
this.payload.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.viewsh.module.ops.core.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工单创建领域事件
|
||||
* <p>
|
||||
* 当新工单创建时发布此事件
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OrderCreatedEvent {
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单类型
|
||||
*/
|
||||
private String orderType;
|
||||
|
||||
/**
|
||||
* 工单编号
|
||||
*/
|
||||
private String orderCode;
|
||||
|
||||
/**
|
||||
* 工单标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 扩展参数
|
||||
*/
|
||||
@Builder.Default
|
||||
private Map<String, Object> payload = new java.util.HashMap<>();
|
||||
|
||||
public OrderCreatedEvent addPayload(String key, Object value) {
|
||||
if (this.payload == null) {
|
||||
this.payload = new java.util.HashMap<>();
|
||||
}
|
||||
this.payload.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.viewsh.module.ops.core.event;
|
||||
|
||||
/**
|
||||
* 工单事件发布器接口
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 发布工单状态变更事件
|
||||
* 2. 发布工单创建事件
|
||||
* 3. 发布工单完成事件
|
||||
* <p>
|
||||
* 设计说明:
|
||||
* - 使用领域事件模式解耦状态机与业务逻辑
|
||||
* - 业务方通过 @EventListener 订阅事件
|
||||
* - 事件异步发布,不阻塞主流程
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface OrderEventPublisher {
|
||||
|
||||
/**
|
||||
* 发布状态变更事件
|
||||
*
|
||||
* @param event 状态变更事件
|
||||
*/
|
||||
void publishStateChanged(OrderStateChangedEvent event);
|
||||
|
||||
/**
|
||||
* 发布工单创建事件
|
||||
*
|
||||
* @param event 工单创建事件
|
||||
*/
|
||||
void publishOrderCreated(OrderCreatedEvent event);
|
||||
|
||||
/**
|
||||
* 发布工单完成事件
|
||||
*
|
||||
* @param event 工单完成事件
|
||||
*/
|
||||
void publishOrderCompleted(OrderCompletedEvent event);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.viewsh.module.ops.core.event;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 工单事件发布器实现
|
||||
* <p>
|
||||
* 使用 Spring 的 ApplicationEventPublisher 发布事件
|
||||
* 业务方通过 @EventListener 订阅事件
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* @Component
|
||||
* public class CleanOrderEventHandler {
|
||||
* @EventListener
|
||||
* public void onStateChanged(OrderStateChangedEvent event) {
|
||||
* // 处理保洁工单状态变更
|
||||
* if ("CLEAN".equals(event.getOrderType())) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrderEventPublisherImpl implements OrderEventPublisher {
|
||||
|
||||
@Resource
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@Override
|
||||
public void publishStateChanged(OrderStateChangedEvent event) {
|
||||
try {
|
||||
applicationEventPublisher.publishEvent(event);
|
||||
log.debug("状态变更事件已发布: orderId={}, {} -> {}",
|
||||
event.getOrderId(), event.getOldStatus(), event.getNewStatus());
|
||||
} catch (Exception e) {
|
||||
// 事件发布失败不应影响主流程
|
||||
log.error("发布状态变更事件失败: orderId={}", event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishOrderCreated(OrderCreatedEvent event) {
|
||||
try {
|
||||
applicationEventPublisher.publishEvent(event);
|
||||
log.debug("工单创建事件已发布: orderId={}, orderCode={}",
|
||||
event.getOrderId(), event.getOrderCode());
|
||||
} catch (Exception e) {
|
||||
log.error("发布工单创建事件失败: orderId={}", event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishOrderCompleted(OrderCompletedEvent event) {
|
||||
try {
|
||||
applicationEventPublisher.publishEvent(event);
|
||||
log.debug("工单完成事件已发布: orderId={}, assigneeId={}",
|
||||
event.getOrderId(), event.getAssigneeId());
|
||||
} catch (Exception e) {
|
||||
log.error("发布工单完成事件失败: orderId={}", event.getOrderId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.viewsh.module.ops.core.event;
|
||||
|
||||
import com.viewsh.module.ops.enums.OperatorTypeEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工单状态变更领域事件
|
||||
* <p>
|
||||
* 当工单状态发生变更时发布此事件,业务方订阅此事件处理自己的逻辑
|
||||
* <p>
|
||||
* 设计说明:
|
||||
* - 使用领域事件模式解耦状态机与业务逻辑
|
||||
* - 业务方通过 @EventListener 订阅事件
|
||||
* - 支持扩展参数传递额外信息
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OrderStateChangedEvent {
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单类型(CLEAN、REPAIR、SECURITY等)
|
||||
*/
|
||||
private String orderType;
|
||||
|
||||
/**
|
||||
* 工单编号
|
||||
*/
|
||||
private String orderCode;
|
||||
|
||||
/**
|
||||
* 旧状态
|
||||
*/
|
||||
private WorkOrderStatusEnum oldStatus;
|
||||
|
||||
/**
|
||||
* 新状态
|
||||
*/
|
||||
private WorkOrderStatusEnum newStatus;
|
||||
|
||||
/**
|
||||
* 操作人类型
|
||||
*/
|
||||
private OperatorTypeEnum operatorType;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private Long operatorId;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private LocalDateTime eventTime;
|
||||
|
||||
/**
|
||||
* 备注/说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 扩展参数(可选)
|
||||
* 用于传递额外的业务信息
|
||||
* 例如:interruptReason, urgentOrderId
|
||||
*/
|
||||
@Builder.Default
|
||||
private Map<String, Object> payload = new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
* 添加扩展参数
|
||||
*/
|
||||
public OrderStateChangedEvent addPayload(String key, Object value) {
|
||||
if (this.payload == null) {
|
||||
this.payload = new java.util.HashMap<>();
|
||||
}
|
||||
this.payload.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展参数
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getPayload(String key, Class<T> type) {
|
||||
if (this.payload == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = this.payload.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (type.isInstance(value)) {
|
||||
return (T) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展参数(字符串)
|
||||
*/
|
||||
public String getPayloadString(String key) {
|
||||
Object value = getPayload(key, Object.class);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展参数(Long)
|
||||
*/
|
||||
public Long getPayloadLong(String key) {
|
||||
Object value = getPayload(key, Object.class);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return (Long) value;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
return ((Integer) value).longValue();
|
||||
}
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Long.parseLong((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含指定的扩展参数
|
||||
*/
|
||||
public boolean hasPayload(String key) {
|
||||
return this.payload != null && this.payload.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态工厂方法:创建事件
|
||||
*/
|
||||
public static OrderStateChangedEvent of(Long orderId, String orderType,
|
||||
WorkOrderStatusEnum oldStatus,
|
||||
WorkOrderStatusEnum newStatus,
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark) {
|
||||
return OrderStateChangedEvent.builder()
|
||||
.orderId(orderId)
|
||||
.orderType(orderType)
|
||||
.oldStatus(oldStatus)
|
||||
.newStatus(newStatus)
|
||||
.operatorType(operatorType)
|
||||
.operatorId(operatorId)
|
||||
.eventTime(LocalDateTime.now())
|
||||
.remark(remark)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,37 @@
|
||||
package com.viewsh.module.ops.service.fsm;
|
||||
|
||||
import com.viewsh.module.ops.core.event.OrderEventPublisher;
|
||||
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.OperatorTypeEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.service.event.OpsOrderEventService;
|
||||
import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent;
|
||||
import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 工单状态机核心
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 管理工单状态转换规则
|
||||
* 2. 执行状态转换并记录事件
|
||||
* 3. 触发状态变更监听器
|
||||
* 3. 发布状态变更事件(通过事件发布器)
|
||||
* <p>
|
||||
* 设计原则:
|
||||
* - 单一职责:只负责状态转换规则验证和状态管理
|
||||
* - 不触发业务逻辑:业务逻辑通过事件订阅处理
|
||||
* - 事件驱动:状态变更后发布事件,业务方订阅处理
|
||||
* <p>
|
||||
* 变更说明:
|
||||
* - 移除了监听器机制(改为事件发布)
|
||||
* - 状态机只负责状态转换验证和状态更新
|
||||
* - 业务逻辑通过 @EventListener 订阅事件处理
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@@ -34,6 +45,12 @@ public class OrderStateMachine {
|
||||
@Resource
|
||||
private OpsOrderEventService eventService;
|
||||
|
||||
/**
|
||||
* 事件发布器(用于发布状态变更事件)
|
||||
*/
|
||||
@Resource
|
||||
private OrderEventPublisher eventPublisher;
|
||||
|
||||
/**
|
||||
* 状态转换规则(清晰可见)
|
||||
* Key: 当前状态
|
||||
@@ -43,72 +60,63 @@ public class OrderStateMachine {
|
||||
* 保洁流程:PENDING → QUEUED → DISPATCHED → CONFIRMED → ARRIVED → COMPLETED
|
||||
*/
|
||||
private static final Map<WorkOrderStatusEnum, Set<WorkOrderStatusEnum>> TRANSITIONS = Map.of(
|
||||
// 初始状态
|
||||
WorkOrderStatusEnum.PENDING, Set.of(
|
||||
WorkOrderStatusEnum.QUEUED, // 保洁业务:入队
|
||||
WorkOrderStatusEnum.DISPATCHED, // 通用业务:直接派单
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
// 初始状态
|
||||
WorkOrderStatusEnum.PENDING, Set.of(
|
||||
WorkOrderStatusEnum.QUEUED, // 保洁业务:入队
|
||||
WorkOrderStatusEnum.DISPATCHED, // 通用业务:直接派单
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
|
||||
// 保洁业务特有状态
|
||||
WorkOrderStatusEnum.QUEUED, Set.of(
|
||||
WorkOrderStatusEnum.DISPATCHED, // 推送到工牌
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
// 保洁业务特有状态
|
||||
WorkOrderStatusEnum.QUEUED, Set.of(
|
||||
WorkOrderStatusEnum.DISPATCHED, // 推送到工牌
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
|
||||
WorkOrderStatusEnum.DISPATCHED, Set.of(
|
||||
WorkOrderStatusEnum.CONFIRMED, // 保洁员确认
|
||||
WorkOrderStatusEnum.ARRIVED, // 通用业务:直接到岗
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
WorkOrderStatusEnum.DISPATCHED, Set.of(
|
||||
WorkOrderStatusEnum.CONFIRMED, // 保洁员确认
|
||||
WorkOrderStatusEnum.ARRIVED, // 通用业务:直接到岗
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
|
||||
WorkOrderStatusEnum.CONFIRMED, Set.of(
|
||||
WorkOrderStatusEnum.ARRIVED, // 感知信标开始作业
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
WorkOrderStatusEnum.CONFIRMED, Set.of(
|
||||
WorkOrderStatusEnum.ARRIVED, // 感知信标开始作业
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
|
||||
// 作业中
|
||||
WorkOrderStatusEnum.ARRIVED, Set.of(
|
||||
WorkOrderStatusEnum.PAUSED, // 暂停作业
|
||||
WorkOrderStatusEnum.COMPLETED // 完成作业
|
||||
),
|
||||
// 作业中
|
||||
WorkOrderStatusEnum.ARRIVED, Set.of(
|
||||
WorkOrderStatusEnum.PAUSED, // 暂停作业
|
||||
WorkOrderStatusEnum.COMPLETED // 完成作业
|
||||
),
|
||||
|
||||
// 暂停状态
|
||||
WorkOrderStatusEnum.PAUSED, Set.of(
|
||||
WorkOrderStatusEnum.ARRIVED, // 恢复作业
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
// 暂停状态
|
||||
WorkOrderStatusEnum.PAUSED, Set.of(
|
||||
WorkOrderStatusEnum.ARRIVED, // 恢复作业
|
||||
WorkOrderStatusEnum.CANCELLED
|
||||
),
|
||||
|
||||
// 终态
|
||||
WorkOrderStatusEnum.COMPLETED, Collections.emptySet(),
|
||||
WorkOrderStatusEnum.CANCELLED, Collections.emptySet()
|
||||
// 终态
|
||||
WorkOrderStatusEnum.COMPLETED, Collections.emptySet(),
|
||||
WorkOrderStatusEnum.CANCELLED, Collections.emptySet()
|
||||
);
|
||||
|
||||
/**
|
||||
* 状态变更监听器列表(支持扩展)
|
||||
*/
|
||||
private final List<OrderStateChangeListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 注册监听器
|
||||
*
|
||||
* @param listener 监听器实例
|
||||
*/
|
||||
public void registerListener(OrderStateChangeListener listener) {
|
||||
if (listener != null) {
|
||||
listeners.add(listener);
|
||||
log.info("注册状态监听器: {}", listener.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行状态转换(核心方法)
|
||||
* <p>
|
||||
* 此方法负责:
|
||||
* 1. 验证状态转换合法性
|
||||
* 2. 更新工单状态和相关字段
|
||||
* 3. 记录事件流
|
||||
* 4. 发布状态变更事件
|
||||
*
|
||||
* @param order 工单对象
|
||||
* @param newStatus 目标状态
|
||||
* @param order 工单对象
|
||||
* @param newStatus 目标状态
|
||||
* @param operatorType 操作人类型
|
||||
* @param operatorId 操作人ID
|
||||
* @param remark 说明
|
||||
* @param operatorId 操作人ID
|
||||
* @param remark 说明
|
||||
* @throws IllegalArgumentException 参数不合法
|
||||
* @throws IllegalStateException 状态转换不合法
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void transition(OpsOrderDO order,
|
||||
@@ -137,32 +145,57 @@ public class OrderStateMachine {
|
||||
|
||||
// 4. 记录事件流
|
||||
eventService.recordEvent(
|
||||
order.getId(),
|
||||
oldStatus.name(),
|
||||
newStatus.name(),
|
||||
determineEventType(newStatus),
|
||||
operatorType.getType(),
|
||||
operatorId,
|
||||
remark
|
||||
order.getId(),
|
||||
oldStatus.name(),
|
||||
newStatus.name(),
|
||||
determineEventType(newStatus),
|
||||
operatorType.getType(),
|
||||
operatorId,
|
||||
remark
|
||||
);
|
||||
|
||||
// 5. 触发监听器(扩展点)
|
||||
notifyListeners(order, oldStatus, newStatus, operatorType, operatorId, remark);
|
||||
// 5. 发布状态变更事件(替代监听器机制)
|
||||
publishStateChangedEvent(order, oldStatus, newStatus, operatorType, operatorId, remark);
|
||||
|
||||
log.info("工单状态转换成功: orderId={}, {} -> {}, operatorType={}, operatorId={}",
|
||||
order.getId(), oldStatus, newStatus, operatorType, operatorId);
|
||||
order.getId(), oldStatus, newStatus, operatorType, operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态转换是否合法(不执行转换)
|
||||
*
|
||||
* @param fromStatus 当前状态
|
||||
* @param toStatus 目标状态
|
||||
* @return 是否可以转换
|
||||
*/
|
||||
public boolean canTransition(WorkOrderStatusEnum fromStatus, WorkOrderStatusEnum toStatus) {
|
||||
if (fromStatus == null || toStatus == null) {
|
||||
return false;
|
||||
}
|
||||
Set<WorkOrderStatusEnum> allowedTargets = TRANSITIONS.get(fromStatus);
|
||||
return allowedTargets != null && allowedTargets.contains(toStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态允许转换到的目标状态集合
|
||||
*
|
||||
* @param currentStatus 当前状态
|
||||
* @return 允许的目标状态集合
|
||||
*/
|
||||
public Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum currentStatus) {
|
||||
return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet());
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 校验状态转换是否合法
|
||||
*/
|
||||
private void validateTransition(WorkOrderStatusEnum currentStatus, WorkOrderStatusEnum newStatus) {
|
||||
Set<WorkOrderStatusEnum> allowedTargets = TRANSITIONS.get(currentStatus);
|
||||
|
||||
if (allowedTargets == null || !allowedTargets.contains(newStatus)) {
|
||||
if (!canTransition(currentStatus, newStatus)) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"非法状态转换: %s -> %s,该转换不被允许",
|
||||
currentStatus.name(), newStatus.name()
|
||||
"非法状态转换: %s -> %s,该转换不被允许",
|
||||
currentStatus.name(), newStatus.name()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -172,39 +205,33 @@ public class OrderStateMachine {
|
||||
*/
|
||||
private void updateStatusFields(OpsOrderDO order, WorkOrderStatusEnum newStatus) {
|
||||
switch (newStatus) {
|
||||
case QUEUED:
|
||||
case QUEUED -> {
|
||||
// 入队时无需特殊处理
|
||||
break;
|
||||
|
||||
case DISPATCHED:
|
||||
}
|
||||
case DISPATCHED -> {
|
||||
// 派单时记录派单时间(如有需要)
|
||||
break;
|
||||
|
||||
case CONFIRMED:
|
||||
// 确认时,保洁员状态由 CleanOrderService 处理
|
||||
break;
|
||||
|
||||
case ARRIVED:
|
||||
}
|
||||
case CONFIRMED -> {
|
||||
// 确认时,保洁员状态由业务层处理
|
||||
}
|
||||
case ARRIVED -> {
|
||||
// 到岗时记录开始时间
|
||||
order.setStartTime(LocalDateTime.now());
|
||||
break;
|
||||
|
||||
case PAUSED:
|
||||
// 暂停时,记录暂停时间由保洁业务线处理
|
||||
break;
|
||||
|
||||
case COMPLETED:
|
||||
}
|
||||
case PAUSED -> {
|
||||
// 暂停时,记录暂停时间由业务层处理
|
||||
}
|
||||
case COMPLETED -> {
|
||||
// 完成时记录结束时间
|
||||
order.setEndTime(LocalDateTime.now());
|
||||
break;
|
||||
|
||||
case CANCELLED:
|
||||
}
|
||||
case CANCELLED -> {
|
||||
// 取消时记录结束时间
|
||||
order.setEndTime(LocalDateTime.now());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default -> {
|
||||
// 其他状态无需处理
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,47 +252,35 @@ public class OrderStateMachine {
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发所有监听器
|
||||
* 发布状态变更事件
|
||||
* <p>
|
||||
* 替代原有的监听器机制:
|
||||
* - 业务方通过 @EventListener 订阅事件
|
||||
* - 事件异步发布,不阻塞主流程
|
||||
*/
|
||||
private void notifyListeners(OpsOrderDO order,
|
||||
WorkOrderStatusEnum oldStatus,
|
||||
WorkOrderStatusEnum newStatus,
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark) {
|
||||
if (listeners.isEmpty()) {
|
||||
return;
|
||||
private void publishStateChangedEvent(OpsOrderDO order,
|
||||
WorkOrderStatusEnum oldStatus,
|
||||
WorkOrderStatusEnum newStatus,
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark) {
|
||||
try {
|
||||
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
|
||||
.orderId(order.getId())
|
||||
.orderType(order.getOrderType())
|
||||
.orderCode(order.getOrderCode())
|
||||
.oldStatus(oldStatus)
|
||||
.newStatus(newStatus)
|
||||
.operatorType(operatorType)
|
||||
.operatorId(operatorId)
|
||||
.eventTime(LocalDateTime.now())
|
||||
.remark(remark)
|
||||
.build();
|
||||
|
||||
eventPublisher.publishStateChanged(event);
|
||||
} catch (Exception e) {
|
||||
// 事件发布失败不应影响主流程
|
||||
log.error("发布状态变更事件失败: orderId={}", order.getId(), e);
|
||||
}
|
||||
|
||||
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
|
||||
.order(order)
|
||||
.oldStatus(oldStatus)
|
||||
.newStatus(newStatus)
|
||||
.operatorType(operatorType)
|
||||
.operatorId(operatorId)
|
||||
.eventTime(LocalDateTime.now())
|
||||
.remark(remark)
|
||||
.build();
|
||||
|
||||
listeners.forEach(listener -> {
|
||||
try {
|
||||
listener.onStateChanged(event);
|
||||
} catch (Exception e) {
|
||||
// 监听器异常不影响主流程
|
||||
log.error("[状态监听器] 执行失败: listener={}, orderId={}, error={}",
|
||||
listener.getClass().getSimpleName(), order.getId(), e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态允许转换到的目标状态集合
|
||||
*
|
||||
* @param currentStatus 当前状态
|
||||
* @return 允许的目标状态集合
|
||||
*/
|
||||
public Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum currentStatus) {
|
||||
return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user