fix(ops): 调度状态补偿 QUEUED→终态跳过 DISPATCHED 场景
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:
@@ -125,7 +125,7 @@ public class DispatchEngineServiceAdapter implements DispatchEngineService {
|
||||
.queueId(queueId)
|
||||
.operatorType(OperatorTypeEnum.ADMIN)
|
||||
.operatorId(assigneeId)
|
||||
.reason("管理员手动派单")
|
||||
.reason("手动派单")
|
||||
.build()
|
||||
);
|
||||
|
||||
|
||||
@@ -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 跳过了 DISPATCHED,waitingTaskCount 没有被减
|
||||
if (oldStatus == WorkOrderStatusEnum.QUEUED) {
|
||||
userDispatchStatusService.decrementWaitingTaskCount(assigneeId);
|
||||
log.info("QUEUED 直接完成/取消,补偿减少 waitingTaskCount: assigneeId={}, orderId={}", assigneeId, orderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 DISPATCHED 状态:区分直接派发 vs 从排队派发
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
// ==================== 读操作(调度时使用)====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
""";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user