diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/fsm/listener/CleanOrderStateChangeListener.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/fsm/listener/CleanOrderStateChangeListener.java new file mode 100644 index 0000000..eaf9a75 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/fsm/listener/CleanOrderStateChangeListener.java @@ -0,0 +1,79 @@ +package com.viewsh.module.ops.environment.service.fsm.listener; + +import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent; +import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener; +import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 保洁工单状态变更监听器 + * 职责:监听工单状态变更,执行保洁特定业务逻辑 + * + * @author lzh + */ +@Slf4j +@Component +public class CleanOrderStateChangeListener implements OrderStateChangeListener { + + @Resource + private CleanOrderService cleanOrderService; + + @Override + public void onStateChanged(OrderStateChangedEvent event) { + // 只处理保洁类型的工单 + if (!"CLEAN".equals(event.getOrder().getOrderType())) { + return; + } + + log.info("保洁工单状态变更: orderId={}, {} -> {}, operatorId={}", + event.getOrder().getId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId()); + + // 根据新状态执行相应逻辑 + switch (event.getNewStatus()) { + case QUEUED: + // 入队:无需特殊处理 + break; + + case DISPATCHED: + // 已推送:推送到工牌,由业务层处理 + break; + + case CONFIRMED: + // 确认:保洁员按键确认,由业务层处理保洁员状态 + log.info("保洁工单已确认: orderId={}, cleanerId={}", + event.getOrder().getId(), event.getOperatorId()); + break; + + case ARRIVED: + // 到岗:记录到岗时间到扩展表 + cleanOrderService.recordArrivedTime(event.getOrder().getId()); + break; + + case PAUSED: + // 暂停:已在CleanOrderService.pauseCleanOrder()中处理 + break; + + case COMPLETED: + // 完成:计算作业时长,更新扩展表 + cleanOrderService.calculateDuration(event.getOrder().getId()); + + // 触发自动推送下一个任务 + if (event.getOperatorId() != null) { + cleanOrderService.autoDispatchNextOrder(event.getOrder().getId(), event.getOperatorId()); + } + break; + + case CANCELLED: + // 取消:清理相关状态 + break; + + default: + // 其他状态无需特殊处理 + break; + } + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java new file mode 100644 index 0000000..a5a0f23 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java @@ -0,0 +1,59 @@ +package com.viewsh.module.ops.service.event; + +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderEventDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderEventMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 工单事件服务 + * 职责:记录工单状态变更事件流 + * + * @author lzh + */ +@Slf4j +@Service +public class OpsOrderEventService { + + @Resource + private OpsOrderEventMapper eventMapper; + + /** + * 记录事件 + * + * @param opsOrderId 工单ID + * @param fromStatus 原状态 + * @param toStatus 新状态 + * @param eventType 事件类型 + * @param operatorType 操作人类型 + * @param operatorId 操作人ID + * @param remark 说明 + */ + public void recordEvent(Long opsOrderId, + String fromStatus, + String toStatus, + String eventType, + String operatorType, + Long operatorId, + String remark) { + OpsOrderEventDO event = OpsOrderEventDO.builder() + .opsOrderId(opsOrderId) + .fromStatus(fromStatus) + .toStatus(toStatus) + .eventType(eventType) + .operatorType(operatorType) + .operatorId(operatorId) + .eventTime(LocalDateTime.now()) + .remark(remark) + .build(); + + eventMapper.insert(event); + + log.debug("记录工单事件: orderId={}, {} -> {}, eventType={}", + opsOrderId, fromStatus, toStatus, eventType); + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/OrderStateMachine.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/OrderStateMachine.java new file mode 100644 index 0000000..c1c30fc --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/OrderStateMachine.java @@ -0,0 +1,271 @@ +package com.viewsh.module.ops.service.fsm; + +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 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.*; + +/** + * 工单状态机核心 + * 职责: + * 1. 管理工单状态转换规则 + * 2. 执行状态转换并记录事件 + * 3. 触发状态变更监听器 + * + * @author lzh + */ +@Slf4j +@Component +public class OrderStateMachine { + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private OpsOrderEventService eventService; + + /** + * 状态转换规则(清晰可见) + * Key: 当前状态 + * Value: 允许转换到的目标状态集合 + * + * 通用流程:PENDING → DISPATCHED → ARRIVED → COMPLETED + * 保洁流程:PENDING → QUEUED → DISPATCHED → CONFIRMED → ARRIVED → COMPLETED + */ + private static final Map> TRANSITIONS = Map.of( + // 初始状态 + WorkOrderStatusEnum.PENDING, Set.of( + WorkOrderStatusEnum.QUEUED, // 保洁业务:入队 + WorkOrderStatusEnum.DISPATCHED, // 通用业务:直接派单 + WorkOrderStatusEnum.CANCELLED + ), + + // 保洁业务特有状态 + WorkOrderStatusEnum.QUEUED, Set.of( + WorkOrderStatusEnum.DISPATCHED, // 推送到工牌 + WorkOrderStatusEnum.CANCELLED + ), + + WorkOrderStatusEnum.DISPATCHED, Set.of( + WorkOrderStatusEnum.CONFIRMED, // 保洁员确认 + WorkOrderStatusEnum.ARRIVED, // 通用业务:直接到岗 + WorkOrderStatusEnum.CANCELLED + ), + + WorkOrderStatusEnum.CONFIRMED, Set.of( + WorkOrderStatusEnum.ARRIVED, // 感知信标开始作业 + WorkOrderStatusEnum.CANCELLED + ), + + // 作业中 + WorkOrderStatusEnum.ARRIVED, Set.of( + WorkOrderStatusEnum.PAUSED, // 暂停作业 + WorkOrderStatusEnum.COMPLETED // 完成作业 + ), + + // 暂停状态 + WorkOrderStatusEnum.PAUSED, Set.of( + WorkOrderStatusEnum.ARRIVED, // 恢复作业 + WorkOrderStatusEnum.CANCELLED + ), + + // 终态 + WorkOrderStatusEnum.COMPLETED, Collections.emptySet(), + WorkOrderStatusEnum.CANCELLED, Collections.emptySet() + ); + + /** + * 状态变更监听器列表(支持扩展) + */ + private final List listeners = new ArrayList<>(); + + /** + * 注册监听器 + * + * @param listener 监听器实例 + */ + public void registerListener(OrderStateChangeListener listener) { + if (listener != null) { + listeners.add(listener); + log.info("注册状态监听器: {}", listener.getClass().getSimpleName()); + } + } + + /** + * 执行状态转换(核心方法) + * + * @param order 工单对象 + * @param newStatus 目标状态 + * @param operatorType 操作人类型 + * @param operatorId 操作人ID + * @param remark 说明 + */ + @Transactional(rollbackFor = Exception.class) + public void transition(OpsOrderDO order, + WorkOrderStatusEnum newStatus, + OperatorTypeEnum operatorType, + Long operatorId, + String remark) { + + // 1. 参数校验 + if (order == null) { + throw new IllegalArgumentException("工单对象不能为空"); + } + if (newStatus == null) { + throw new IllegalArgumentException("目标状态不能为空"); + } + + // 2. 校验状态转换合法性 + WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus()); + validateTransition(currentStatus, newStatus); + + // 3. 更新工单状态和相关字段 + WorkOrderStatusEnum oldStatus = currentStatus; + order.setStatus(newStatus.name()); + updateStatusFields(order, newStatus); + opsOrderMapper.updateById(order); + + // 4. 记录事件流 + eventService.recordEvent( + order.getId(), + oldStatus.name(), + newStatus.name(), + determineEventType(newStatus), + operatorType.getType(), + operatorId, + remark + ); + + // 5. 触发监听器(扩展点) + notifyListeners(order, oldStatus, newStatus, operatorType, operatorId, remark); + + log.info("工单状态转换成功: orderId={}, {} -> {}, operatorType={}, operatorId={}", + order.getId(), oldStatus, newStatus, operatorType, operatorId); + } + + /** + * 校验状态转换是否合法 + */ + private void validateTransition(WorkOrderStatusEnum currentStatus, WorkOrderStatusEnum newStatus) { + Set allowedTargets = TRANSITIONS.get(currentStatus); + + if (allowedTargets == null || !allowedTargets.contains(newStatus)) { + throw new IllegalStateException(String.format( + "非法状态转换: %s -> %s,该转换不被允许", + currentStatus.name(), newStatus.name() + )); + } + } + + /** + * 根据状态更新相关字段 + */ + private void updateStatusFields(OpsOrderDO order, WorkOrderStatusEnum newStatus) { + switch (newStatus) { + case QUEUED: + // 入队时无需特殊处理 + break; + + case DISPATCHED: + // 派单时记录派单时间(如有需要) + break; + + case CONFIRMED: + // 确认时,保洁员状态由 CleanOrderService 处理 + break; + + case ARRIVED: + // 到岗时记录开始时间 + order.setStartTime(LocalDateTime.now()); + break; + + case PAUSED: + // 暂停时,记录暂停时间由保洁业务线处理 + break; + + case COMPLETED: + // 完成时记录结束时间 + order.setEndTime(LocalDateTime.now()); + break; + + case CANCELLED: + // 取消时记录结束时间 + order.setEndTime(LocalDateTime.now()); + break; + + default: + break; + } + } + + /** + * 根据目标状态确定事件类型 + */ + private String determineEventType(WorkOrderStatusEnum newStatus) { + return switch (newStatus) { + case QUEUED -> "QUEUED"; + case DISPATCHED -> "DISPATCHED"; + case CONFIRMED -> "CONFIRMED"; + case ARRIVED -> "ACCEPT"; + case PAUSED -> "PAUSE"; + case COMPLETED -> "COMPLETE"; + case CANCELLED -> "CANCEL"; + default -> "STATUS_CHANGE"; + }; + } + + /** + * 触发所有监听器 + */ + private void notifyListeners(OpsOrderDO order, + WorkOrderStatusEnum oldStatus, + WorkOrderStatusEnum newStatus, + OperatorTypeEnum operatorType, + Long operatorId, + String remark) { + if (listeners.isEmpty()) { + return; + } + + 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 getAllowedTransitions(WorkOrderStatusEnum currentStatus) { + return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet()); + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/event/OrderStateChangedEvent.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/event/OrderStateChangedEvent.java new file mode 100644 index 0000000..f04cc14 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/event/OrderStateChangedEvent.java @@ -0,0 +1,55 @@ +package com.viewsh.module.ops.service.fsm.event; + +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.enums.OperatorTypeEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 工单状态变更事件 + * + * @author lzh + */ +@Data +@Builder +public class OrderStateChangedEvent { + + /** + * 工单对象 + */ + private OpsOrderDO order; + + /** + * 原状态 + */ + private WorkOrderStatusEnum oldStatus; + + /** + * 新状态 + */ + private WorkOrderStatusEnum newStatus; + + /** + * 操作人类型 + */ + private OperatorTypeEnum operatorType; + + /** + * 操作人ID + */ + private Long operatorId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + /** + * 说明 + */ + private String remark; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/listener/OrderStateChangeListener.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/listener/OrderStateChangeListener.java new file mode 100644 index 0000000..9263ba5 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/fsm/listener/OrderStateChangeListener.java @@ -0,0 +1,34 @@ +package com.viewsh.module.ops.service.fsm.listener; + +import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent; + +/** + * 工单状态变更监听器接口 + * 用于扩展业务逻辑,各业务线可注册自己的监听器 + * + * 使用示例: + *
+ * @Component
+ * public class CleanOrderStateChangeListener implements OrderStateChangeListener {
+ *     @Override
+ *     public void onStateChanged(OrderStateChangedEvent event) {
+ *         // 处理保洁工单状态变更
+ *         if ("CLEAN".equals(event.getOrder().getOrderType())) {
+ *             // 自定义逻辑
+ *         }
+ *     }
+ * }
+ * 
+ * + * @author lzh + */ +public interface OrderStateChangeListener { + + /** + * 状态变更时触发 + * + * @param event 状态变更事件 + */ + void onStateChanged(OrderStateChangedEvent event); + +} diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/config/OrderStateMachineConfig.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/config/OrderStateMachineConfig.java new file mode 100644 index 0000000..407f09a --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/config/OrderStateMachineConfig.java @@ -0,0 +1,38 @@ +package com.viewsh.module.ops.config; + +import com.viewsh.module.ops.service.fsm.OrderStateMachine; +import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +/** + * 工单状态机配置 + * 职责:自动注册所有状态监听器 + * + * @author lzh + */ +@Slf4j +@Configuration +public class OrderStateMachineConfig { + + @Resource + private OrderStateMachine orderStateMachine; + + /** + * 注册所有状态监听器 + * 使用 Bean 定义的方式,Spring 会自动收集所有 OrderStateChangeListener 类型的 Bean + */ + @Bean + public Boolean registerStateChangeListeners(List listeners) { + if (listeners != null && !listeners.isEmpty()) { + listeners.forEach(orderStateMachine::registerListener); + log.info("工单状态机监听器注册完成,共{}个监听器", listeners.size()); + } + return true; + } + +}