Compare commits
2 Commits
release/ne
...
9db692d09c
| Author | SHA1 | Date | |
|---|---|---|---|
| 9db692d09c | |||
| 276c6c631b |
@@ -6,6 +6,9 @@ import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
|||||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.HttpStatusCodeException;
|
import org.springframework.web.client.HttpStatusCodeException;
|
||||||
import org.springframework.web.client.ResourceAccessException;
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
@@ -44,9 +47,13 @@ public class VspNotifyClientImpl implements VspNotifyClient {
|
|||||||
|
|
||||||
private void executeWithRetry(String url, Object req,
|
private void executeWithRetry(String url, Object req,
|
||||||
String action, String orderId, String alarmId) {
|
String action, String orderId, String alarmId) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
HttpEntity<Object> entity = new HttpEntity<>(req, headers);
|
||||||
|
|
||||||
for (int i = 0; i <= properties.getMaxRetry(); i++) {
|
for (int i = 0; i <= properties.getMaxRetry(); i++) {
|
||||||
try {
|
try {
|
||||||
VspResponseDTO resp = restTemplate.postForObject(url, req, VspResponseDTO.class);
|
VspResponseDTO resp = restTemplate.postForObject(url, entity, VspResponseDTO.class);
|
||||||
if (resp != null && resp.isSuccess()) {
|
if (resp != null && resp.isSuccess()) {
|
||||||
log.info("[{}] vsp通知成功, orderId={}, alarmId={}", action, orderId, alarmId);
|
log.info("[{}] vsp通知成功, orderId={}, alarmId={}", action, orderId, alarmId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ public class SecurityOrderEventListener {
|
|||||||
DispatchResult result = dispatchEngine.dispatch(context);
|
DispatchResult result = dispatchEngine.dispatch(context);
|
||||||
|
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
log.info("安保工单自动派单完成: orderId={}, assigneeId={}", event.getOrderId(), result.getAssigneeId());
|
log.info("安保工单自动派单完成: orderId={}, assigneeId={}, path={}",
|
||||||
|
event.getOrderId(), result.getAssigneeId(), result.getPath());
|
||||||
// 记录派单成功日志
|
// 记录派单成功日志
|
||||||
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED,
|
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED,
|
||||||
"自动派单成功,分配给: " + result.getAssigneeName(),
|
"自动派单成功,分配给: " + result.getAssigneeName(),
|
||||||
event.getOrderId(), result.getAssigneeId());
|
event.getOrderId(), result.getAssigneeId());
|
||||||
// 派单成功后发送企微卡片通知
|
// 企微卡片通知统一在 handleDispatched 中发送(状态变为 DISPATCHED 时触发)
|
||||||
vspNotifyListener.sendCard(event, result);
|
|
||||||
} else {
|
} else {
|
||||||
log.warn("安保工单自动派单失败: orderId={}, reason={}", event.getOrderId(), result.getMessage());
|
log.warn("安保工单自动派单失败: orderId={}, reason={}", event.getOrderId(), result.getMessage());
|
||||||
// 记录派单失败日志
|
// 记录派单失败日志
|
||||||
@@ -205,6 +205,11 @@ public class SecurityOrderEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, message, orderId, assigneeId);
|
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, message, orderId, assigneeId);
|
||||||
|
|
||||||
|
// 3. 工单推送时发送企微卡片通知(暂停恢复不重发,人员已知晓该工单)
|
||||||
|
if (event.getOldStatus() != WorkOrderStatusEnum.PAUSED) {
|
||||||
|
vspNotifyListener.sendCardByOrderId(orderId, assigneeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleConfirmed(Long orderId, OrderStateChangedEvent event) {
|
private void handleConfirmed(Long orderId, OrderStateChangedEvent event) {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.viewsh.module.ops.security.integration.listener;
|
package com.viewsh.module.ops.security.integration.listener;
|
||||||
|
|
||||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
|
||||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
|
||||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||||
@@ -9,6 +7,8 @@ import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
|||||||
import com.viewsh.module.ops.infrastructure.vsp.WechatUserIdResolver;
|
import com.viewsh.module.ops.infrastructure.vsp.WechatUserIdResolver;
|
||||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
||||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
||||||
|
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||||
|
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||||
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
||||||
@@ -47,6 +47,9 @@ public class SecurityOrderVspNotifyListener {
|
|||||||
@Resource
|
@Resource
|
||||||
private OpsBusAreaService opsBusAreaService;
|
private OpsBusAreaService opsBusAreaService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OpsOrderMapper opsOrderMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||||
|
|
||||||
@@ -67,43 +70,49 @@ public class SecurityOrderVspNotifyListener {
|
|||||||
// ==================== 核心方法 ====================
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 派单成功后发送企微工单卡片
|
* 工单推送(DISPATCHED)时发送企微工单卡片
|
||||||
* <p>
|
* <p>
|
||||||
* 由 {@link SecurityOrderEventListener#onOrderCreated} 在派单成功后调用
|
* 由 {@link SecurityOrderEventListener#onOrderStateChanged} 在工单状态变为 DISPATCHED 时调用,
|
||||||
|
* 统一覆盖直接派单和从队列推送两种场景。
|
||||||
*/
|
*/
|
||||||
public void sendCard(OrderCreatedEvent event, DispatchResult result) {
|
public void sendCardByOrderId(Long orderId, Long assigneeId) {
|
||||||
try {
|
try {
|
||||||
OpsOrderSecurityExtDO ext = securityExtMapper.selectByOpsOrderId(event.getOrderId());
|
OpsOrderSecurityExtDO ext = securityExtMapper.selectByOpsOrderId(orderId);
|
||||||
if (ext == null || ext.getAlarmId() == null) {
|
if (ext == null || ext.getAlarmId() == null) {
|
||||||
return; // 非告警来源工单不发卡片
|
return; // 非告警来源工单不发卡片
|
||||||
}
|
}
|
||||||
|
|
||||||
String wechatUserId = wechatUserIdResolver.resolve(result.getAssigneeId());
|
String wechatUserId = wechatUserIdResolver.resolve(assigneeId);
|
||||||
if (wechatUserId == null) {
|
if (wechatUserId == null) {
|
||||||
log.warn("[sendCard] 未找到企微userId, assigneeId={}, orderId={}",
|
log.warn("[sendCard] 未找到企微userId, assigneeId={}, orderId={}", assigneeId, orderId);
|
||||||
result.getAssigneeId(), event.getOrderId());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String areaName = resolveAreaName(event.getAreaId());
|
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||||
|
if (order == null) {
|
||||||
|
log.warn("[sendCard] 工单不存在, orderId={}", orderId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String areaName = resolveAreaName(order.getAreaId());
|
||||||
|
|
||||||
VspSendCardReqDTO req = VspSendCardReqDTO.builder()
|
VspSendCardReqDTO req = VspSendCardReqDTO.builder()
|
||||||
.alarmId(ext.getAlarmId())
|
.alarmId(ext.getAlarmId())
|
||||||
.orderId(String.valueOf(event.getOrderId()))
|
.orderId(String.valueOf(orderId))
|
||||||
.userIds(List.of(wechatUserId))
|
.userIds(List.of(wechatUserId))
|
||||||
.title(event.getTitle())
|
.title(order.getTitle())
|
||||||
.areaName(areaName)
|
.areaName(areaName)
|
||||||
.cameraName(ext.getCameraName())
|
.cameraName(ext.getCameraName())
|
||||||
.eventTime(event.getCreateTime() != null
|
.eventTime(order.getCreateTime() != null
|
||||||
? event.getCreateTime().format(EVENT_TIME_FMT)
|
? order.getCreateTime().format(EVENT_TIME_FMT)
|
||||||
: null)
|
: null)
|
||||||
.level(event.getPriority())
|
.level(order.getPriority())
|
||||||
.snapshotUrl(ext.getImageUrl())
|
.snapshotUrl(ext.getImageUrl())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
vspNotifyClient.sendCard(req);
|
vspNotifyClient.sendCard(req);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[sendCard] 发送企微卡片异常, orderId={}", event.getOrderId(), e);
|
log.error("[sendCard] 发送企微卡片异常, orderId={}", orderId, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
|||||||
import com.viewsh.module.ops.core.event.OrderCompletedEvent;
|
import com.viewsh.module.ops.core.event.OrderCompletedEvent;
|
||||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||||
|
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||||
@@ -34,9 +35,15 @@ public class SecurityOrderEventListenerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private OpsOrderMapper opsOrderMapper;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private DispatchEngine dispatchEngine;
|
private DispatchEngine dispatchEngine;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SecurityOrderVspNotifyListener vspNotifyListener;
|
||||||
|
|
||||||
private static final Long TEST_ORDER_ID = 10001L;
|
private static final Long TEST_ORDER_ID = 10001L;
|
||||||
private static final String TEST_ORDER_CODE = "SECURITY-20260310-0001";
|
private static final String TEST_ORDER_CODE = "SECURITY-20260310-0001";
|
||||||
|
|
||||||
@@ -141,9 +148,6 @@ public class SecurityOrderEventListenerTest {
|
|||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
||||||
|
|
||||||
OpsOrderSecurityExtDO existingExt = OpsOrderSecurityExtDO.builder()
|
|
||||||
.id(1L).opsOrderId(TEST_ORDER_ID).build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(existingExt);
|
|
||||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||||
|
|
||||||
// 执行
|
// 执行
|
||||||
@@ -157,20 +161,6 @@ public class SecurityOrderEventListenerTest {
|
|||||||
assertNotNull(ext.getCompletedTime());
|
assertNotNull(ext.getCompletedTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testOnOrderStateChanged_Completed_NoExt_Skipped() {
|
|
||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
|
||||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
|
||||||
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(null);
|
|
||||||
|
|
||||||
// 执行
|
|
||||||
listener.onOrderStateChanged(event);
|
|
||||||
|
|
||||||
// 验证:扩展记录不存在时不写入
|
|
||||||
verify(securityExtMapper, never()).insertOrUpdateSelective(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnOrderStateChanged_CleanType_Ignored() {
|
void testOnOrderStateChanged_CleanType_Ignored() {
|
||||||
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
|
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
package com.viewsh.module.ops.security.integration.listener;
|
package com.viewsh.module.ops.security.integration.listener;
|
||||||
|
|
||||||
import com.viewsh.framework.common.pojo.CommonResult;
|
|
||||||
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
||||||
|
import com.viewsh.module.ops.core.dispatch.model.DispatchPath;
|
||||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
||||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
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.PriorityEnum;
|
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
||||||
|
import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
||||||
|
import com.viewsh.module.ops.infrastructure.vsp.WechatUserIdResolver;
|
||||||
|
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
||||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||||
import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
|
||||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
|
||||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
|
||||||
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
||||||
import com.viewsh.module.ops.dal.dataobject.vo.area.OpsBusAreaRespVO;
|
import com.viewsh.module.ops.dal.dataobject.vo.area.OpsBusAreaRespVO;
|
||||||
import com.viewsh.module.system.api.social.SocialUserApi;
|
import com.viewsh.module.system.api.social.SocialUserApi;
|
||||||
import com.viewsh.module.system.api.social.dto.SocialUserRespDTO;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@@ -31,21 +33,23 @@ import static org.mockito.ArgumentMatchers.*;
|
|||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安保工单事件监听器 - VSP 企微通知集成测试
|
* 安保工单事件监听器 - VSP 企微通知时机测试
|
||||||
|
* <p>
|
||||||
|
* 验证 sendCard 在工单状态变为 DISPATCHED 时发送(而非创建时)
|
||||||
*
|
*
|
||||||
* @author lzh
|
* @author lzh
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class SecurityOrderEventListenerVspTest {
|
public class SecurityOrderEventListenerVspTest {
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private SecurityOrderEventListener listener;
|
private SecurityOrderEventListener listener;
|
||||||
|
private SecurityOrderVspNotifyListener vspNotifyListener;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private VspNotifyClient vspNotifyClient;
|
private VspNotifyClient vspNotifyClient;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SocialUserApi socialUserApi;
|
private WechatUserIdResolver wechatUserIdResolver;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private OpsBusAreaService opsBusAreaService;
|
private OpsBusAreaService opsBusAreaService;
|
||||||
@@ -53,6 +57,9 @@ public class SecurityOrderEventListenerVspTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private OpsOrderMapper opsOrderMapper;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private DispatchEngine dispatchEngine;
|
private DispatchEngine dispatchEngine;
|
||||||
|
|
||||||
@@ -65,11 +72,28 @@ public class SecurityOrderEventListenerVspTest {
|
|||||||
private static final String TEST_WECHAT_ID = "test_wechat_id";
|
private static final String TEST_WECHAT_ID = "test_wechat_id";
|
||||||
private static final String TEST_ALARM_ID = "alarm-001";
|
private static final String TEST_ALARM_ID = "alarm-001";
|
||||||
|
|
||||||
// ==================== onOrderCreated VSP 通知测试 ====================
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// 构建真实 vspNotifyListener 并注入 mock 依赖
|
||||||
|
vspNotifyListener = new SecurityOrderVspNotifyListener();
|
||||||
|
ReflectionTestUtils.setField(vspNotifyListener, "vspNotifyClient", vspNotifyClient);
|
||||||
|
ReflectionTestUtils.setField(vspNotifyListener, "wechatUserIdResolver", wechatUserIdResolver);
|
||||||
|
ReflectionTestUtils.setField(vspNotifyListener, "opsBusAreaService", opsBusAreaService);
|
||||||
|
ReflectionTestUtils.setField(vspNotifyListener, "opsOrderMapper", opsOrderMapper);
|
||||||
|
ReflectionTestUtils.setField(vspNotifyListener, "securityExtMapper", securityExtMapper);
|
||||||
|
|
||||||
|
// 构建真实 listener 并注入依赖
|
||||||
|
listener = new SecurityOrderEventListener();
|
||||||
|
ReflectionTestUtils.setField(listener, "securityExtMapper", securityExtMapper);
|
||||||
|
ReflectionTestUtils.setField(listener, "dispatchEngine", dispatchEngine);
|
||||||
|
ReflectionTestUtils.setField(listener, "eventLogRecorder", eventLogRecorder);
|
||||||
|
ReflectionTestUtils.setField(listener, "vspNotifyListener", vspNotifyListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== onOrderCreated 不再发送卡片 ====================
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void onOrderCreated_shouldSendCard_whenDispatchSuccessAndAlarmSource() {
|
void onOrderCreated_shouldNotSendCard_whenEnqueueOnly() {
|
||||||
// 准备事件
|
|
||||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||||
.orderId(TEST_ORDER_ID)
|
.orderId(TEST_ORDER_ID)
|
||||||
.orderType("SECURITY")
|
.orderType("SECURITY")
|
||||||
@@ -77,36 +101,77 @@ public class SecurityOrderEventListenerVspTest {
|
|||||||
.title("入侵告警")
|
.title("入侵告警")
|
||||||
.areaId(100L)
|
.areaId(100L)
|
||||||
.priority(PriorityEnum.P1.getPriority())
|
.priority(PriorityEnum.P1.getPriority())
|
||||||
.createTime(LocalDateTime.of(2026, 3, 24, 10, 0))
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 派单成功
|
DispatchResult result = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三",
|
||||||
DispatchResult dispatchResult = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三", null, null);
|
DispatchPath.ENQUEUE_ONLY, 1L);
|
||||||
when(dispatchEngine.dispatch(any())).thenReturn(dispatchResult);
|
when(dispatchEngine.dispatch(any())).thenReturn(result);
|
||||||
|
|
||||||
// 告警来源工单扩展记录
|
listener.onOrderCreated(event);
|
||||||
|
|
||||||
|
// onOrderCreated 不发卡片
|
||||||
|
verify(vspNotifyClient, never()).sendCard(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onOrderCreated_shouldNotSendCard_whenDirectDispatch() {
|
||||||
|
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||||
|
.orderId(TEST_ORDER_ID)
|
||||||
|
.orderType("SECURITY")
|
||||||
|
.orderCode(TEST_ORDER_CODE)
|
||||||
|
.title("入侵告警")
|
||||||
|
.areaId(100L)
|
||||||
|
.priority(PriorityEnum.P1.getPriority())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DispatchResult result = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三",
|
||||||
|
DispatchPath.DIRECT_DISPATCH, null);
|
||||||
|
when(dispatchEngine.dispatch(any())).thenReturn(result);
|
||||||
|
|
||||||
|
listener.onOrderCreated(event);
|
||||||
|
|
||||||
|
// 即使直接派单,onOrderCreated 也不发卡片(由 handleDispatched 处理)
|
||||||
|
verify(vspNotifyClient, never()).sendCard(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== handleDispatched 发送卡片(端到端验证) ====================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onOrderStateChanged_shouldSendCard_whenDispatched() {
|
||||||
|
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||||
|
WorkOrderStatusEnum.QUEUED, WorkOrderStatusEnum.DISPATCHED);
|
||||||
|
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||||
|
event.addPayload("assigneeName", "张三");
|
||||||
|
event.addPayload("assigneePhone", "13800138000");
|
||||||
|
|
||||||
|
// mock:告警来源工单
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||||
.id(1L)
|
.id(1L).opsOrderId(TEST_ORDER_ID)
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
.alarmId(TEST_ALARM_ID).cameraName("大堂摄像头")
|
||||||
.alarmId(TEST_ALARM_ID)
|
|
||||||
.cameraName("大堂摄像头")
|
|
||||||
.imageUrl("http://example.com/snapshot.jpg")
|
.imageUrl("http://example.com/snapshot.jpg")
|
||||||
.build();
|
.build();
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||||
|
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||||
|
|
||||||
// 企微 userId 查询
|
// mock:企微 userId
|
||||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
when(wechatUserIdResolver.resolve(TEST_ASSIGNEE_ID)).thenReturn(TEST_WECHAT_ID);
|
||||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
|
||||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
|
||||||
.thenReturn(CommonResult.success(socialUser));
|
|
||||||
|
|
||||||
// 区域名称
|
// mock:工单主表
|
||||||
|
OpsOrderDO order = new OpsOrderDO();
|
||||||
|
order.setId(TEST_ORDER_ID);
|
||||||
|
order.setTitle("入侵告警");
|
||||||
|
order.setAreaId(100L);
|
||||||
|
order.setPriority(PriorityEnum.P1.getPriority());
|
||||||
|
order.setCreateTime(LocalDateTime.of(2026, 3, 24, 10, 0));
|
||||||
|
when(opsOrderMapper.selectById(TEST_ORDER_ID)).thenReturn(order);
|
||||||
|
|
||||||
|
// mock:区域名称
|
||||||
OpsBusAreaRespVO area = new OpsBusAreaRespVO();
|
OpsBusAreaRespVO area = new OpsBusAreaRespVO();
|
||||||
area.setAreaName("A栋大堂");
|
area.setAreaName("A栋大堂");
|
||||||
when(opsBusAreaService.getArea(100L)).thenReturn(area);
|
when(opsBusAreaService.getArea(100L)).thenReturn(area);
|
||||||
|
|
||||||
// 执行
|
// 执行
|
||||||
listener.onOrderCreated(event);
|
listener.onOrderStateChanged(event);
|
||||||
|
|
||||||
// 验证发送了企微卡片
|
// 验证发送了企微卡片
|
||||||
ArgumentCaptor<VspSendCardReqDTO> captor = ArgumentCaptor.forClass(VspSendCardReqDTO.class);
|
ArgumentCaptor<VspSendCardReqDTO> captor = ArgumentCaptor.forClass(VspSendCardReqDTO.class);
|
||||||
@@ -121,153 +186,49 @@ public class SecurityOrderEventListenerVspTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void onOrderCreated_shouldNotSendCard_whenNoAlarmId() {
|
void onOrderStateChanged_shouldNotSendCard_whenNoAlarmId() {
|
||||||
// 准备事件
|
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
WorkOrderStatusEnum.QUEUED, WorkOrderStatusEnum.DISPATCHED);
|
||||||
.orderId(TEST_ORDER_ID)
|
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||||
.orderType("SECURITY")
|
event.addPayload("assigneeName", "张三");
|
||||||
.orderCode(TEST_ORDER_CODE)
|
|
||||||
.title("手动巡查工单")
|
|
||||||
.areaId(100L)
|
|
||||||
.priority(PriorityEnum.P2.getPriority())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// 派单成功
|
// 非告警工单
|
||||||
DispatchResult dispatchResult = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三", null, null);
|
|
||||||
when(dispatchEngine.dispatch(any())).thenReturn(dispatchResult);
|
|
||||||
|
|
||||||
// 手动工单无告警ID
|
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||||
.id(1L)
|
.id(1L).opsOrderId(TEST_ORDER_ID).alarmId(null).build();
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
|
||||||
.alarmId(null) // 无告警ID
|
|
||||||
.build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||||
|
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||||
|
|
||||||
// 执行
|
listener.onOrderStateChanged(event);
|
||||||
listener.onOrderCreated(event);
|
|
||||||
|
|
||||||
// 验证不发送卡片
|
|
||||||
verify(vspNotifyClient, never()).sendCard(any());
|
verify(vspNotifyClient, never()).sendCard(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== onOrderStateChanged VSP 同步测试 ====================
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void onOrderStateChanged_shouldSyncConfirmed() {
|
void onOrderStateChanged_shouldNotSendCard_whenConfirmed() {
|
||||||
|
// CONFIRMED 状态不发卡片
|
||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||||
WorkOrderStatusEnum.DISPATCHED, WorkOrderStatusEnum.CONFIRMED);
|
WorkOrderStatusEnum.DISPATCHED, WorkOrderStatusEnum.CONFIRMED);
|
||||||
|
|
||||||
// 告警来源工单
|
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
|
||||||
.id(1L)
|
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
|
||||||
.alarmId(TEST_ALARM_ID)
|
|
||||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
|
||||||
.build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
|
||||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
|
||||||
|
|
||||||
// 企微 userId 查询
|
|
||||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
|
||||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
|
||||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
|
||||||
.thenReturn(CommonResult.success(socialUser));
|
|
||||||
|
|
||||||
// 执行
|
|
||||||
listener.onOrderStateChanged(event);
|
listener.onOrderStateChanged(event);
|
||||||
|
|
||||||
// 验证同步状态
|
verify(vspNotifyClient, never()).sendCard(any());
|
||||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
|
||||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
|
||||||
VspSyncStatusReqDTO req = captor.getValue();
|
|
||||||
assertEquals(TEST_ALARM_ID, req.getAlarmId());
|
|
||||||
assertEquals(String.valueOf(TEST_ORDER_ID), req.getOrderId());
|
|
||||||
assertEquals("confirmed", req.getStatus());
|
|
||||||
assertEquals(TEST_WECHAT_ID, req.getOperator());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void onOrderStateChanged_shouldSyncCompleted() {
|
void onOrderStateChanged_shouldNotSendCard_whenResumedFromPause() {
|
||||||
|
// 暂停恢复不重发卡片,人员已知晓该工单
|
||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
WorkOrderStatusEnum.PAUSED, WorkOrderStatusEnum.DISPATCHED);
|
||||||
|
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||||
|
event.addPayload("assigneeName", "张三");
|
||||||
|
|
||||||
// 告警来源工单,非误报
|
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
|
||||||
.id(1L)
|
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
|
||||||
.alarmId(TEST_ALARM_ID)
|
|
||||||
.falseAlarm(false)
|
|
||||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
|
||||||
.build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
|
||||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
|
||||||
|
|
||||||
// 企微 userId 查询
|
|
||||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
|
||||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
|
||||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
|
||||||
.thenReturn(CommonResult.success(socialUser));
|
|
||||||
|
|
||||||
// 执行
|
|
||||||
listener.onOrderStateChanged(event);
|
listener.onOrderStateChanged(event);
|
||||||
|
|
||||||
// 验证同步状态为 completed
|
verify(vspNotifyClient, never()).sendCard(any());
|
||||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
|
||||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
|
||||||
assertEquals("completed", captor.getValue().getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void onOrderStateChanged_shouldSyncFalseAlarm() {
|
|
||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
|
||||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
|
||||||
|
|
||||||
// 告警来源工单,标记为误报
|
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
|
||||||
.id(1L)
|
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
|
||||||
.alarmId(TEST_ALARM_ID)
|
|
||||||
.falseAlarm(true)
|
|
||||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
|
||||||
.build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
|
||||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
|
||||||
|
|
||||||
// 企微 userId 查询
|
|
||||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
|
||||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
|
||||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
|
||||||
.thenReturn(CommonResult.success(socialUser));
|
|
||||||
|
|
||||||
// 执行
|
|
||||||
listener.onOrderStateChanged(event);
|
|
||||||
|
|
||||||
// 验证同步状态为 false_alarm
|
|
||||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
|
||||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
|
||||||
assertEquals("false_alarm", captor.getValue().getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void onOrderStateChanged_shouldNotSync_whenNoAlarmId() {
|
|
||||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
|
||||||
WorkOrderStatusEnum.DISPATCHED, WorkOrderStatusEnum.CONFIRMED);
|
|
||||||
|
|
||||||
// 手动工单,无告警ID
|
|
||||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
|
||||||
.id(1L)
|
|
||||||
.opsOrderId(TEST_ORDER_ID)
|
|
||||||
.alarmId(null)
|
|
||||||
.build();
|
|
||||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
|
||||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
|
||||||
|
|
||||||
// 执行
|
|
||||||
listener.onOrderStateChanged(event);
|
|
||||||
|
|
||||||
// 验证不同步状态
|
|
||||||
verify(vspNotifyClient, never()).syncStatus(any());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 辅助方法 ====================
|
// ==================== 辅助方法 ====================
|
||||||
|
|||||||
Reference in New Issue
Block a user