feat(ops): 安保工单模块完整实现 #1

Merged
lzh merged 17 commits from feat/security-work-order into master 2026-03-15 16:44:14 +08:00
Showing only changes of commit 4d36bf5b1c - Show all commits

View File

@@ -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;
/**
* 安保工单事件监听器
* <p>
* 监听工单生命周期事件,处理安保业务特有逻辑:
* - 工单创建后触发自动派单
* - 状态变更时记录扩展表时间点
* - 统一记录业务日志(所有状态变更)
*
* @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;
// ==================== 工单创建事件 ====================
/**
* 工单创建事件 - 异步触发自动派单
* <p>
* {@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);
}
}
}