test: Resolve conflicts with master and complete comprehensive test suite

This commit is contained in:
lzh
2026-01-24 20:35:09 +08:00
5 changed files with 284 additions and 11 deletions

View File

@@ -8,6 +8,9 @@ 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.environment.integration.dto.CleanOrderArriveEventDTO;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
@@ -73,6 +76,9 @@ public class CleanOrderArriveEventHandler implements RocketMQListener<String> {
@Resource
private OrderLifecycleManager orderLifecycleManager;
@Resource
private EventLogRecorder eventLogRecorder;
@Override
public void onMessage(String message) {
try {
@@ -146,7 +152,10 @@ public class CleanOrderArriveEventHandler implements RocketMQListener<String> {
// 5. 通过生命周期管理器执行状态转换DISPATCHED/CONFIRMED -> ARRIVED
orderLifecycleManager.transition(request);
// 6. 更新 Redis 缓存(设备当前工单)
// 6. 记录业务日志
recordOrderArrivedLog(event, request);
// 7. 更新 Redis 缓存(设备当前工单)
cacheDeviceCurrentOrder(event);
log.info("[CleanOrderArriveEventHandler] 工单到岗成功: eventId={}, orderId={}, deviceId={}",
@@ -190,4 +199,36 @@ public class CleanOrderArriveEventHandler implements RocketMQListener<String> {
log.error("[CleanOrderArriveEventHandler] 设备工单缓存更新失败: deviceId={}", event.getDeviceId(), e);
}
}
/**
* 记录工单到岗业务日志
*/
private void recordOrderArrivedLog(CleanOrderArriveEventDTO event, OrderTransitionRequest request) {
try {
// 构建扩展信息
Map<String, Object> extra = new HashMap<>();
extra.put("eventId", event.getEventId());
extra.put("triggerSource", event.getTriggerSource());
extra.put("areaId", event.getAreaId());
if (event.getTriggerData() != null) {
extra.putAll(event.getTriggerData());
}
// 记录日志
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.BEACON)
.eventType("ORDER_ARRIVED")
.message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d]",
event.getDeviceKey(), event.getAreaId()))
.targetId(event.getOrderId())
.targetType("order")
.deviceId(event.getDeviceId())
.payload(extra)
.build());
} catch (Exception e) {
log.warn("[CleanOrderArriveEventHandler] 记录业务日志失败: orderId={}", event.getOrderId(), e);
}
}
}

View File

@@ -9,6 +9,9 @@ import com.viewsh.module.ops.enums.OperatorTypeEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.environment.integration.dto.CleanOrderCompleteEventDTO;
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
@@ -72,6 +75,9 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener<String>
@Resource
private CleanOrderService cleanOrderService;
@Resource
private EventLogRecorder eventLogRecorder;
@Override
public void onMessage(String message) {
try {
@@ -143,10 +149,13 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener<String>
// 5. 通过生命周期管理器执行状态转换ARRIVED -> COMPLETED
orderLifecycleManager.completeOrder(event.getOrderId(), null, remark);
// 6. 清除 Redis 缓存(设备当前工单)
// 6. 记录业务日志
recordOrderCompletedLog(event, order, remark);
// 7. 清除 Redis 缓存(设备当前工单)
clearDeviceCurrentOrder(event.getDeviceId());
// 7. 自动调度下一个任务(优先恢复被中断的任务)
// 8. 自动调度下一个任务(优先恢复被中断的任务)
if (order.getAssigneeId() != null) {
cleanOrderService.autoDispatchNextOrder(event.getOrderId(), order.getAssigneeId());
}
@@ -187,4 +196,49 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener<String>
log.error("[CleanOrderCompleteEventHandler] 设备工单缓存清除失败: deviceId={}", deviceId, e);
}
}
/**
* 记录工单完成业务日志
*/
private void recordOrderCompletedLog(CleanOrderCompleteEventDTO event, OpsOrderDO order, String remark) {
try {
// 构建扩展信息
Map<String, Object> extra = new HashMap<>();
extra.put("eventId", event.getEventId());
extra.put("triggerSource", event.getTriggerSource());
extra.put("areaId", event.getAreaId());
extra.put("completionReason", event.getTriggerData() != null ?
event.getTriggerData().get("completionReason") : "SIGNAL_LOSS_TIMEOUT");
if (event.getTriggerData() != null) {
extra.putAll(event.getTriggerData());
}
// 计算作业时长(分钟)
String durationInfo = "";
if (event.getTriggerData() != null && event.getTriggerData().containsKey("durationMs")) {
Object durationMs = event.getTriggerData().get("durationMs");
if (durationMs != null) {
long durationMinutes = ((Number) durationMs).longValue() / 60000;
durationInfo = String.format(",作业时长: %d分钟", durationMinutes);
extra.put("durationMinutes", durationMinutes);
}
}
// 记录日志
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.BEACON)
.eventType("ORDER_COMPLETED")
.message("信号丢失超时自动完成 [设备:" + event.getDeviceKey() + durationInfo + "]")
.targetId(event.getOrderId())
.targetType("order")
.deviceId(event.getDeviceId())
.personId(order.getAssigneeId())
.payload(extra)
.build());
} catch (Exception e) {
log.warn("[CleanOrderCompleteEventHandler] 记录业务日志失败: orderId={}", event.getOrderId(), e);
}
}
}

