Merge branch 'master' of http://124.222.218.198:3000/XW-AIOT/aiot-platform-cloud into feature/cleaning-inspection
This commit is contained in:
@@ -93,22 +93,21 @@ public class OrderStateMachine {
|
||||
WorkOrderStatusEnum.CANCELLED, Collections.emptySet()
|
||||
);
|
||||
|
||||
/** 终态集合,不允许再转换 */
|
||||
private static final Set<WorkOrderStatusEnum> TERMINAL_STATES = Set.of(
|
||||
WorkOrderStatusEnum.COMPLETED, WorkOrderStatusEnum.CANCELLED
|
||||
);
|
||||
|
||||
/**
|
||||
* 执行状态转换(核心方法)
|
||||
* <p>
|
||||
* 此方法负责:
|
||||
* 1. 验证状态转换合法性
|
||||
* 2. 更新工单状态和相关字段
|
||||
* 3. 记录事件流
|
||||
* 4. 发布状态变更事件
|
||||
* 执行状态转换(校验转换规则)
|
||||
*
|
||||
* @param order 工单对象
|
||||
* @param newStatus 目标状态
|
||||
* @param order 工单对象
|
||||
* @param newStatus 目标状态
|
||||
* @param operatorType 操作人类型
|
||||
* @param operatorId 操作人ID
|
||||
* @param remark 说明
|
||||
* @param operatorId 操作人ID
|
||||
* @param remark 说明
|
||||
* @throws IllegalArgumentException 参数不合法
|
||||
* @throws IllegalStateException 状态转换不合法
|
||||
* @throws IllegalStateException 状态转换不合法
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void transition(OpsOrderDO order,
|
||||
@@ -116,7 +115,66 @@ public class OrderStateMachine {
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark) {
|
||||
doTransition(order, newStatus, operatorType, operatorId, remark, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制状态跳转(不受转换规则限制)
|
||||
* <p>
|
||||
* 适用于系统自动结单等场景:告警自动解除时工单可能处于任何非终态,
|
||||
* 需要直接跳转到 COMPLETED / CANCELLED,不经过中间状态。
|
||||
* <p>
|
||||
* 与 {@link #transition} 的区别:跳过转换规则校验,
|
||||
* 其余逻辑(字段更新、事件记录)完全一致。
|
||||
*
|
||||
* @param order 工单对象
|
||||
* @param newStatus 目标状态(通常为终态 COMPLETED / CANCELLED)
|
||||
* @param operatorType 操作人类型
|
||||
* @param operatorId 操作人ID
|
||||
* @param remark 说明
|
||||
* @throws IllegalArgumentException 参数不合法
|
||||
* @throws IllegalStateException 当前已是终态
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void forceTransition(OpsOrderDO order,
|
||||
WorkOrderStatusEnum newStatus,
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark) {
|
||||
doTransition(order, newStatus, operatorType, operatorId, remark, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态转换是否合法(不执行转换)
|
||||
*/
|
||||
public boolean canTransition(WorkOrderStatusEnum fromStatus, WorkOrderStatusEnum toStatus) {
|
||||
if (fromStatus == null || toStatus == null) {
|
||||
return false;
|
||||
}
|
||||
Set<WorkOrderStatusEnum> allowedTargets = TRANSITIONS.get(fromStatus);
|
||||
return allowedTargets != null && allowedTargets.contains(toStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态允许转换到的目标状态集合
|
||||
*/
|
||||
public Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum currentStatus) {
|
||||
return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet());
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 状态转换核心逻辑(transition / forceTransition 的统一实现)
|
||||
*
|
||||
* @param validate true=校验转换规则(transition),false=仅校验终态(forceTransition)
|
||||
*/
|
||||
private void doTransition(OpsOrderDO order,
|
||||
WorkOrderStatusEnum newStatus,
|
||||
OperatorTypeEnum operatorType,
|
||||
Long operatorId,
|
||||
String remark,
|
||||
boolean validate) {
|
||||
// 1. 参数校验
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("工单对象不能为空");
|
||||
@@ -128,15 +186,19 @@ public class OrderStateMachine {
|
||||
// 2. 获取当前状态
|
||||
WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
|
||||
// 3. 如果目标状态与当前状态相同,直接返回(避免不必要的操作)
|
||||
// 3. 相同状态,跳过
|
||||
if (currentStatus == newStatus) {
|
||||
log.debug("工单状态未变化,跳过状态转换: orderId={}, status={}",
|
||||
order.getId(), currentStatus);
|
||||
log.debug("工单状态未变化,跳过: orderId={}, status={}", order.getId(), currentStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 校验状态转换合法性
|
||||
validateTransition(currentStatus, newStatus);
|
||||
// 4. 校验
|
||||
if (validate) {
|
||||
validateTransition(currentStatus, newStatus);
|
||||
} else if (TERMINAL_STATES.contains(currentStatus)) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"工单已处于终态 %s,无法转换到 %s", currentStatus.name(), newStatus.name()));
|
||||
}
|
||||
|
||||
// 5. 更新工单状态和相关字段
|
||||
WorkOrderStatusEnum oldStatus = currentStatus;
|
||||
@@ -155,37 +217,10 @@ public class OrderStateMachine {
|
||||
remark
|
||||
);
|
||||
|
||||
log.info("工单状态转换成功: orderId={}, {} -> {}, operatorType={}, operatorId={}",
|
||||
order.getId(), oldStatus, newStatus, operatorType, operatorId);
|
||||
log.info("工单状态转换: orderId={}, {} -> {}, forced={}, operatorType={}, operatorId={}",
|
||||
order.getId(), oldStatus, newStatus, !validate, operatorType, operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查状态转换是否合法(不执行转换)
|
||||
*
|
||||
* @param fromStatus 当前状态
|
||||
* @param toStatus 目标状态
|
||||
* @return 是否可以转换
|
||||
*/
|
||||
public boolean canTransition(WorkOrderStatusEnum fromStatus, WorkOrderStatusEnum toStatus) {
|
||||
if (fromStatus == null || toStatus == null) {
|
||||
return false;
|
||||
}
|
||||
Set<WorkOrderStatusEnum> allowedTargets = TRANSITIONS.get(fromStatus);
|
||||
return allowedTargets != null && allowedTargets.contains(toStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态允许转换到的目标状态集合
|
||||
*
|
||||
* @param currentStatus 当前状态
|
||||
* @return 允许的目标状态集合
|
||||
*/
|
||||
public Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum currentStatus) {
|
||||
return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet());
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 校验状态转换是否合法
|
||||
*/
|
||||
|
||||
@@ -94,7 +94,7 @@ public class SecurityOrderController {
|
||||
@Operation(summary = "误报标记", description = "将安保工单标记为误报并完成")
|
||||
@PreAuthorize("@ss.hasPermission('ops:security-order:complete')")
|
||||
public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) {
|
||||
securityOrderService.falseAlarmOrder(reqVO.getOrderId(), SecurityFrameworkUtils.getLoginUserId());
|
||||
securityOrderService.falseAlarmOrder(reqVO.getOrderId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ public class SecurityOrderOpenController {
|
||||
@ApiSignature
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) {
|
||||
securityOrderService.falseAlarmOrder(reqVO.getOrderId(), null);
|
||||
securityOrderService.falseAlarmOrder(reqVO.getOrderId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,14 @@ public interface SecurityOrderService {
|
||||
void confirmOrder(Long orderId, Long userId);
|
||||
|
||||
/**
|
||||
* 自动完单(对方系统调用,无需提交结果)
|
||||
* 自动完单(由边缘系统调用,告警自动解除)
|
||||
* <p>
|
||||
* 根据当前工单状态:
|
||||
* <ul>
|
||||
* <li>PENDING(未派单)→ 直接取消</li>
|
||||
* <li>DISPATCHED / CONFIRMED / ARRIVED / PAUSED → 强制完成</li>
|
||||
* <li>已终态 → 幂等跳过</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param remark 备注
|
||||
@@ -45,12 +52,11 @@ public interface SecurityOrderService {
|
||||
void manualCompleteOrder(SecurityOrderCompleteReqDTO req);
|
||||
|
||||
/**
|
||||
* 误报标记(将工单标记为误报并完成)
|
||||
* 误报标记(复用自动完单逻辑,额外标记误报)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param operatorId 操作人ID(可为 null,为 null 时取已分配人员)
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
void falseAlarmOrder(Long orderId, Long operatorId);
|
||||
void falseAlarmOrder(Long orderId);
|
||||
|
||||
/**
|
||||
* 根据工单ID查询安保扩展信息
|
||||
|
||||
@@ -163,12 +163,42 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
|
||||
OpsOrderDO order = getOrderOrThrow(orderId);
|
||||
validateOrderType(order);
|
||||
|
||||
// 状态转换 → COMPLETED(扩展表时间 + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置)
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SYSTEM, null,
|
||||
StrUtil.isNotBlank(remark) ? remark : "系统自动完单");
|
||||
String effectiveRemark = StrUtil.isNotBlank(remark) ? remark : "系统自动完单";
|
||||
WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
|
||||
log.info("安保工单自动完单: orderId={}", orderId);
|
||||
// 1. 已经是终态,幂等跳过
|
||||
if (currentStatus == WorkOrderStatusEnum.COMPLETED || currentStatus == WorkOrderStatusEnum.CANCELLED) {
|
||||
log.info("安保工单已处于终态,跳过自动完单: orderId={}, status={}", orderId, currentStatus);
|
||||
return;
|
||||
}
|
||||
// 2. PENDING:未派单,直接取消(PENDING → CANCELLED 规则已支持)
|
||||
if (currentStatus == WorkOrderStatusEnum.PENDING) {
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.CANCELLED,
|
||||
OperatorTypeEnum.SYSTEM, null, effectiveRemark + "(工单未派单)");
|
||||
}
|
||||
// 3. 其他非终态(DISPATCHED / CONFIRMED / ARRIVED / PAUSED)→ 强制跳转 COMPLETED
|
||||
else {
|
||||
orderStateMachine.forceTransition(order, WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SYSTEM, null, effectiveRemark);
|
||||
}
|
||||
|
||||
log.info("安保工单自动结单: orderId={}, 原始状态={}", orderId, currentStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void falseAlarmOrder(Long orderId) {
|
||||
// 复用自动完单逻辑
|
||||
autoCompleteOrder(orderId, "误报");
|
||||
|
||||
// 更新扩展表:标记误报
|
||||
OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO();
|
||||
extUpdate.setOpsOrderId(orderId);
|
||||
extUpdate.setFalseAlarm(true);
|
||||
extUpdate.setResult("误报");
|
||||
securityExtMapper.insertOrUpdateSelective(extUpdate);
|
||||
|
||||
log.info("安保工单误报标记: orderId={}", orderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -196,29 +226,6 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
|
||||
log.info("安保工单人工完单: orderId={}", req.getOrderId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void falseAlarmOrder(Long orderId, Long operatorId) {
|
||||
OpsOrderDO order = getOrderOrThrow(orderId);
|
||||
validateOrderType(order);
|
||||
|
||||
// 如果 operatorId 为 null(open-api 调用),取已分配人员
|
||||
Long effectiveOperatorId = resolveOperatorId(orderId, operatorId);
|
||||
|
||||
// 状态转换 → COMPLETED
|
||||
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SECURITY_GUARD, effectiveOperatorId, "误报标记");
|
||||
|
||||
// 更新扩展表:标记误报 + 结果
|
||||
OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO();
|
||||
extUpdate.setOpsOrderId(orderId);
|
||||
extUpdate.setFalseAlarm(true);
|
||||
extUpdate.setResult("误报");
|
||||
securityExtMapper.insertOrUpdateSelective(extUpdate);
|
||||
|
||||
log.info("安保工单误报标记: orderId={}", orderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) {
|
||||
return securityExtMapper.selectByOpsOrderId(opsOrderId);
|
||||
|
||||
@@ -251,8 +251,8 @@ public class SecurityOrderServiceTest {
|
||||
// 执行
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除");
|
||||
|
||||
// 验证状态机调用
|
||||
verify(orderStateMachine).transition(
|
||||
// 验证状态机调用(ARRIVED 非终态,走 forceTransition)
|
||||
verify(orderStateMachine).forceTransition(
|
||||
eq(order),
|
||||
eq(WorkOrderStatusEnum.COMPLETED),
|
||||
eq(OperatorTypeEnum.SYSTEM),
|
||||
@@ -260,14 +260,7 @@ public class SecurityOrderServiceTest {
|
||||
eq("告警自动解除")
|
||||
);
|
||||
|
||||
// 验证主表 endTime 更新
|
||||
ArgumentCaptor<OpsOrderDO> updateCaptor = ArgumentCaptor.forClass(OpsOrderDO.class);
|
||||
verify(opsOrderMapper).updateById(updateCaptor.capture());
|
||||
OpsOrderDO updated = updateCaptor.getValue();
|
||||
assertEquals(TEST_ORDER_ID, updated.getId());
|
||||
assertNotNull(updated.getEndTime());
|
||||
|
||||
// 验证不再直接写扩展表时间
|
||||
// 验证不直接写扩展表
|
||||
verify(securityExtMapper, never()).insertOrUpdateSelective(any());
|
||||
}
|
||||
|
||||
@@ -281,7 +274,7 @@ public class SecurityOrderServiceTest {
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, null);
|
||||
|
||||
// 验证使用默认备注
|
||||
verify(orderStateMachine).transition(
|
||||
verify(orderStateMachine).forceTransition(
|
||||
any(), eq(WorkOrderStatusEnum.COMPLETED),
|
||||
eq(OperatorTypeEnum.SYSTEM), isNull(),
|
||||
eq("系统自动完单")
|
||||
@@ -298,7 +291,7 @@ public class SecurityOrderServiceTest {
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, " ");
|
||||
|
||||
// 验证使用默认备注
|
||||
verify(orderStateMachine).transition(
|
||||
verify(orderStateMachine).forceTransition(
|
||||
any(), eq(WorkOrderStatusEnum.COMPLETED),
|
||||
eq(OperatorTypeEnum.SYSTEM), isNull(),
|
||||
eq("系统自动完单")
|
||||
@@ -311,6 +304,87 @@ public class SecurityOrderServiceTest {
|
||||
() -> securityOrderService.autoCompleteOrder(999L, "test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoCompleteOrder_Pending_ShouldCancel() {
|
||||
// 准备:PENDING 状态工单
|
||||
OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.PENDING);
|
||||
orderDB.put(TEST_ORDER_ID, order);
|
||||
|
||||
// 执行
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除");
|
||||
|
||||
// 验证:走正常 transition → CANCELLED
|
||||
verify(orderStateMachine).transition(
|
||||
eq(order),
|
||||
eq(WorkOrderStatusEnum.CANCELLED),
|
||||
eq(OperatorTypeEnum.SYSTEM),
|
||||
isNull(),
|
||||
eq("告警自动解除(工单未派单)")
|
||||
);
|
||||
// 不应调用 forceTransition
|
||||
verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoCompleteOrder_Dispatched_ShouldForceComplete() {
|
||||
// 准备:DISPATCHED 状态工单
|
||||
OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED);
|
||||
orderDB.put(TEST_ORDER_ID, order);
|
||||
|
||||
// 执行
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除");
|
||||
|
||||
// 验证:走 forceTransition → COMPLETED
|
||||
verify(orderStateMachine).forceTransition(
|
||||
eq(order),
|
||||
eq(WorkOrderStatusEnum.COMPLETED),
|
||||
eq(OperatorTypeEnum.SYSTEM),
|
||||
isNull(),
|
||||
eq("告警自动解除")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoCompleteOrder_AlreadyCompleted_Idempotent() {
|
||||
// 准备:已完成工单
|
||||
OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.COMPLETED);
|
||||
orderDB.put(TEST_ORDER_ID, order);
|
||||
|
||||
// 执行:不应抛异常
|
||||
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除");
|
||||
|
||||
// 验证:状态机未被调用
|
||||
verify(orderStateMachine, never()).transition(any(), any(), any(), any(), any());
|
||||
verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFalseAlarmOrder_DelegatesToAutoCompleteAndMarksExt() {
|
||||
// 准备:DISPATCHED 状态工单
|
||||
OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED);
|
||||
orderDB.put(TEST_ORDER_ID, order);
|
||||
|
||||
// 执行
|
||||
securityOrderService.falseAlarmOrder(TEST_ORDER_ID);
|
||||
|
||||
// 验证:forceTransition 被调用(通过 autoCompleteOrder 委托)
|
||||
verify(orderStateMachine).forceTransition(
|
||||
eq(order),
|
||||
eq(WorkOrderStatusEnum.COMPLETED),
|
||||
eq(OperatorTypeEnum.SYSTEM),
|
||||
isNull(),
|
||||
eq("误报")
|
||||
);
|
||||
|
||||
// 验证:扩展表写入误报标记
|
||||
ArgumentCaptor<OpsOrderSecurityExtDO> extCaptor = ArgumentCaptor.forClass(OpsOrderSecurityExtDO.class);
|
||||
verify(securityExtMapper).insertOrUpdateSelective(extCaptor.capture());
|
||||
OpsOrderSecurityExtDO extUpdate = extCaptor.getValue();
|
||||
assertEquals(TEST_ORDER_ID, extUpdate.getOpsOrderId());
|
||||
assertTrue(extUpdate.getFalseAlarm());
|
||||
assertEquals("误报", extUpdate.getResult());
|
||||
}
|
||||
|
||||
// ==================== 人工完单测试 ====================
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user