From 78396cf35aeba5912196d97f6cb1b5c11df995c8 Mon Sep 17 00:00:00 2001 From: lzh Date: Fri, 27 Mar 2026 16:09:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(ops):=20=E8=B0=83=E5=BA=A6=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E8=A1=A5=E5=81=BF=20QUEUED=E2=86=92=E7=BB=88=E6=80=81?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=20DISPATCHED=20=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../DispatchEngineServiceAdapter.java | 2 +- .../UserDispatchStatusEventListener.java | 29 +++++++++++++++- .../dispatch/UserDispatchStatusService.java | 11 ++++++ .../UserDispatchStatusServiceImpl.java | 34 +++++++++++++++++++ .../service/event/OpsOrderEventService.java | 29 ++++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/DispatchEngineServiceAdapter.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/DispatchEngineServiceAdapter.java index 16ee5e3..a6433ec 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/DispatchEngineServiceAdapter.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/DispatchEngineServiceAdapter.java @@ -125,7 +125,7 @@ public class DispatchEngineServiceAdapter implements DispatchEngineService { .queueId(queueId) .operatorType(OperatorTypeEnum.ADMIN) .operatorId(assigneeId) - .reason("管理员手动派单") + .reason("手动派单") .build() ); diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusEventListener.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusEventListener.java index 99ad67f..3592acc 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusEventListener.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusEventListener.java @@ -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 从排队派发 */ diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusService.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusService.java index f5e0ab9..bd3059f 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusService.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusService.java @@ -101,6 +101,17 @@ public interface UserDispatchStatusService { */ void onOrderResumed(Long userId, Long orderId); + /** + * 补偿减少 waitingTaskCount + *

+ * 当工单从 QUEUED 直接到 COMPLETED/CANCELLED(跳过 DISPATCHED)时, + * waitingTaskCount 在入队时+1 但没有经过 onQueuedOrderDispatched 减回, + * 需要通过此方法补偿。 + * + * @param userId 执行人ID + */ + void decrementWaitingTaskCount(Long userId); + // ==================== 读操作(调度时使用)==================== /** diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusServiceImpl.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusServiceImpl.java index 6803a09..4529cfb 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusServiceImpl.java @@ -39,6 +39,7 @@ public class UserDispatchStatusServiceImpl implements UserDispatchStatusService private DefaultRedisScript queuedToDispatchedScript; private DefaultRedisScript pauseResumeScript; private DefaultRedisScript statusAdvancedScript; + private DefaultRedisScript 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 + """; } diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java index a5a0f23..20e9398 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/event/OpsOrderEventService.java @@ -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); + } + }