From 0775ead5ffc6545fca326d758c845b1ae5a3d3b4 Mon Sep 17 00:00:00 2001 From: lzh Date: Sun, 8 Feb 2026 00:21:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(ops):=20=E5=AE=A2=E6=B5=81=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=91=A8=E6=9C=9F=E5=8C=96=EF=BC=8C=E5=90=8C=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E5=A4=8D=E7=94=A8=E6=B4=BB=E8=B7=83=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E5=B9=B6=E9=80=90=E7=BA=A7=E5=8D=87=E7=BA=A7=E4=BC=98=E5=85=88?= =?UTF-8?q?=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决同一区域客流持续达标时重复创建工单的问题。改为:无活跃工单时 创建新工单,有未派发工单(PENDING/QUEUED)时升级优先级一级,有已派发 工单时忽略,所有分支均重置阈值计数器。工单终态时清除活跃标记。 - 新增 TrafficActiveOrderRedisDAO 管理区域活跃工单 Redis 标记 - 新增 CleanOrderService.upgradeOneLevelPriority 逐级升级优先级 - 改造 CleanOrderCreateEventHandler 实现客流触发周期化分支逻辑 - 新增 OpsOrderMapper.selectActiveTrafficOrder 作为 DB 兜底查询 Co-Authored-By: Claude Opus 4.6 --- .../dal/redis/ActiveOrderInfo.java | 35 +++ .../dal/redis/TrafficActiveOrderRedisDAO.java | 106 +++++++ .../CleanOrderCreateEventHandler.java | 277 +++++++++++++++--- .../service/cleanorder/CleanOrderService.java | 13 + .../cleanorder/CleanOrderServiceImpl.java | 43 +++ .../dal/mysql/workorder/OpsOrderMapper.java | 15 + 6 files changed, 454 insertions(+), 35 deletions(-) create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/ActiveOrderInfo.java create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/TrafficActiveOrderRedisDAO.java diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/ActiveOrderInfo.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/ActiveOrderInfo.java new file mode 100644 index 0000000..eda76ab --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/ActiveOrderInfo.java @@ -0,0 +1,35 @@ +package com.viewsh.module.ops.environment.dal.redis; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 客流触发的活跃工单信息 DTO + *

