diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/integration/listener/SecurityOrderEventListener.java b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/integration/listener/SecurityOrderEventListener.java new file mode 100644 index 0000000..602adc5 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/integration/listener/SecurityOrderEventListener.java @@ -0,0 +1,256 @@ +package com.viewsh.module.ops.security.integration.listener; + +import com.viewsh.module.ops.core.event.OrderCreatedEvent; +import com.viewsh.module.ops.core.event.OrderStateChangedEvent; +import com.viewsh.module.ops.core.event.OrderCompletedEvent; +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; +import com.viewsh.module.ops.enums.OperatorTypeEnum; +import com.viewsh.module.ops.enums.PriorityEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.enums.WorkOrderTypeEnum; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule; +import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; +import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO; +import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import java.time.LocalDateTime; + +/** + * 安保工单事件监听器 + *

+ * 监听工单生命周期事件,处理安保业务特有逻辑: + * - 工单创建后触发自动派单 + * - 状态变更时记录扩展表时间点 + * - 统一记录业务日志(所有状态变更) + * + * @author lzh + */ +@Slf4j +@Component +public class SecurityOrderEventListener { + + private static final String ORDER_TYPE_SECURITY = WorkOrderTypeEnum.SECURITY.getType(); + + @Resource + private OpsOrderSecurityExtMapper securityExtMapper; + + @Resource + private DispatchEngine dispatchEngine; + + @Resource + private EventLogRecorder eventLogRecorder; + + // ==================== 工单创建事件 ==================== + + /** + * 工单创建事件 - 异步触发自动派单 + *

+ * {@code @Async} + {@code @TransactionalEventListener(AFTER_COMMIT)} 组合: + * Spring 先等事务提交,再在异步线程池中执行本方法。 + */ + @Async("ops-task-executor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onOrderCreated(OrderCreatedEvent event) { + if (!ORDER_TYPE_SECURITY.equals(event.getOrderType())) { + return; + } + log.info("安保工单创建事件: orderId={}, orderCode={}", event.getOrderId(), event.getOrderCode()); + + try { + OrderDispatchContext context = OrderDispatchContext.builder() + .orderId(event.getOrderId()) + .orderCode(event.getOrderCode()) + .orderTitle(event.getTitle()) + .businessType(ORDER_TYPE_SECURITY) + .areaId(event.getAreaId()) + .priority(PriorityEnum.fromPriority(event.getPriority())) + .build(); + + DispatchResult result = dispatchEngine.dispatch(context); + + if (result.isSuccess()) { + log.info("安保工单自动派单完成: orderId={}, assigneeId={}", event.getOrderId(), result.getAssigneeId()); + // 记录派单成功日志 + recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, + "自动派单成功,分配给: " + result.getAssigneeName(), + event.getOrderId(), result.getAssigneeId()); + } else { + log.warn("安保工单自动派单失败: orderId={}, reason={}", event.getOrderId(), result.getMessage()); + // 记录派单失败日志 + recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, + "自动派单失败: " + result.getMessage(), + event.getOrderId(), null); + } + } catch (Exception e) { + log.error("安保工单自动派单失败: orderId={}", event.getOrderId(), e); + recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, + "自动派单异常: " + e.getMessage(), + event.getOrderId(), null); + } + } + + // ==================== 状态变更事件 ==================== + + /** + * 状态变更事件 - 记录扩展表时间点 + 业务日志 + */ + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onOrderStateChanged(OrderStateChangedEvent event) { + if (!ORDER_TYPE_SECURITY.equals(event.getOrderType())) { + return; + } + + WorkOrderStatusEnum newStatus = event.getNewStatus(); + Long orderId = event.getOrderId(); + + log.info("安保工单状态变更: orderId={}, {} -> {}", orderId, event.getOldStatus(), newStatus); + + switch (newStatus) { + case DISPATCHED -> handleDispatched(orderId, event); + case CONFIRMED -> handleConfirmed(orderId, event); + case COMPLETED -> handleCompleted(orderId, event); + case CANCELLED -> handleCancelled(orderId, event); + case PAUSED -> handlePaused(orderId, event); + default -> log.debug("安保工单状态变更无需额外处理: orderId={}, status={}", orderId, newStatus); + } + } + + /** + * 工单完成事件 - 自动派送下一个任务 + */ + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onOrderCompleted(OrderCompletedEvent event) { + if (!ORDER_TYPE_SECURITY.equals(event.getOrderType())) { + return; + } + if (event.getAssigneeId() != null) { + try { + dispatchEngine.autoDispatchNext(event.getOrderId(), event.getAssigneeId()); + } catch (Exception e) { + log.error("安保工单完成后自动派送下一个失败: orderId={}", event.getOrderId(), e); + } + } + } + + // ==================== 状态处理方法 ==================== + + private void handleDispatched(Long orderId, OrderStateChangedEvent event) { + // 1. 记录下发时间 + OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); + extUpdate.setOpsOrderId(orderId); + extUpdate.setDispatchedTime(LocalDateTime.now()); + securityExtMapper.insertOrUpdateSelective(extUpdate); + + // 2. 业务日志 + Long assigneeId = event.getPayloadLong("assigneeId"); + String assigneeName = (String) event.getPayload().get("assigneeName"); + String message = assigneeName != null + ? String.format("工单已派发给 %s", assigneeName) + : "工单已派发"; + + // 如果是从 PAUSED 恢复,补充说明 + if (event.getOldStatus() == WorkOrderStatusEnum.PAUSED) { + message = "工单从暂停恢复,重新派发"; + } + + recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, message, orderId, assigneeId); + } + + private void handleConfirmed(Long orderId, OrderStateChangedEvent event) { + // 1. 记录确认时间 + OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); + extUpdate.setOpsOrderId(orderId); + extUpdate.setConfirmedTime(LocalDateTime.now()); + securityExtMapper.insertOrUpdateSelective(extUpdate); + + // 2. 业务日志 + Long operatorId = event.getOperatorId(); + recordLog(EventDomain.DISPATCH, LogType.ORDER_CONFIRM, "安保人员确认接单", orderId, operatorId); + } + + private void handleCompleted(Long orderId, OrderStateChangedEvent event) { + // 1. 记录完成时间 + OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); + extUpdate.setOpsOrderId(orderId); + extUpdate.setCompletedTime(LocalDateTime.now()); + securityExtMapper.insertOrUpdateSelective(extUpdate); + + // 2. 业务日志(区分自动完单 vs 人工完单) + Long operatorId = event.getOperatorId(); + OperatorTypeEnum operatorType = event.getOperatorType(); + String remark = event.getRemark(); + + String message; + if (operatorType == OperatorTypeEnum.SYSTEM || operatorId == null) { + message = "系统自动完单"; + if (remark != null && !remark.isEmpty()) { + message += "(" + remark + ")"; + } + } else { + message = "安保人员提交处理结果"; + } + + recordLog(EventDomain.DISPATCH, LogType.ORDER_COMPLETED, message, orderId, operatorId); + } + + private void handleCancelled(Long orderId, OrderStateChangedEvent event) { + Long operatorId = event.getOperatorId(); + String remark = event.getRemark(); + String message = "安保工单已取消"; + if (remark != null && !remark.isEmpty()) { + message += "(" + remark + ")"; + } + + recordLog(EventDomain.DISPATCH, LogType.ORDER_CANCELLED, message, orderId, operatorId); + } + + private void handlePaused(Long orderId, OrderStateChangedEvent event) { + Long operatorId = event.getOperatorId(); + String remark = event.getRemark(); + String message = "安保工单已暂停"; + if (remark != null && !remark.isEmpty()) { + message += "(" + remark + ")"; + } + + recordLog(EventDomain.DISPATCH, LogType.ORDER_PAUSED, message, orderId, operatorId); + } + + // ==================== 日志辅助方法 ==================== + + /** + * 统一记录安保业务日志 + */ + private void recordLog(EventDomain domain, LogType logType, String message, + Long orderId, Long personId) { + try { + EventLogRecord.EventLogRecordBuilder builder = EventLogRecord.builder() + .module(LogModule.SECURITY) + .domain(domain) + .eventType(logType.getCode()) + .message(message) + .targetId(orderId) + .targetType("order"); + + if (personId != null) { + builder.personId(personId); + } + + eventLogRecorder.record(builder.build()); + } catch (Exception e) { + log.warn("[SecurityOrderEventListener] 记录业务日志失败: orderId={}, eventType={}", + orderId, logType.getCode(), e); + } + } + +}