View File

@@ -7,6 +7,10 @@ import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqDTO;
import com.viewsh.module.ops.environment.integration.dto.CleanOrderCreateEventDTO;
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
@@ -15,6 +19,8 @@ import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -60,6 +66,9 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
@Resource
private IotDeviceControlApi iotDeviceControlApi;
@Resource
private EventLogRecorder eventLogRecorder;
@Override
public void onMessage(String message) {
try {
@@ -117,7 +126,10 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
// 2. 创建工单(同时创建主表+扩展表)
Long orderId = cleanOrderService.createAutoCleanOrder(createReq);
// 3. 如果是客流触发的工单,重置客流计数器基准值
// 3. 记录业务日志
recordOrderCreatedLog(event, orderId, createReq);
// 4. 如果是客流触发的工单,重置客流计数器基准值
// TODO: 需要优化这个工单是否创建成功,才重置
if ("IOT_TRAFFIC".equals(event.getTriggerSource()) && event.getTriggerData() != null) {
resetTrafficCounter(event, orderId);
@@ -127,6 +139,80 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
event.getEventId(), orderId, event.getAreaId());
}
/**
* 记录工单创建业务日志
*/
private void recordOrderCreatedLog(CleanOrderCreateEventDTO event, Long orderId, CleanOrderAutoCreateReqDTO createReq) {
try {
// 确定事件域和类型
EventDomain domain = determineDomain(event.getTriggerSource());
String eventType = "ORDER_CREATED";
// 构建扩展信息
Map<String, Object> extra = new HashMap<>();
extra.put("eventId", event.getEventId());
extra.put("triggerSource", event.getTriggerSource());
extra.put("areaId", event.getAreaId());
extra.put("priority", createReq.getPriority());
extra.put("expectedDuration", createReq.getExpectedDuration());
if (event.getTriggerData() != null) {
extra.putAll(event.getTriggerData());
}
// 记录日志
eventLogRecorder.info("clean", domain, eventType,
buildLogMessage(event, createReq),
orderId,
event.getTriggerDeviceId(),
null);
// 添加扩展信息
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(domain)
.eventType(eventType + "_DETAIL")
.message("工单创建详细数据")
.targetId(orderId)
.targetType("order")
.deviceId(event.getTriggerDeviceId())
.level(EventLevel.INFO)
.payload(extra)
.build());
} catch (Exception e) {
log.warn("[CleanOrderCreateEventHandler] 记录业务日志失败: orderId={}", orderId, e);
}
}
/**
* 确定事件域
*/
private EventDomain determineDomain(String triggerSource) {
if ("IOT_TRAFFIC".equals(triggerSource)) {
return EventDomain.TRAFFIC;
} else if ("IOT_BEACON".equals(triggerSource)) {
return EventDomain.BEACON;
} else if ("IOT_SIGNAL_LOSS".equals(triggerSource)) {
return EventDomain.BEACON;
}
return EventDomain.SYSTEM;
}
/**
* 构建日志消息
*/
private String buildLogMessage(CleanOrderCreateEventDTO event, CleanOrderAutoCreateReqDTO createReq) {
if ("IOT_TRAFFIC".equals(event.getTriggerSource())) {
return String.format("客流阈值触发工单创建 [设备:%s, 区域:%d]",
event.getTriggerDeviceKey(), event.getAreaId());
} else if ("IOT_BEACON".equals(event.getTriggerSource())) {
return String.format("信标检测触发工单创建 [设备:%s, 区域:%d]",
event.getTriggerDeviceKey(), event.getAreaId());
}
return String.format("IoT设备触发工单创建 [设备:%s, 来源:%s]",
event.getTriggerDeviceKey(), event.getTriggerSource());
}
/**
* 重置客流计数器基准值
* <p>

View File

@@ -1,26 +1,22 @@
package com.viewsh.module.ops.environment.test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.viewsh.module.ops.api.queue.OrderQueueDTO;
import com.viewsh.module.ops.api.queue.OrderQueueService;
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusServiceImpl;
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
import com.viewsh.module.ops.environment.service.dispatch.BadgeDeviceAreaAssignStrategy;
import com.viewsh.module.ops.environment.service.dispatch.BadgeDeviceScheduleStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

View File

@@ -16,6 +16,9 @@ import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
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.infrastructure.log.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import com.viewsh.module.ops.service.fsm.OrderStateMachine;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
@@ -23,6 +26,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
@@ -66,6 +71,9 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
@Resource
private EventPublishHandler eventPublishHandler;
@Resource
private EventLogRecorder eventLogRecorder;
/**
* 责任链处理器
*/
@@ -169,6 +177,9 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
if (!result.isSuccess()) {
throw new IllegalStateException("暂停工单失败: " + result.getMessage());
}
// 记录业务日志
recordStatusChangeLog(orderId, result, "ORDER_PAUSED", "工单暂停");
}
@Override
@@ -191,6 +202,9 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
if (!result.isSuccess()) {
throw new IllegalStateException("恢复工单失败: " + result.getMessage());
}
// 记录业务日志
recordStatusChangeLog(orderId, result, "ORDER_RESUMED", "工单恢复");
}
// ==================== 打断/恢复 ====================
@@ -217,6 +231,24 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
if (!result.isSuccess()) {
throw new IllegalStateException("打断工单失败: " + result.getMessage());
}
// 记录业务日志
Map<String, Object> extra = new HashMap<>();
extra.put("urgentOrderId", urgentOrderId);
extra.put("interruptReason", "P0任务插队");
OpsOrderDO order = opsOrderMapper.selectById(orderId);
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.DISPATCH)
.eventType("ORDER_INTERRUPTED")
.message("工单被P0紧急任务打断")
.targetId(orderId)
.targetType("order")
.deviceId(order != null ? order.getAssigneeDeviceId() : null)
.personId(order != null ? order.getAssigneeId() : null)
.payload(extra)
.build());
}
@Override
@@ -252,6 +284,12 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
if (!result.isSuccess()) {
throw new IllegalStateException("完成工单失败: " + result.getMessage());
}
// 注意IoT 触发的自动完成在 CleanOrderCompleteEventHandler 中记录日志
// 这里只记录手动完成的日志operatorId 不为空的情况)
if (operatorId != null) {
recordStatusChangeLog(orderId, result, "ORDER_COMPLETED_MANUAL", "工单手动完成");
}
}
@Override
@@ -260,6 +298,9 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
log.info("开始取消工单: orderId={}, operatorId={}, operatorType={}, reason={}",
orderId, operatorId, operatorType, reason);
// 查询工单信息(日志记录用)
OpsOrderDO order = opsOrderMapper.selectById(orderId);
// 构建请求
OrderTransitionRequest request = OrderTransitionRequest.builder()
.orderId(orderId)
@@ -275,6 +316,23 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
if (!result.isSuccess()) {
throw new IllegalStateException("取消工单失败: " + result.getMessage());
}
// 记录业务日志
Map<String, Object> extra = new HashMap<>();
extra.put("cancelReason", reason);
extra.put("operatorType", operatorType != null ? operatorType.getType() : "SYSTEM");
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.SYSTEM)
.eventType("ORDER_CANCELLED")
.message("工单已取消: " + reason)
.targetId(orderId)
.targetType("order")
.deviceId(order != null ? order.getAssigneeDeviceId() : null)
.personId(order != null ? order.getAssigneeId() : null)
.payload(extra)
.build());
}
// ==================== 查询方法 ====================
@@ -337,4 +395,42 @@ public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
|| WorkOrderStatusEnum.CONFIRMED == status
|| WorkOrderStatusEnum.ARRIVED == status;
}
/**
* 记录状态变更业务日志
*
* @param orderId 工单ID
* @param result 状态转换结果
* @param eventType 事件类型
* @param message 日志消息
*/
private void recordStatusChangeLog(Long orderId, OrderTransitionResult result,
String eventType, String message) {
try {
OpsOrderDO order = opsOrderMapper.selectById(orderId);
if (order == null) {
return;
}
Map<String, Object> extra = new HashMap<>();
extra.put("oldStatus", result.getOldStatus() != null ? result.getOldStatus().getStatus() : null);
extra.put("newStatus", result.getNewStatus() != null ? result.getNewStatus().getStatus() : null);
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.DISPATCH)
.eventType(eventType)
.message(message)
.targetId(orderId)
.targetType("order")
.deviceId(order.getAssigneeDeviceId())
.personId(order.getAssigneeId())
.payload(extra)
.build());
} catch (Exception e) {
log.warn("[OrderLifecycleManager] 记录状态变更日志失败: orderId={}, eventType={}",
orderId, eventType, e);
}
}
}