+ * 存储在 Redis Hash 中,用于快速判断区域是否有活跃的客流触发工单 + * + * @author AI + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ActiveOrderInfo { + + /** + * 工单ID + */ + private Long orderId; + + /** + * 工单状态 + */ + private String status; + + /** + * 工单优先级 + */ + private Integer priority; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/TrafficActiveOrderRedisDAO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/TrafficActiveOrderRedisDAO.java new file mode 100644 index 0000000..3b473e2 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/TrafficActiveOrderRedisDAO.java @@ -0,0 +1,106 @@ +package com.viewsh.module.ops.environment.dal.redis; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.util.Map; + +/** + * 客流触发活跃工单 Redis DAO + *

+ * 用于标记某区域是否有客流触发的活跃工单,避免重复创建。 + *

+ * Redis Key: ops:clean:traffic:active-order:{areaId} + * Value: Hash { orderId, status, priority } + * TTL: 无(由终态主动删除) + * + * @author AI + */ +@Slf4j +@Repository +public class TrafficActiveOrderRedisDAO { + + private static final String KEY_PATTERN = "ops:clean:traffic:active-order:%s"; + + private static final String FIELD_ORDER_ID = "orderId"; + private static final String FIELD_STATUS = "status"; + private static final String FIELD_PRIORITY = "priority"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 标记区域有活跃的客流触发工单 + */ + public void markActive(Long areaId, Long orderId, String status, Integer priority) { + String key = buildKey(areaId); + stringRedisTemplate.opsForHash().putAll(key, Map.of( + FIELD_ORDER_ID, String.valueOf(orderId), + FIELD_STATUS, status, + FIELD_PRIORITY, String.valueOf(priority) + )); + log.debug("[TrafficActiveOrderRedisDAO] 标记活跃工单: areaId={}, orderId={}, status={}, priority={}", + areaId, orderId, status, priority); + } + + /** + * 获取区域的活跃工单信息 + * + * @return 活跃工单信息,不存在返回 null + */ + public ActiveOrderInfo getActive(Long areaId) { + String key = buildKey(areaId); + Map entries = stringRedisTemplate.opsForHash().entries(key); + if (entries == null || entries.isEmpty()) { + return null; + } + + String orderIdStr = (String) entries.get(FIELD_ORDER_ID); + if (orderIdStr == null) { + return null; + } + + return ActiveOrderInfo.builder() + .orderId(Long.parseLong(orderIdStr)) + .status((String) entries.get(FIELD_STATUS)) + .priority(Integer.parseInt((String) entries.get(FIELD_PRIORITY))) + .build(); + } + + /** + * 更新活跃工单的状态 + */ + public void updateStatus(Long areaId, String newStatus) { + String key = buildKey(areaId); + if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) { + stringRedisTemplate.opsForHash().put(key, FIELD_STATUS, newStatus); + log.debug("[TrafficActiveOrderRedisDAO] 更新状态: areaId={}, newStatus={}", areaId, newStatus); + } + } + + /** + * 更新活跃工单的优先级 + */ + public void updatePriority(Long areaId, Integer newPriority) { + String key = buildKey(areaId); + if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) { + stringRedisTemplate.opsForHash().put(key, FIELD_PRIORITY, String.valueOf(newPriority)); + log.debug("[TrafficActiveOrderRedisDAO] 更新优先级: areaId={}, newPriority={}", areaId, newPriority); + } + } + + /** + * 移除区域的活跃工单标记(工单终态时调用) + */ + public void removeActive(Long areaId) { + String key = buildKey(areaId); + stringRedisTemplate.delete(key); + log.debug("[TrafficActiveOrderRedisDAO] 移除活跃标记: areaId={}", areaId); + } + + private String buildKey(Long areaId) { + return String.format(KEY_PATTERN, areaId); + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCreateEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCreateEventHandler.java index 6f171cf..cad90d8 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCreateEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCreateEventHandler.java @@ -1,8 +1,15 @@ package com.viewsh.module.ops.environment.integration.consumer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.viewsh.module.iot.api.device.IotDeviceControlApi; +import com.viewsh.module.iot.api.device.dto.ResetTrafficCounterReqDTO; +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.WorkOrderStatusEnum; import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqDTO; +import com.viewsh.module.ops.environment.dal.redis.ActiveOrderInfo; +import com.viewsh.module.ops.environment.dal.redis.TrafficActiveOrderRedisDAO; import com.viewsh.module.ops.environment.integration.dto.CleanOrderCreateEventDTO; import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService; import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; @@ -19,12 +26,19 @@ import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** * 保洁工单创建事件消费者 *

- * 订阅 IoT 模块发布的保洁工单创建事件 + * 订阅 IoT 模块发布的保洁工单创建事件。 + *

+ * 客流触发逻辑(周期化): + * 1. 无活跃工单 → 创建新工单 → 标记活跃 → 重置阈值 + * 2. 有未派发工单(PENDING/QUEUED) → 升级优先级一级 → 重置阈值 + * 3. 有已派发工单(DISPATCHED/CONFIRMED/ARRIVED) → 忽略 → 重置阈值 + * 4. 已是 P0 → 不升级,记录审计日志 → 重置阈值 *

* RocketMQ 配置: * - Topic: ops-order-create @@ -47,6 +61,14 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { */ private static final int DEDUP_TTL_SECONDS = 300; + /** + * 未派发状态集合(可升级优先级) + */ + private static final Set UNDISPATCHED_STATUSES = Set.of( + WorkOrderStatusEnum.PENDING.getStatus(), + WorkOrderStatusEnum.QUEUED.getStatus() + ); + @Resource private ObjectMapper objectMapper; @@ -59,6 +81,15 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { @Resource private EventLogRecorder eventLogRecorder; + @Resource + private TrafficActiveOrderRedisDAO trafficActiveOrderRedisDAO; + + @Resource + private IotDeviceControlApi iotDeviceControlApi; + + @Resource + private OpsOrderMapper opsOrderMapper; + @Override public void onMessage(String message) { try { @@ -70,7 +101,7 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { Boolean firstTime = stringRedisTemplate.opsForValue() .setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS); - if (!firstTime) { + if (!Boolean.TRUE.equals(firstTime)) { log.debug("[CleanOrderCreateEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); return; } @@ -91,10 +122,152 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { log.info("[CleanOrderCreateEventHandler] 收到工单创建事件: eventId={}, areaId={}, triggerSource={}", event.getEventId(), event.getAreaId(), event.getTriggerSource()); - // 1. 构建创建请求(使用 CleanOrderAutoCreateReqDTO) + // 客流触发 → 周期化逻辑 + if ("IOT_TRAFFIC".equals(event.getTriggerSource())) { + handleTrafficTrigger(event); + return; + } + + // 非客流触发 → 原逻辑 + handleNonTrafficTrigger(event); + } + + /** + * 处理客流触发的工单创建(周期化逻辑) + */ + private void handleTrafficTrigger(CleanOrderCreateEventDTO event) { + Long areaId = event.getAreaId(); + + // 1. 查询活跃工单(Redis 优先,DB 兜底) + ActiveOrderInfo activeOrder = trafficActiveOrderRedisDAO.getActive(areaId); + if (activeOrder == null) { + activeOrder = queryActiveTrafficOrderFromDb(areaId); + if (activeOrder != null) { + // 恢复 Redis 标记 + trafficActiveOrderRedisDAO.markActive(areaId, activeOrder.getOrderId(), + activeOrder.getStatus(), activeOrder.getPriority()); + log.info("[CleanOrderCreateEventHandler] Redis标记丢失已恢复: areaId={}, orderId={}", + areaId, activeOrder.getOrderId()); + } + } + + // 2. 有活跃工单 → 升级或忽略 + if (activeOrder != null) { + String status = activeOrder.getStatus(); + + if (UNDISPATCHED_STATUSES.contains(status)) { + // 未派发 → 升级优先级一级 + PriorityEnum result = cleanOrderService.upgradeOneLevelPriority( + activeOrder.getOrderId(), "客流持续达标自动升级"); + + if (result != null) { + trafficActiveOrderRedisDAO.updatePriority(areaId, result.getPriority()); + recordUpgradeLog(event, activeOrder.getOrderId(), result); + log.info("[CleanOrderCreateEventHandler] 工单优先级已升级: areaId={}, orderId={}, newPriority={}", + areaId, activeOrder.getOrderId(), result); + } else { + // 已是 P0,记录审计日志 + recordP0CeilingLog(event, activeOrder.getOrderId()); + log.info("[CleanOrderCreateEventHandler] 工单已是P0封顶,不再升级: areaId={}, orderId={}", + areaId, activeOrder.getOrderId()); + } + } else { + // 已派发(DISPATCHED/CONFIRMED/ARRIVED)→ 忽略 + log.info("[CleanOrderCreateEventHandler] 区域{}已有已派发工单{}(状态:{}),忽略本次客流触发", + areaId, activeOrder.getOrderId(), status); + } + + // ★ 所有分支都重置阈值 + resetTrafficCounter(event); + return; + } + + // 3. 无活跃工单 → 创建新工单 + CleanOrderAutoCreateReqDTO createReq = buildCreateRequest(event); + Long orderId = cleanOrderService.createAutoCleanOrder(createReq); + + // 标记 Redis 活跃工单 + trafficActiveOrderRedisDAO.markActive(areaId, orderId, + WorkOrderStatusEnum.PENDING.getStatus(), createReq.getPriority()); + + // 重置阈值 + resetTrafficCounter(event); + + // 记录业务日志 + recordOrderCreatedLog(event, orderId, createReq); + + log.info("[CleanOrderCreateEventHandler] 客流工单创建成功: eventId={}, orderId={}, areaId={}", + event.getEventId(), orderId, areaId); + } + + /** + * 处理非客流触发的工单创建(原逻辑不变) + */ + private void handleNonTrafficTrigger(CleanOrderCreateEventDTO event) { + CleanOrderAutoCreateReqDTO createReq = buildCreateRequest(event); + Long orderId = cleanOrderService.createAutoCleanOrder(createReq); + + recordOrderCreatedLog(event, orderId, createReq); + + log.info("[CleanOrderCreateEventHandler] 工单创建成功: eventId={}, orderId={}, areaId={}", + event.getEventId(), orderId, event.getAreaId()); + } + + // ==================== 阈值重置 ==================== + + /** + * 重置客流阈值计数器 + */ + private void resetTrafficCounter(CleanOrderCreateEventDTO event) { + Long deviceId = event.getTriggerDeviceId(); + if (deviceId == null) { + log.warn("[CleanOrderCreateEventHandler] 缺少设备ID,跳过重置阈值: eventId={}", event.getEventId()); + return; + } + + try { + ResetTrafficCounterReqDTO reqDTO = ResetTrafficCounterReqDTO.builder() + .deviceId(deviceId) + .remark("客流达标后重置阈值计数器") + .build(); + var result = iotDeviceControlApi.resetTrafficCounter(reqDTO); + + if (result.getData() != null && result.getData()) { + log.info("[CleanOrderCreateEventHandler] 阈值计数器重置成功: deviceId={}", deviceId); + } else { + log.error("[CleanOrderCreateEventHandler] 阈值计数器重置失败: deviceId={}", deviceId); + } + } catch (Exception e) { + log.error("[CleanOrderCreateEventHandler] 阈值计数器重置异常: deviceId={}", deviceId, e); + } + } + + // ==================== DB 兜底查询 ==================== + + /** + * 从 DB 查询区域内客流触发的活跃工单(Redis 标记丢失时兜底) + */ + private ActiveOrderInfo queryActiveTrafficOrderFromDb(Long areaId) { + OpsOrderDO order = opsOrderMapper.selectActiveTrafficOrder(areaId); + if (order == null) { + return null; + } + return ActiveOrderInfo.builder() + .orderId(order.getId()) + .status(order.getStatus()) + .priority(order.getPriority()) + .build(); + } + + // ==================== 构建请求 ==================== + + /** + * 构建工单创建请求 + */ + private CleanOrderAutoCreateReqDTO buildCreateRequest(CleanOrderCreateEventDTO event) { CleanOrderAutoCreateReqDTO createReq = new CleanOrderAutoCreateReqDTO(); createReq.setOrderType("CLEAN"); - createReq.setSourceType("TRAFFIC"); // 系统触发 + createReq.setSourceType("TRAFFIC"); createReq.setTitle(generateOrderTitle(event)); createReq.setDescription(generateOrderDescription(event)); createReq.setPriority( @@ -113,28 +286,19 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { createReq.setTriggerDeviceKey(event.getTriggerDeviceKey()); createReq.setTriggerData(event.getTriggerData()); - // 2. 创建工单(同时创建主表+扩展表) - Long orderId = cleanOrderService.createAutoCleanOrder(createReq); - - // 3. 记录业务日志 - recordOrderCreatedLog(event, orderId, createReq); - - // 注意:客流计数器重置已移至 CleanOrderEventListener,在事务提交后执行 - - log.info("[CleanOrderCreateEventHandler] 工单创建成功: eventId={}, orderId={}, areaId={}", - event.getEventId(), orderId, event.getAreaId()); + return createReq; } + // ==================== 业务日志 ==================== + /** * 记录工单创建业务日志 */ private void recordOrderCreatedLog(CleanOrderCreateEventDTO event, Long orderId, CleanOrderAutoCreateReqDTO createReq) { try { - // 确定事件域 EventDomain domain = determineDomain(event.getTriggerSource()); - // 构建扩展信息 Map extra = new HashMap<>(); extra.put("eventId", event.getEventId()); extra.put("triggerSource", event.getTriggerSource()); @@ -145,7 +309,6 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { extra.putAll(event.getTriggerData()); } - // 记录日志(合并为一次调用) eventLogRecorder.record(EventLogRecord.builder() .module("clean") .domain(domain) @@ -163,6 +326,62 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { } } + /** + * 记录优先级升级日志 + */ + private void recordUpgradeLog(CleanOrderCreateEventDTO event, Long orderId, PriorityEnum newPriority) { + try { + Map extra = new HashMap<>(); + extra.put("eventId", event.getEventId()); + extra.put("areaId", event.getAreaId()); + extra.put("newPriority", newPriority.getPriority()); + extra.put("reason", "客流持续达标自动升级"); + + eventLogRecorder.record(EventLogRecord.builder() + .module("clean") + .domain(EventDomain.TRAFFIC) + .eventType("PRIORITY_UPGRADE") + .message(String.format("客流持续达标,工单优先级升级至%s [区域:%d]", + newPriority.getDescription(), event.getAreaId())) + .targetId(orderId) + .targetType("order") + .deviceId(event.getTriggerDeviceId()) + .level(EventLevel.WARN) + .payload(extra) + .build()); + + } catch (Exception e) { + log.warn("[CleanOrderCreateEventHandler] 记录升级日志失败: orderId={}", orderId, e); + } + } + + /** + * 记录 P0 封顶审计日志 + */ + private void recordP0CeilingLog(CleanOrderCreateEventDTO event, Long orderId) { + try { + Map extra = new HashMap<>(); + extra.put("eventId", event.getEventId()); + extra.put("areaId", event.getAreaId()); + extra.put("reason", "已是P0最高优先级,无法继续升级"); + + eventLogRecorder.record(EventLogRecord.builder() + .module("clean") + .domain(EventDomain.TRAFFIC) + .eventType("PRIORITY_CEILING") + .message(String.format("客流持续达标但工单已是P0封顶 [区域:%d]", event.getAreaId())) + .targetId(orderId) + .targetType("order") + .deviceId(event.getTriggerDeviceId()) + .level(EventLevel.WARN) + .payload(extra) + .build()); + + } catch (Exception e) { + log.warn("[CleanOrderCreateEventHandler] 记录P0封顶日志失败: orderId={}", orderId, e); + } + } + /** * 确定事件域 */ @@ -192,9 +411,8 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { event.getTriggerDeviceKey(), event.getTriggerSource()); } - /** - * 生成工单标题 - */ + // ==================== 工具方法 ==================== + private String generateOrderTitle(CleanOrderCreateEventDTO event) { if ("IOT_TRAFFIC".equals(event.getTriggerSource())) { return "客流阈值触发保洁"; @@ -207,9 +425,6 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { } } - /** - * 生成工单描述 - */ private String generateOrderDescription(CleanOrderCreateEventDTO event) { StringBuilder desc = new StringBuilder(); desc.append("触发来源: ").append(event.getTriggerSource()).append("\n"); @@ -227,31 +442,23 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { return desc.toString(); } - /** - * 计算预计作业时长(分钟) - */ private Integer calculateExpectedDuration(CleanOrderCreateEventDTO event) { - // 根据触发来源和客流数据估算时长 if ("IOT_TRAFFIC".equals(event.getTriggerSource()) && event.getTriggerData() != null) { Object actualCountObj = event.getTriggerData().get("actualCount"); if (actualCountObj != null) { Long actualCount = ((Number) actualCountObj).longValue(); - // 客流越大,预计耗时越长 if (actualCount > 50) { - return 45; // 高客流 + return 45; } else if (actualCount > 20) { - return 30; // 中客流 + return 30; } else { - return 20; // 低客流 + return 20; } } } - return 30; // 默认30分钟 + return 30; } - /** - * 从事件数据中提取规则ID - */ private Long extractRuleId(CleanOrderCreateEventDTO event) { if (event.getTriggerData() != null && event.getTriggerData().containsKey("ruleId")) { Object ruleIdObj = event.getTriggerData().get("ruleId"); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java index f800dae..f839391 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java @@ -1,5 +1,6 @@ package com.viewsh.module.ops.environment.service.cleanorder; +import com.viewsh.module.ops.enums.PriorityEnum; import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqDTO; /** @@ -133,6 +134,18 @@ public interface CleanOrderService { */ boolean upgradePriorityToP0(Long orderId, String reason); + /** + * 升级工单优先级一级(客流持续达标自动升级) + *

+ * P3→P2, P2→P1, P1→P0。已是 P0 时不升级。 + * 如果升级到 P0 且工单在队列中,会触发 P0 打断逻辑。 + * + * @param orderId 工单ID + * @param reason 升级原因 + * @return 升级后的优先级枚举,如果已是 P0 返回 null + */ + PriorityEnum upgradeOneLevelPriority(Long orderId, String reason); + // ========== 作业时长计算 ========== /** diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java index 31fd56a..9b0de33 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java @@ -256,6 +256,49 @@ public class CleanOrderServiceImpl implements CleanOrderService { return true; } + @Override + @Transactional(rollbackFor = Exception.class) + public PriorityEnum upgradeOneLevelPriority(Long orderId, String reason) { + log.info("升级工单优先级一级: orderId={}, reason={}", orderId, reason); + + // 1. 查询工单 + OpsOrderDO order = opsOrderMapper.selectById(orderId); + if (order == null) { + log.error("工单不存在: orderId={}", orderId); + return null; + } + + // 2. 检查当前优先级 + PriorityEnum currentPriority = PriorityEnum.fromPriority(order.getPriority()); + if (currentPriority == PriorityEnum.P0) { + log.info("工单已经是P0优先级,无法再升级: orderId={}", orderId); + return null; + } + + // 3. 计算新优先级(priority 数值越小越高) + PriorityEnum newPriority = PriorityEnum.fromPriority(currentPriority.getPriority() - 1); + log.info("工单优先级升级: orderId={}, {} -> {}", orderId, currentPriority, newPriority); + + // 4. 更新工单优先级 + order.setPriority(newPriority.getPriority()); + opsOrderMapper.updateById(order); + + // 5. 如果工单在队列中,同步队列分数 + OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId); + if (queueDTO != null) { + orderQueueService.adjustPriority(queueDTO.getId(), newPriority, reason); + + // 6. 如果升级到 P0,触发紧急打断逻辑 + if (newPriority == PriorityEnum.P0) { + DispatchResult result = dispatchEngine.urgentInterrupt(orderId, queueDTO.getUserId()); + cleanOrderEventListener.sendPriorityUpgradeNotification(queueDTO.getUserId(), order.getOrderCode(), orderId); + log.warn("客流升级到P0,触发紧急打断: orderId={}, success={}", orderId, result.isSuccess()); + } + } + + return newPriority; + } + // ==================== 保洁特有的状态转换 ==================== @Override diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/workorder/OpsOrderMapper.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/workorder/OpsOrderMapper.java index 345cb01..f927eda 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/workorder/OpsOrderMapper.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/workorder/OpsOrderMapper.java @@ -56,6 +56,21 @@ public interface OpsOrderMapper extends BaseMapperX { .orderByAsc(OpsOrderDO::getCreateTime)); } + /** + * 查询区域内客流触发的活跃工单(非终态) + * + * @param areaId 区域ID + * @return 最近创建的活跃客流工单,不存在返回 null + */ + default OpsOrderDO selectActiveTrafficOrder(Long areaId) { + return selectOne(new LambdaQueryWrapperX() + .eq(OpsOrderDO::getAreaId, areaId) + .eq(OpsOrderDO::getTriggerSource, "IOT_TRAFFIC") + .notIn(OpsOrderDO::getStatus, "COMPLETED", "CANCELLED") + .orderByDesc(OpsOrderDO::getCreateTime) + .last("LIMIT 1")); + } + // 注意:分页查询方法需要在Service层实现,这里只提供基础查询方法 // 具体分页查询请参考Service实现