fix(ops): 调度状态补偿 QUEUED→终态跳过 DISPATCHED 场景
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

UserDispatchStatusEventListener:
- assigneeId 兜底从工单主表获取(forceComplete 等 payload 缺失场景)
- QUEUED→COMPLETED/CANCELLED 补偿 decrementWaitingTaskCount

UserDispatchStatusServiceImpl:
- 新增 LUA_DECREMENT_WAITING 脚本,安全递减 waitingTaskCount(不低于 0)

OpsOrderEventService:新增 8 参数 recordEvent 重载(含 operatorName)
DispatchEngineServiceAdapter:reason 文案统一为"手动派单"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-27 16:09:30 +08:00
parent 06d701ed5e
commit 78396cf35a
5 changed files with 103 additions and 2 deletions

View File

@@ -125,7 +125,7 @@ public class DispatchEngineServiceAdapter implements DispatchEngineService {
.queueId(queueId)
.operatorType(OperatorTypeEnum.ADMIN)
.operatorId(assigneeId)
.reason("管理员手动派单")
.reason("手动派单")
.build()
);

View File

@@ -1,6 +1,8 @@
package com.viewsh.module.ops.service.dispatch;
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.WorkOrderStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -28,6 +30,9 @@ public class UserDispatchStatusEventListener {
@Resource
private UserDispatchStatusService userDispatchStatusService;
@Resource
private OpsOrderMapper opsOrderMapper;
/**
* 监听工单状态变更,同步更新人员调度状态
*/
@@ -39,6 +44,13 @@ public class UserDispatchStatusEventListener {
}
Long assigneeId = event.getPayloadLong("assigneeId");
// payload 中没有 assigneeId 时从工单主表兜底forceComplete 等场景)
if (assigneeId == null) {
OpsOrderDO order = opsOrderMapper.selectById(event.getOrderId());
if (order != null) {
assigneeId = order.getAssigneeId();
}
}
if (assigneeId == null) {
return;
}
@@ -56,12 +68,27 @@ public class UserDispatchStatusEventListener {
case QUEUED -> userDispatchStatusService.onOrderQueued(assigneeId, orderId, orderType);
case CONFIRMED, ARRIVED -> userDispatchStatusService.onOrderStatusAdvanced(
assigneeId, orderId, newStatus.getStatus());
case COMPLETED, CANCELLED -> userDispatchStatusService.onOrderFinished(assigneeId, orderId);
case COMPLETED, CANCELLED -> handleFinished(assigneeId, orderId, oldStatus);
case PAUSED -> userDispatchStatusService.onOrderPaused(assigneeId, orderId);
default -> log.debug("人员调度状态无需处理: orderId={}, status={}", orderId, newStatus);
}
}
/**
* 处理 COMPLETED/CANCELLED 状态:
* 如果是从 QUEUED 直接到终态forceComplete / admin cancel
* 需要额外减 waitingTaskCount因为跳过了 DISPATCHED 阶段。
*/
private void handleFinished(Long assigneeId, Long orderId, WorkOrderStatusEnum oldStatus) {
userDispatchStatusService.onOrderFinished(assigneeId, orderId);
// QUEUED→COMPLETED/CANCELLED 跳过了 DISPATCHEDwaitingTaskCount 没有被减
if (oldStatus == WorkOrderStatusEnum.QUEUED) {
userDispatchStatusService.decrementWaitingTaskCount(assigneeId);
log.info("QUEUED 直接完成/取消,补偿减少 waitingTaskCount: assigneeId={}, orderId={}", assigneeId, orderId);
}
}
/**
* 处理 DISPATCHED 状态:区分直接派发 vs 从排队派发
*/

View File

@@ -101,6 +101,17 @@ public interface UserDispatchStatusService {
*/
void onOrderResumed(Long userId, Long orderId);
/**
* 补偿减少 waitingTaskCount
* <p>
* 当工单从 QUEUED 直接到 COMPLETED/CANCELLED跳过 DISPATCHED
* waitingTaskCount 在入队时+1 但没有经过 onQueuedOrderDispatched 减回,
* 需要通过此方法补偿。
*
* @param userId 执行人ID
*/
void decrementWaitingTaskCount(Long userId);
// ==================== 读操作(调度时使用)====================
/**

View File

@@ -39,6 +39,7 @@ public class UserDispatchStatusServiceImpl implements UserDispatchStatusService
private DefaultRedisScript<Long> queuedToDispatchedScript;
private DefaultRedisScript<Long> pauseResumeScript;
private DefaultRedisScript<Long> statusAdvancedScript;
private DefaultRedisScript<Long> decrementWaitingScript;
@PostConstruct
public void init() {
@@ -48,6 +49,7 @@ public class UserDispatchStatusServiceImpl implements UserDispatchStatusService
queuedToDispatchedScript = buildScript(LUA_QUEUED_TO_DISPATCHED);
pauseResumeScript = buildScript(LUA_PAUSE_RESUME);
statusAdvancedScript = buildScript(LUA_STATUS_ADVANCED);
decrementWaitingScript = buildScript(LUA_DECREMENT_WAITING);
log.info("人员调度状态服务已初始化Lua 脚本已预加载");
}
@@ -171,6 +173,21 @@ public class UserDispatchStatusServiceImpl implements UserDispatchStatusService
}
}
@Override
public void decrementWaitingTaskCount(Long userId) {
if (userId == null) {
return;
}
try {
stringRedisTemplate.execute(decrementWaitingScript,
List.of(keyOf(userId)),
String.valueOf(System.currentTimeMillis()));
log.debug("人员状态更新[DECREMENT_WAITING]: userId={}", userId);
} catch (Exception e) {
log.error("人员状态更新[DECREMENT_WAITING]失败: userId={}", userId, e);
}
}
// ==================== 读操作 ====================
@Override
@@ -389,4 +406,21 @@ public class UserDispatchStatusServiceImpl implements UserDispatchStatusService
redis.call('EXPIRE', KEYS[1], 86400)
return 1
""";
/**
* DECREMENT_WAITING: 补偿减少 waitingTaskCount
* 用于 QUEUED→COMPLETED/CANCELLED 跳过 DISPATCHED 的场景
* KEYS[1] = hash key
* ARGV[1] = timestamp
*/
private static final String LUA_DECREMENT_WAITING = """
local waiting = redis.call('HINCRBY', KEYS[1], 'waitingTaskCount', -1)
if waiting < 0 then
waiting = 0
redis.call('HSET', KEYS[1], 'waitingTaskCount', '0')
end
redis.call('HSET', KEYS[1], 'lastUpdateTime', ARGV[1])
redis.call('EXPIRE', KEYS[1], 86400)
return waiting
""";
}

View File

@@ -56,4 +56,33 @@ public class OpsOrderEventService {
opsOrderId, fromStatus, toStatus, eventType);
}
/**
* 记录工单事件(含操作人姓名冗余)
*/
public void recordEvent(Long opsOrderId,
String fromStatus,
String toStatus,
String eventType,
String operatorType,
Long operatorId,
String operatorName,
String remark) {
OpsOrderEventDO event = OpsOrderEventDO.builder()
.opsOrderId(opsOrderId)
.fromStatus(fromStatus)
.toStatus(toStatus)
.eventType(eventType)
.operatorType(operatorType)
.operatorId(operatorId)
.operatorName(operatorName)
.eventTime(LocalDateTime.now())
.remark(remark)
.build();
eventMapper.insert(event);
log.debug("记录工单事件: orderId={}, {} -> {}, eventType={}, operatorName={}",
opsOrderId, fromStatus, toStatus, eventType, operatorName);
}
}