feat(ops): 客流工单周期化,同区域复用活跃工单并逐级升级优先级
解决同一区域客流持续达标时重复创建工单的问题。改为:无活跃工单时 创建新工单,有未派发工单(PENDING/QUEUED)时升级优先级一级,有已派发 工单时忽略,所有分支均重置阈值计数器。工单终态时清除活跃标记。 - 新增 TrafficActiveOrderRedisDAO 管理区域活跃工单 Redis 标记 - 新增 CleanOrderService.upgradeOneLevelPriority 逐级升级优先级 - 改造 CleanOrderCreateEventHandler 实现客流触发周期化分支逻辑 - 新增 OpsOrderMapper.selectActiveTrafficOrder 作为 DB 兜底查询 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
* <p>
|
||||
* 存储在 Redis Hash 中,用于快速判断区域是否有活跃的客流触发工单
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ActiveOrderInfo {
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 工单优先级
|
||||
*/
|
||||
private Integer priority;
|
||||
}
|
||||
@@ -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
|
||||
* <p>
|
||||
* 用于标记某区域是否有客流触发的活跃工单,避免重复创建。
|
||||
* <p>
|
||||
* 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<Object, Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 保洁工单创建事件消费者
|
||||
* <p>
|
||||
* 订阅 IoT 模块发布的保洁工单创建事件
|
||||
* 订阅 IoT 模块发布的保洁工单创建事件。
|
||||
* <p>
|
||||
* 客流触发逻辑(周期化):
|
||||
* 1. 无活跃工单 → 创建新工单 → 标记活跃 → 重置阈值
|
||||
* 2. 有未派发工单(PENDING/QUEUED) → 升级优先级一级 → 重置阈值
|
||||
* 3. 有已派发工单(DISPATCHED/CONFIRMED/ARRIVED) → 忽略 → 重置阈值
|
||||
* 4. 已是 P0 → 不升级,记录审计日志 → 重置阈值
|
||||
* <p>
|
||||
* RocketMQ 配置:
|
||||
* - Topic: ops-order-create
|
||||
@@ -47,6 +61,14 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
|
||||
*/
|
||||
private static final int DEDUP_TTL_SECONDS = 300;
|
||||
|
||||
/**
|
||||
* 未派发状态集合(可升级优先级)
|
||||
*/
|
||||
private static final Set<String> UNDISPATCHED_STATUSES = Set.of(
|
||||
WorkOrderStatusEnum.PENDING.getStatus(),
|
||||
WorkOrderStatusEnum.QUEUED.getStatus()
|
||||
);
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@@ -59,6 +81,15 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
|
||||
@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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String, Object> extra = new HashMap<>();
|
||||
extra.put("eventId", event.getEventId());
|
||||
extra.put("triggerSource", event.getTriggerSource());
|
||||
@@ -145,7 +309,6 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
|
||||
extra.putAll(event.getTriggerData());
|
||||
}
|
||||
|
||||
// 记录日志(合并为一次调用)
|
||||
eventLogRecorder.record(EventLogRecord.builder()
|
||||
.module("clean")
|
||||
.domain(domain)
|
||||
@@ -163,6 +326,62 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录优先级升级日志
|
||||
*/
|
||||
private void recordUpgradeLog(CleanOrderCreateEventDTO event, Long orderId, PriorityEnum newPriority) {
|
||||
try {
|
||||
Map<String, Object> 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<String, Object> 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<String> {
|
||||
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<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成工单描述
|
||||
*/
|
||||
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<String> {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* 升级工单优先级一级(客流持续达标自动升级)
|
||||
* <p>
|
||||
* P3→P2, P2→P1, P1→P0。已是 P0 时不升级。
|
||||
* 如果升级到 P0 且工单在队列中,会触发 P0 打断逻辑。
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param reason 升级原因
|
||||
* @return 升级后的优先级枚举,如果已是 P0 返回 null
|
||||
*/
|
||||
PriorityEnum upgradeOneLevelPriority(Long orderId, String reason);
|
||||
|
||||
// ========== 作业时长计算 ==========
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -56,6 +56,21 @@ public interface OpsOrderMapper extends BaseMapperX<OpsOrderDO> {
|
||||
.orderByAsc(OpsOrderDO::getCreateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询区域内客流触发的活跃工单(非终态)
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 最近创建的活跃客流工单,不存在返回 null
|
||||
*/
|
||||
default OpsOrderDO selectActiveTrafficOrder(Long areaId) {
|
||||
return selectOne(new LambdaQueryWrapperX<OpsOrderDO>()
|
||||
.eq(OpsOrderDO::getAreaId, areaId)
|
||||
.eq(OpsOrderDO::getTriggerSource, "IOT_TRAFFIC")
|
||||
.notIn(OpsOrderDO::getStatus, "COMPLETED", "CANCELLED")
|
||||
.orderByDesc(OpsOrderDO::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
// 注意:分页查询方法需要在Service层实现,这里只提供基础查询方法
|
||||
// 具体分页查询请参考Service实现
|
||||
|
||||
|
||||
Reference in New Issue
Block a user