From f080cff7b7f9ed693f4bcbcb701b04cc6b117edd Mon Sep 17 00:00:00 2001 From: lzh Date: Wed, 29 Apr 2026 22:21:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(ops):=20=E6=81=A2=E5=A4=8D=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B6=88=E8=B4=B9=E9=A1=B9=E7=9B=AE=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BadgeDeviceStatusEventHandler.java | 38 +- .../CleanOrderArriveEventHandler.java | 7 +- .../consumer/CleanOrderAuditEventHandler.java | 509 +++++++++--------- .../CleanOrderCompleteEventHandler.java | 7 +- .../CleanOrderConfirmEventHandler.java | 7 +- .../CleanOrderCreateEventHandler.java | 45 +- .../IntegrationProjectContextExecutor.java | 91 ++++ .../consumer/TrajectoryEnterEventHandler.java | 28 +- .../consumer/TrajectoryLeaveEventHandler.java | 24 +- .../integration/dto/BaseDeviceEventDTO.java | 3 + .../dto/CleanOrderArriveEventDTO.java | 128 ++--- .../dto/CleanOrderAuditEventDTO.java | 136 ++--- .../dto/CleanOrderCompleteEventDTO.java | 128 ++--- .../dto/CleanOrderCreateEventDTO.java | 133 ++--- .../dto/IotDeviceStatusChangedEventDTO.java | 220 ++++---- .../dto/TrajectoryEnterEventDTO.java | 2 + .../dto/TrajectoryLeaveEventDTO.java | 2 + 17 files changed, 842 insertions(+), 666 deletions(-) create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/IntegrationProjectContextExecutor.java diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/BadgeDeviceStatusEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/BadgeDeviceStatusEventHandler.java index 283f3cef..98f78335 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/BadgeDeviceStatusEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/BadgeDeviceStatusEventHandler.java @@ -46,6 +46,9 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener { @Resource private AreaDeviceService areaDeviceService; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -66,12 +69,16 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener { } private void handleDeviceStatusChange(IotDeviceStatusChangedEventDTO event) { - if (!isBadgeDevice(event.getDeviceId())) { - log.debug("[BadgeDeviceStatusEventHandler] 非工牌设备,忽略状态同步: deviceId={}", event.getDeviceId()); + OpsAreaDeviceRelationDO relation = getBadgeRelation(event.getDeviceId()); + if (relation == null) { + log.debug("[BadgeDeviceStatusEventHandler] ignore non-badge device status event: deviceId={}", event.getDeviceId()); return; } + projectContextExecutor.execute(event.getProjectId(), null, relation.getAreaId(), event.getDeviceId(), event.getEventId(), + () -> updateBadgeDeviceStatus(event, relation.getAreaId())); + } - Long areaId = getAreaIdByDeviceId(event.getDeviceId()); + private void updateBadgeDeviceStatus(IotDeviceStatusChangedEventDTO event, Long areaId) { if (event.isOnline()) { badgeDeviceStatusService.updateBadgeOnlineStatus( event.getDeviceId(), event.getDeviceName(), event.getNickname(), areaId, @@ -84,39 +91,22 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener { BadgeDeviceStatusEnum.OFFLINE, "设备离线"); return; } - log.debug("[BadgeDeviceStatusEventHandler] 忽略未处理的状态变更: deviceId={}, newStatus={}", + log.debug("[BadgeDeviceStatusEventHandler] ignore device status event: deviceId={}, newStatus={}", event.getDeviceId(), event.getNewStatus()); } - private boolean isBadgeDevice(Long deviceId) { - if (deviceId == null) { - return false; - } - try { - OpsAreaDeviceRelationDO relation = areaDeviceService.getByDeviceId(deviceId); - return relation != null && "BADGE".equals(relation.getRelationType()); - } catch (Exception e) { - log.warn("[BadgeDeviceStatusEventHandler] 查询设备绑定关系失败: deviceId={}", deviceId, e); - return false; - } - } - - private Long getAreaIdByDeviceId(Long deviceId) { + private OpsAreaDeviceRelationDO getBadgeRelation(Long deviceId) { if (deviceId == null) { return null; } try { OpsAreaDeviceRelationDO relation = areaDeviceService.getByDeviceId(deviceId); - if (relation != null && "BADGE".equals(relation.getRelationType())) { - return relation.getAreaId(); - } - return null; + return relation != null && "BADGE".equals(relation.getRelationType()) ? relation : null; } catch (Exception e) { - log.warn("[BadgeDeviceStatusEventHandler] 查询设备所属区域失败: deviceId={}", deviceId, e); + log.warn("[BadgeDeviceStatusEventHandler] query badge relation failed: deviceId={}", deviceId, e); return null; } } - private void executeInTenantContext(Long tenantId, Runnable runnable) { Long currentTenantId = TenantContextHolder.getTenantId(); if (tenantId == null || Objects.equals(currentTenantId, tenantId)) { diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderArriveEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderArriveEventHandler.java index 7fb6e99b..7b9fee9d 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderArriveEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderArriveEventHandler.java @@ -50,6 +50,9 @@ public class CleanOrderArriveEventHandler implements RocketMQListener { @Resource private OrderLifecycleManager orderLifecycleManager; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -61,7 +64,9 @@ public class CleanOrderArriveEventHandler implements RocketMQListener { log.debug("[CleanOrderArriveEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); return; } - executeInTenantContext(event.getTenantId(), () -> handleOrderArrive(event)); + executeInTenantContext(event.getTenantId(), () -> projectContextExecutor.execute( + event.getProjectId(), event.getOrderId(), event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleOrderArrive(event))); } catch (Exception e) { log.error("[CleanOrderArriveEventHandler] 消息处理失败: message={}", message, e); throw new RuntimeException("保洁工单到岗事件处理失败", e); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java index 93f26aeb..c8cf1555 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderAuditEventHandler.java @@ -1,265 +1,270 @@ -package com.viewsh.module.ops.environment.integration.consumer; - +package com.viewsh.module.ops.environment.integration.consumer; + import com.fasterxml.jackson.databind.ObjectMapper; import com.viewsh.framework.tenant.core.context.TenantContextHolder; import com.viewsh.framework.tenant.core.util.TenantUtils; -import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; -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 com.viewsh.module.ops.environment.constants.CleanNotificationConstants; -import com.viewsh.module.ops.environment.integration.dto.CleanOrderAuditEventDTO; -import com.viewsh.module.ops.environment.service.voice.TtsQueueMessage; -import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService; -import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; -import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; -import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule; -import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; +import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; +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 com.viewsh.module.ops.environment.constants.CleanNotificationConstants; +import com.viewsh.module.ops.environment.integration.dto.CleanOrderAuditEventDTO; +import com.viewsh.module.ops.environment.service.voice.TtsQueueMessage; +import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; +import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule; +import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import com.viewsh.module.ops.infrastructure.redis.OpsRedisKeyBuilder; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.apache.rocketmq.spring.annotation.ConsumeMode; -import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; -import org.apache.rocketmq.spring.core.RocketMQListener; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Component; - +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + import java.util.Arrays; import java.util.Objects; import java.util.concurrent.TimeUnit; - -/** - * 保洁工单审计事件消费者 - *

- * 订阅 IoT 模块发布的保洁工单审计事件 - * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) - *

- * RocketMQ 配置: - * - Topic: ops-order-audit - * - ConsumerGroup: ops-clean-order-audit-group - * - * @author AI - */ -@Slf4j -@Component -@RocketMQMessageListener( - topic = "ops-order-audit", - consumerGroup = "ops-clean-order-audit-group", - consumeMode = ConsumeMode.CONCURRENTLY, - selectorExpression = "*", - accessKey = "${rocketmq.consumer.access-key:}", - secretKey = "${rocketmq.consumer.secret-key:}" -) -public class CleanOrderAuditEventHandler implements RocketMQListener { - - /** - * 幂等性控制 Key 模式 - */ - private static final String DEDUP_KEY_PATTERN = "ops:clean:dedup:audit:%s"; - - /** - * 幂等性控制 TTL(秒) - */ - private static final int DEDUP_TTL_SECONDS = 300; - - private static final String TRIGGER_SOURCE_QUERY = "IOT_BUTTON_QUERY"; - - @Resource - private ObjectMapper objectMapper; - - @Resource - private StringRedisTemplate stringRedisTemplate; - - @Resource - private EventLogRecorder eventLogRecorder; - - @Resource - private VoiceBroadcastService voiceBroadcastService; - - @Resource - private OpsOrderMapper opsOrderMapper; - - @Override - public void onMessage(String message) { - try { - // 1. JSON 反序列化 - CleanOrderAuditEventDTO event = objectMapper.readValue(message, CleanOrderAuditEventDTO.class); - - // 2. 幂等性检查 + +/** + * 保洁工单审计事件消费者 + *

+ * 订阅 IoT 模块发布的保洁工单审计事件 + * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) + *

+ * RocketMQ 配置: + * - Topic: ops-order-audit + * - ConsumerGroup: ops-clean-order-audit-group + * + * @author AI + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = "ops-order-audit", + consumerGroup = "ops-clean-order-audit-group", + consumeMode = ConsumeMode.CONCURRENTLY, + selectorExpression = "*", + accessKey = "${rocketmq.consumer.access-key:}", + secretKey = "${rocketmq.consumer.secret-key:}" +) +public class CleanOrderAuditEventHandler implements RocketMQListener { + + /** + * 幂等性控制 Key 模式 + */ + private static final String DEDUP_KEY_PATTERN = "ops:clean:dedup:audit:%s"; + + /** + * 幂等性控制 TTL(秒) + */ + private static final int DEDUP_TTL_SECONDS = 300; + + private static final String TRIGGER_SOURCE_QUERY = "IOT_BUTTON_QUERY"; + + @Resource + private ObjectMapper objectMapper; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Resource + private EventLogRecorder eventLogRecorder; + + @Resource + private VoiceBroadcastService voiceBroadcastService; + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + + @Override + public void onMessage(String message) { + try { + // 1. JSON 反序列化 + CleanOrderAuditEventDTO event = objectMapper.readValue(message, CleanOrderAuditEventDTO.class); + + // 2. 幂等性检查 String dedupKey = OpsRedisKeyBuilder.eventDedup(event.getTenantId(), "audit", event.getEventId()); - Boolean firstTime = stringRedisTemplate.opsForValue() - .setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS); - - if (!firstTime) { - log.debug("[CleanOrderAuditEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); - return; - } - - // 3. 业务处理 - executeInTenantContext(event.getTenantId(), () -> handleAuditEvent(event)); - - } catch (Exception e) { - log.error("[CleanOrderAuditEventHandler] 消息处理失败: message={}", message, e); - // 审计日志失败不抛出异常,避免影响主流程 - } - } - - /** - * 处理审计事件 - */ - private void handleAuditEvent(CleanOrderAuditEventDTO event) { - log.debug("[CleanOrderAuditEventHandler] 收到审计事件: eventId={}, auditType={}, message={}", - event.getEventId(), event.getAuditType(), event.getMessage()); - - if (TRIGGER_SOURCE_QUERY.equals(event.getTriggerSource())) { - handleQueryEvent(event); - return; - } - - // 1. 跳过与状态变更日志重复的审计事件(到岗确认/自动完成请求已由 CleanOrderEventListener 记录) - String auditType = event.getAuditType(); - if (LogType.BEACON_ARRIVE_CONFIRMED.getCode().equals(auditType) - || LogType.BEACON_COMPLETE_REQUESTED.getCode().equals(auditType)) { - log.debug("[CleanOrderAuditEventHandler] 跳过重复审计事件: eventId={}, auditType={}", - event.getEventId(), auditType); - return; - } - - // 2. 确定日志级别和域 - EventDomain domain = determineDomain(auditType); - EventLevel level = determineLevel(auditType); - LogType logType = auditType != null ? LogType.getByCode(auditType) : null; - String eventType = logType != null ? logType.getCode() : (auditType != null ? auditType : "AUDIT"); - - // 3. 记录审计日志 - eventLogRecorder.record( - EventLogRecord.builder() - .module(LogModule.CLEAN) - .domain(domain) - .eventType(eventType) - .message(event.getMessage()) - .targetId(event.getOrderId()) - .targetType(event.getOrderId() != null ? "order" : null) - .deviceId(event.getDeviceId()) - .level(level) - .build() - ); - - log.debug("[CleanOrderAuditEventHandler] 审计日志已记录: eventId={}, auditType={}", - event.getEventId(), auditType); - - // 2. 如果是 TTS 请求,调用 IoT 模块下发语音 - if (LogType.TTS_REQUEST.getCode().equals(auditType) && event.getDeviceId() != null) { - handleTtsRequest(event); - } - } - - /** - * 处理 TTS 请求 - *

- * 调用 IoT 模块的设备控制接口,下发语音播报到工牌设备 - * - * @param event 审计事件 - */ - private void handleTtsRequest(CleanOrderAuditEventDTO event) { - // 1. 从审计数据中提取 TTS 文本 - String ttsText = null; - if (event.getData() != null && event.getData().containsKey("tts")) { - ttsText = (String) event.getData().get("tts"); - } - - if (ttsText == null || ttsText.isEmpty()) { - log.warn("[CleanOrderAuditEventHandler] TTS 文本为空,跳过下发: eventId={}", event.getEventId()); - return; - } - - sendTts(event.getDeviceId(), ttsText, event.getOrderId()); - } - - /** - * 处理查询事件 (IOT_BUTTON_QUERY) - *

- * 查询事件仅播报当前工单地点信息,不涉及循环播报停止和状态流转。 - * 循环播报的停止由确认路径(CleanOrderEventListener.handleConfirmed)负责。 - */ - private void handleQueryEvent(CleanOrderAuditEventDTO event) { - log.info("[CleanOrderAuditEventHandler] Handling query event: eventId={}", event.getEventId()); - - Long deviceId = event.getDeviceId(); - if (deviceId == null) { - log.warn("[CleanOrderAuditEventHandler] Query event missing deviceId: eventId={}", event.getEventId()); - return; - } - - // 1. 获取当前正在处理的工单(DISPATCHED, CONFIRMED, ARRIVED 状态) - String currentAreaName = null; - OpsOrderDO currentOrder = opsOrderMapper.selectOne(new LambdaQueryWrapperX() - .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) - .in(OpsOrderDO::getStatus, Arrays.asList( - WorkOrderStatusEnum.DISPATCHED.getStatus(), - WorkOrderStatusEnum.CONFIRMED.getStatus(), - WorkOrderStatusEnum.ARRIVED.getStatus())) - .orderByAsc(OpsOrderDO::getId) - .last("LIMIT 1")); - - if (currentOrder != null && currentOrder.getLocation() != null) { - currentAreaName = currentOrder.getLocation(); - } - - // 2. 查询待办工单数量(QUEUED 状态,不含当前处理中工单) - Long pendingCount = opsOrderMapper.selectCount(new LambdaQueryWrapperX() - .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) - .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.QUEUED.getStatus())); - - // 3. 构建 TTS 文本(使用统一模板构建器) - String ttsText = CleanNotificationConstants.VoiceBuilder.buildQuery(currentAreaName, pendingCount.intValue()); - - // 4. 直接下发 TTS(按键响应需立即播报,不走队列) - Long orderId = currentOrder != null ? currentOrder.getId() : null; - sendTts(deviceId, ttsText, orderId); - } - - /** - * 下发 TTS 语音播报(直接发送,不走队列,按键响应需立即播报) - */ - private void sendTts(Long deviceId, String text, Long orderId) { - try { - voiceBroadcastService.broadcastDirect(deviceId, text, - TtsQueueMessage.TTS_FLAG_URGENT, orderId); - log.info("[CleanOrderAuditEventHandler] TTS 直接下发成功: deviceId={}, text={}", deviceId, text); - } catch (Exception e) { - log.error("[CleanOrderAuditEventHandler] TTS 直接下发异常: deviceId={}", deviceId, e); - } - } - - /** - * 确定事件域 - */ - private EventDomain determineDomain(String auditType) { - if (auditType == null) { - return EventDomain.SYSTEM; - } - if (auditType.startsWith("BEACON_") || auditType.contains("BEACON")) { - return EventDomain.BEACON; - } else if (LogType.TTS_REQUEST.getCode().equals(auditType)) { - return EventDomain.DEVICE; - } else { - return EventDomain.AUDIT; - } - } - - /** - * 确定日志级别 - */ - private EventLevel determineLevel(String auditType) { - if (auditType != null && (auditType.endsWith("_WARNING") || - auditType.endsWith("_SUPPRESSED") || - auditType.endsWith("_REJECTED"))) { - return EventLevel.WARN; - } - return EventLevel.INFO; + Boolean firstTime = stringRedisTemplate.opsForValue() + .setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS); + + if (!firstTime) { + log.debug("[CleanOrderAuditEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); + return; + } + + // 3. 业务处理 + executeInTenantContext(event.getTenantId(), () -> projectContextExecutor.execute( + event.getProjectId(), event.getOrderId(), event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleAuditEvent(event))); + + } catch (Exception e) { + log.error("[CleanOrderAuditEventHandler] 消息处理失败: message={}", message, e); + // 审计日志失败不抛出异常,避免影响主流程 + } + } + + /** + * 处理审计事件 + */ + private void handleAuditEvent(CleanOrderAuditEventDTO event) { + log.debug("[CleanOrderAuditEventHandler] 收到审计事件: eventId={}, auditType={}, message={}", + event.getEventId(), event.getAuditType(), event.getMessage()); + + if (TRIGGER_SOURCE_QUERY.equals(event.getTriggerSource())) { + handleQueryEvent(event); + return; + } + + // 1. 跳过与状态变更日志重复的审计事件(到岗确认/自动完成请求已由 CleanOrderEventListener 记录) + String auditType = event.getAuditType(); + if (LogType.BEACON_ARRIVE_CONFIRMED.getCode().equals(auditType) + || LogType.BEACON_COMPLETE_REQUESTED.getCode().equals(auditType)) { + log.debug("[CleanOrderAuditEventHandler] 跳过重复审计事件: eventId={}, auditType={}", + event.getEventId(), auditType); + return; + } + + // 2. 确定日志级别和域 + EventDomain domain = determineDomain(auditType); + EventLevel level = determineLevel(auditType); + LogType logType = auditType != null ? LogType.getByCode(auditType) : null; + String eventType = logType != null ? logType.getCode() : (auditType != null ? auditType : "AUDIT"); + + // 3. 记录审计日志 + eventLogRecorder.record( + EventLogRecord.builder() + .module(LogModule.CLEAN) + .domain(domain) + .eventType(eventType) + .message(event.getMessage()) + .targetId(event.getOrderId()) + .targetType(event.getOrderId() != null ? "order" : null) + .deviceId(event.getDeviceId()) + .level(level) + .build() + ); + + log.debug("[CleanOrderAuditEventHandler] 审计日志已记录: eventId={}, auditType={}", + event.getEventId(), auditType); + + // 2. 如果是 TTS 请求,调用 IoT 模块下发语音 + if (LogType.TTS_REQUEST.getCode().equals(auditType) && event.getDeviceId() != null) { + handleTtsRequest(event); + } + } + + /** + * 处理 TTS 请求 + *

+ * 调用 IoT 模块的设备控制接口,下发语音播报到工牌设备 + * + * @param event 审计事件 + */ + private void handleTtsRequest(CleanOrderAuditEventDTO event) { + // 1. 从审计数据中提取 TTS 文本 + String ttsText = null; + if (event.getData() != null && event.getData().containsKey("tts")) { + ttsText = (String) event.getData().get("tts"); + } + + if (ttsText == null || ttsText.isEmpty()) { + log.warn("[CleanOrderAuditEventHandler] TTS 文本为空,跳过下发: eventId={}", event.getEventId()); + return; + } + + sendTts(event.getDeviceId(), ttsText, event.getOrderId()); + } + + /** + * 处理查询事件 (IOT_BUTTON_QUERY) + *

+ * 查询事件仅播报当前工单地点信息,不涉及循环播报停止和状态流转。 + * 循环播报的停止由确认路径(CleanOrderEventListener.handleConfirmed)负责。 + */ + private void handleQueryEvent(CleanOrderAuditEventDTO event) { + log.info("[CleanOrderAuditEventHandler] Handling query event: eventId={}", event.getEventId()); + + Long deviceId = event.getDeviceId(); + if (deviceId == null) { + log.warn("[CleanOrderAuditEventHandler] Query event missing deviceId: eventId={}", event.getEventId()); + return; + } + + // 1. 获取当前正在处理的工单(DISPATCHED, CONFIRMED, ARRIVED 状态) + String currentAreaName = null; + OpsOrderDO currentOrder = opsOrderMapper.selectOne(new LambdaQueryWrapperX() + .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) + .in(OpsOrderDO::getStatus, Arrays.asList( + WorkOrderStatusEnum.DISPATCHED.getStatus(), + WorkOrderStatusEnum.CONFIRMED.getStatus(), + WorkOrderStatusEnum.ARRIVED.getStatus())) + .orderByAsc(OpsOrderDO::getId) + .last("LIMIT 1")); + + if (currentOrder != null && currentOrder.getLocation() != null) { + currentAreaName = currentOrder.getLocation(); + } + + // 2. 查询待办工单数量(QUEUED 状态,不含当前处理中工单) + Long pendingCount = opsOrderMapper.selectCount(new LambdaQueryWrapperX() + .eq(OpsOrderDO::getAssigneeDeviceId, deviceId) + .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.QUEUED.getStatus())); + + // 3. 构建 TTS 文本(使用统一模板构建器) + String ttsText = CleanNotificationConstants.VoiceBuilder.buildQuery(currentAreaName, pendingCount.intValue()); + + // 4. 直接下发 TTS(按键响应需立即播报,不走队列) + Long orderId = currentOrder != null ? currentOrder.getId() : null; + sendTts(deviceId, ttsText, orderId); + } + + /** + * 下发 TTS 语音播报(直接发送,不走队列,按键响应需立即播报) + */ + private void sendTts(Long deviceId, String text, Long orderId) { + try { + voiceBroadcastService.broadcastDirect(deviceId, text, + TtsQueueMessage.TTS_FLAG_URGENT, orderId); + log.info("[CleanOrderAuditEventHandler] TTS 直接下发成功: deviceId={}, text={}", deviceId, text); + } catch (Exception e) { + log.error("[CleanOrderAuditEventHandler] TTS 直接下发异常: deviceId={}", deviceId, e); + } + } + + /** + * 确定事件域 + */ + private EventDomain determineDomain(String auditType) { + if (auditType == null) { + return EventDomain.SYSTEM; + } + if (auditType.startsWith("BEACON_") || auditType.contains("BEACON")) { + return EventDomain.BEACON; + } else if (LogType.TTS_REQUEST.getCode().equals(auditType)) { + return EventDomain.DEVICE; + } else { + return EventDomain.AUDIT; + } + } + + /** + * 确定日志级别 + */ + private EventLevel determineLevel(String auditType) { + if (auditType != null && (auditType.endsWith("_WARNING") || + auditType.endsWith("_SUPPRESSED") || + auditType.endsWith("_REJECTED"))) { + return EventLevel.WARN; + } + return EventLevel.INFO; } private void executeInTenantContext(Long tenantId, Runnable runnable) { diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCompleteEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCompleteEventHandler.java index ffc85294..cf71c533 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCompleteEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderCompleteEventHandler.java @@ -50,6 +50,9 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener @Resource private OrderLifecycleManager orderLifecycleManager; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -61,7 +64,9 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener log.debug("[CleanOrderCompleteEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); return; } - executeInTenantContext(event.getTenantId(), () -> handleOrderComplete(event)); + executeInTenantContext(event.getTenantId(), () -> projectContextExecutor.execute( + event.getProjectId(), event.getOrderId(), event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleOrderComplete(event))); } catch (Exception e) { log.error("[CleanOrderCompleteEventHandler] 消息处理失败: message={}", message, e); throw new RuntimeException("保洁工单完单事件处理失败", e); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java index ddde5bfa..af802d87 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/CleanOrderConfirmEventHandler.java @@ -47,6 +47,9 @@ public class CleanOrderConfirmEventHandler implements RocketMQListener { @Resource private OrderLifecycleManager orderLifecycleManager; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -58,7 +61,9 @@ public class CleanOrderConfirmEventHandler implements RocketMQListener { log.debug("[CleanOrderConfirmEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); return; } - executeInTenantContext(event.getTenantId(), () -> handleConfirmEvent(event)); + executeInTenantContext(event.getTenantId(), () -> projectContextExecutor.execute( + event.getProjectId(), event.getOrderId(), event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleConfirmEvent(event))); } catch (Exception e) { log.error("[CleanOrderConfirmEventHandler] 消息处理失败: message={}", message, e); throw new RuntimeException("保洁工单确认事件处理失败", e); 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 96ac616d..6795892d 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,11 +1,15 @@ package com.viewsh.module.ops.environment.integration.consumer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.viewsh.framework.tenant.core.context.ProjectContextHolder; import com.viewsh.framework.tenant.core.context.TenantContextHolder; +import com.viewsh.framework.tenant.core.util.ProjectUtils; import com.viewsh.framework.tenant.core.util.TenantUtils; import com.viewsh.module.iot.api.device.IotDeviceControlApi; import com.viewsh.module.iot.api.device.dto.ResetTrafficCounterReqDTO; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; import com.viewsh.module.ops.enums.PriorityEnum; import com.viewsh.module.ops.enums.WorkOrderStatusEnum; @@ -103,6 +107,9 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { @Resource private OpsOrderMapper opsOrderMapper; + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + @Override public void onMessage(String message) { try { @@ -120,7 +127,8 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { } // 3. 业务处理 - executeInTenantContext(event.getTenantId(), () -> handleOrderCreate(event)); + executeInTenantContext(event.getTenantId(), () -> + executeInProjectContext(event, () -> handleOrderCreate(event))); } catch (Exception e) { log.error("[CleanOrderCreateEventHandler] 消息处理失败: message={}", message, e); @@ -538,6 +546,41 @@ public class CleanOrderCreateEventHandler implements RocketMQListener { return null; } + private void executeInProjectContext(CleanOrderCreateEventDTO event, Runnable runnable) { + Long projectId = resolveProjectId(event); + Long currentProjectId = ProjectContextHolder.getProjectId(); + if (projectId == null) { + throw new IllegalStateException("Clean order create event missing projectId, eventId=" + + event.getEventId() + ", areaId=" + event.getAreaId()); + } + if (Objects.equals(currentProjectId, projectId)) { + runnable.run(); + return; + } + ProjectUtils.execute(projectId, runnable); + } + + private Long resolveProjectId(CleanOrderCreateEventDTO event) { + if (event.getProjectId() != null) { + return event.getProjectId(); + } + Long currentProjectId = ProjectContextHolder.getProjectId(); + if (currentProjectId != null) { + return currentProjectId; + } + Long areaId = event.getAreaId(); + if (areaId == null) { + return null; + } + OpsBusAreaDO area = ProjectUtils.executeIgnore(() -> opsBusAreaMapper.selectById(areaId)); + if (area == null) { + log.warn("[CleanOrderCreateEventHandler] area not found when resolving projectId: eventId={}, areaId={}", + event.getEventId(), areaId); + return null; + } + return area.getProjectId(); + } + private void executeInTenantContext(Long tenantId, Runnable runnable) { Long currentTenantId = TenantContextHolder.getTenantId(); if (tenantId == null || Objects.equals(currentTenantId, tenantId)) { diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/IntegrationProjectContextExecutor.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/IntegrationProjectContextExecutor.java new file mode 100644 index 00000000..bc670efb --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/IntegrationProjectContextExecutor.java @@ -0,0 +1,91 @@ +package com.viewsh.module.ops.environment.integration.consumer; + +import com.viewsh.framework.tenant.core.context.ProjectContextHolder; +import com.viewsh.framework.tenant.core.util.ProjectUtils; +import com.viewsh.module.ops.dal.dataobject.area.OpsAreaDeviceRelationDO; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.area.OpsAreaDeviceRelationMapper; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +@Slf4j +@Component +public class IntegrationProjectContextExecutor { + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + + @Resource + private OpsAreaDeviceRelationMapper opsAreaDeviceRelationMapper; + + public void execute(Long projectId, Long orderId, Long areaId, Long deviceId, String eventId, Runnable runnable) { + Long resolvedProjectId = resolveProjectId(projectId, orderId, areaId, deviceId, eventId); + Long currentProjectId = ProjectContextHolder.getProjectId(); + if (resolvedProjectId == null) { + throw new IllegalStateException("integration event missing projectId, eventId=" + eventId + + ", orderId=" + orderId + ", areaId=" + areaId + ", deviceId=" + deviceId); + } + if (Objects.equals(currentProjectId, resolvedProjectId)) { + runnable.run(); + return; + } + ProjectUtils.execute(resolvedProjectId, runnable); + } + + private Long resolveProjectId(Long projectId, Long orderId, Long areaId, Long deviceId, String eventId) { + if (projectId != null) { + return projectId; + } + Long currentProjectId = ProjectContextHolder.getProjectId(); + if (currentProjectId != null) { + return currentProjectId; + } + Long projectIdByOrder = resolveProjectIdByOrder(orderId); + if (projectIdByOrder != null) { + return projectIdByOrder; + } + Long projectIdByArea = resolveProjectIdByArea(areaId); + if (projectIdByArea != null) { + return projectIdByArea; + } + Long projectIdByDevice = resolveProjectIdByDevice(deviceId); + if (projectIdByDevice == null) { + log.warn("[IntegrationProjectContextExecutor] projectId not resolved: eventId={}, orderId={}, areaId={}, deviceId={}", + eventId, orderId, areaId, deviceId); + } + return projectIdByDevice; + } + + private Long resolveProjectIdByOrder(Long orderId) { + if (orderId == null) { + return null; + } + OpsOrderDO order = ProjectUtils.executeIgnore(() -> opsOrderMapper.selectById(orderId)); + return order != null ? order.getProjectId() : null; + } + + private Long resolveProjectIdByArea(Long areaId) { + if (areaId == null) { + return null; + } + OpsBusAreaDO area = ProjectUtils.executeIgnore(() -> opsBusAreaMapper.selectById(areaId)); + return area != null ? area.getProjectId() : null; + } + + private Long resolveProjectIdByDevice(Long deviceId) { + if (deviceId == null) { + return null; + } + OpsAreaDeviceRelationDO relation = ProjectUtils.executeIgnore(() -> opsAreaDeviceRelationMapper.selectByDeviceId(deviceId)); + return relation != null ? relation.getProjectId() : null; + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryEnterEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryEnterEventHandler.java index a1363dbb..3b68a3dc 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryEnterEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryEnterEventHandler.java @@ -42,6 +42,9 @@ public class TrajectoryEnterEventHandler implements RocketMQListener { @Resource private DeviceTrajectoryService trajectoryService; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -57,17 +60,8 @@ public class TrajectoryEnterEventHandler implements RocketMQListener { event.getEventId(), event.getDeviceId(), event.getAreaId()); // 解析事件时间 - LocalDateTime enterTime = parseEventTime(event.getEventTime()); - - // 创建轨迹记录 - trajectoryService.recordEnter( - event.getDeviceId(), - event.getDeviceName(), - event.getNickname(), - event.getAreaId(), - event.getBeaconMac(), - event.getEnterRssi(), - enterTime); + projectContextExecutor.execute(event.getProjectId(), null, event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleTrajectoryEnter(event)); } catch (Exception e) { log.error("[TrajectoryEnterHandler] 消息处理失败:message={}", message, e); @@ -75,6 +69,18 @@ public class TrajectoryEnterEventHandler implements RocketMQListener { } } + private void handleTrajectoryEnter(TrajectoryEnterEventDTO event) { + LocalDateTime enterTime = parseEventTime(event.getEventTime()); + trajectoryService.recordEnter( + event.getDeviceId(), + event.getDeviceName(), + event.getNickname(), + event.getAreaId(), + event.getBeaconMac(), + event.getEnterRssi(), + enterTime); + } + private LocalDateTime parseEventTime(String eventTime) { if (eventTime == null || eventTime.isEmpty()) { return LocalDateTime.now(); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryLeaveEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryLeaveEventHandler.java index 6499c882..25565328 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryLeaveEventHandler.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/consumer/TrajectoryLeaveEventHandler.java @@ -42,6 +42,9 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener { @Resource private DeviceTrajectoryService trajectoryService; + @Resource + private IntegrationProjectContextExecutor projectContextExecutor; + @Override public void onMessage(String message) { try { @@ -57,15 +60,8 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener { event.getEventId(), event.getDeviceId(), event.getAreaId(), event.getLeaveReason()); // 解析事件时间 - LocalDateTime leaveTime = parseEventTime(event.getEventTime()); - - // 更新轨迹记录 - trajectoryService.recordLeave( - event.getDeviceId(), - event.getAreaId(), - event.getLeaveReason(), - event.getEnterTimestamp(), - leaveTime); + projectContextExecutor.execute(event.getProjectId(), null, event.getAreaId(), event.getDeviceId(), + event.getEventId(), () -> handleTrajectoryLeave(event)); } catch (Exception e) { log.error("[TrajectoryLeaveHandler] 消息处理失败:message={}", message, e); @@ -73,6 +69,16 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener { } } + private void handleTrajectoryLeave(TrajectoryLeaveEventDTO event) { + LocalDateTime leaveTime = parseEventTime(event.getEventTime()); + trajectoryService.recordLeave( + event.getDeviceId(), + event.getAreaId(), + event.getLeaveReason(), + event.getEnterTimestamp(), + leaveTime); + } + private LocalDateTime parseEventTime(String eventTime) { if (eventTime == null || eventTime.isEmpty()) { return LocalDateTime.now(); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/BaseDeviceEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/BaseDeviceEventDTO.java index 25b8069d..7861813f 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/BaseDeviceEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/BaseDeviceEventDTO.java @@ -51,6 +51,9 @@ public class BaseDeviceEventDTO { @JsonProperty("tenantId") private Long tenantId; + @JsonProperty("projectId") + private Long projectId; + /** * 事件时间 */ diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderArriveEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderArriveEventDTO.java index 7750ed9b..92fa7931 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderArriveEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderArriveEventDTO.java @@ -1,66 +1,68 @@ -package com.viewsh.module.ops.environment.integration.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -/** - * 保洁工单到岗事件 DTO - *

- * 由 IoT 模块发布,Ops 模块消费 - * - * @author AI - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CleanOrderArriveEventDTO { - - /** - * 事件ID(UUID,用于幂等性控制) - */ - private String eventId; - - /** - * 工单类型(CLEAN=保洁) - */ - private String orderType; - - /** - * 工单ID - */ - private Long orderId; - - /** - * 设备ID(保洁员工牌设备ID) - */ - private Long deviceId; - - /** - * 设备Key - */ - private String deviceKey; - - /** - * 区域ID - */ +package com.viewsh.module.ops.environment.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 保洁工单到岗事件 DTO + *

+ * 由 IoT 模块发布,Ops 模块消费 + * + * @author AI + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CleanOrderArriveEventDTO { + + /** + * 事件ID(UUID,用于幂等性控制) + */ + private String eventId; + + /** + * 工单类型(CLEAN=保洁) + */ + private String orderType; + + /** + * 工单ID + */ + private Long orderId; + + /** + * 设备ID(保洁员工牌设备ID) + */ + private Long deviceId; + + /** + * 设备Key + */ + private String deviceKey; + + /** + * 区域ID + */ private Long areaId; private Long tenantId; - - /** - * 触发来源(IOT_BEACON=蓝牙信标检测) - */ - private String triggerSource; - - /** - * 触发数据(JSON 格式的附加信息) - *

- * 示例:{"beaconMac":"F0:C8:60:1D:10:BB","rssi":-65,"windowSnapshot":[-70,-68,-65,-64,-66]} - */ - private Map triggerData; -} + + private Long projectId; + + /** + * 触发来源(IOT_BEACON=蓝牙信标检测) + */ + private String triggerSource; + + /** + * 触发数据(JSON 格式的附加信息) + *

+ * 示例:{"beaconMac":"F0:C8:60:1D:10:BB","rssi":-65,"windowSnapshot":[-70,-68,-65,-64,-66]} + */ + private Map triggerData; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java index 8075a035..f029a5f5 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java @@ -1,37 +1,37 @@ -package com.viewsh.module.ops.environment.integration.dto; - +package com.viewsh.module.ops.environment.integration.dto; + import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -/** - * 保洁工单审计事件 DTO - *

- * 由 IoT 模块发布,Ops 模块消费 - * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) - * - * @author AI - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CleanOrderAuditEventDTO { - - /** - * 事件ID(UUID,用于幂等性控制) - */ - private String eventId; - - /** - * 工单ID(可选,部分审计事件可能没有工单ID) - */ - private Long orderId; - +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 保洁工单审计事件 DTO + *

+ * 由 IoT 模块发布,Ops 模块消费 + * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) + * + * @author AI + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CleanOrderAuditEventDTO { + + /** + * 事件ID(UUID,用于幂等性控制) + */ + private String eventId; + + /** + * 工单ID(可选,部分审计事件可能没有工单ID) + */ + private Long orderId; + /** * 触发来源 (如 IOT_BUTTON_QUERY) */ @@ -40,40 +40,42 @@ public class CleanOrderAuditEventDTO { /** * 审计类型 - *

- * - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认 - * - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送 - * - COMPLETE_SUPPRESSED_INVALID: 作业时长不足,抑制自动完成 - * - BEACON_COMPLETE_REQUESTED: 信号丢失超时自动完成请求 - * - TTS_REQUEST: TTS 语音播报请求 - * - ARRIVE_REJECTED: 到岗请求被拒绝(状态不符) - */ - private String auditType; - - /** - * 设备ID - */ - private Long deviceId; - - /** - * 设备Key - */ - private String deviceKey; - - /** - * 区域ID - */ + *

+ * - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认 + * - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送 + * - COMPLETE_SUPPRESSED_INVALID: 作业时长不足,抑制自动完成 + * - BEACON_COMPLETE_REQUESTED: 信号丢失超时自动完成请求 + * - TTS_REQUEST: TTS 语音播报请求 + * - ARRIVE_REJECTED: 到岗请求被拒绝(状态不符) + */ + private String auditType; + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 设备Key + */ + private String deviceKey; + + /** + * 区域ID + */ private Long areaId; private Long tenantId; - - /** - * 消息内容 - */ - private String message; - - /** - * 审计数据(JSON 格式的附加信息) - */ - private Map data; -} + + private Long projectId; + + /** + * 消息内容 + */ + private String message; + + /** + * 审计数据(JSON 格式的附加信息) + */ + private Map data; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCompleteEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCompleteEventDTO.java index 56fbbc48..4fb1ab8b 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCompleteEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCompleteEventDTO.java @@ -1,66 +1,68 @@ -package com.viewsh.module.ops.environment.integration.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -/** - * 保洁工单完成事件 DTO - *

- * 由 IoT 模块发布,Ops 模块消费 - * - * @author AI - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CleanOrderCompleteEventDTO { - - /** - * 事件ID(UUID,用于幂等性控制) - */ - private String eventId; - - /** - * 工单类型(CLEAN=保洁) - */ - private String orderType; - - /** - * 工单ID - */ - private Long orderId; - - /** - * 设备ID(保洁员工牌设备ID) - */ - private Long deviceId; - - /** - * 设备Key - */ - private String deviceKey; - - /** - * 区域ID - */ +package com.viewsh.module.ops.environment.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 保洁工单完成事件 DTO + *

+ * 由 IoT 模块发布,Ops 模块消费 + * + * @author AI + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CleanOrderCompleteEventDTO { + + /** + * 事件ID(UUID,用于幂等性控制) + */ + private String eventId; + + /** + * 工单类型(CLEAN=保洁) + */ + private String orderType; + + /** + * 工单ID + */ + private Long orderId; + + /** + * 设备ID(保洁员工牌设备ID) + */ + private Long deviceId; + + /** + * 设备Key + */ + private String deviceKey; + + /** + * 区域ID + */ private Long areaId; private Long tenantId; - - /** - * 触发来源(IOT_SIGNAL_LOSS=信号丢失超时) - */ - private String triggerSource; - - /** - * 触发数据(JSON 格式的附加信息) - *

- * 示例:{"durationMs":1800000,"lastLossTime":1704067200000,"completionReason":"SIGNAL_LOSS_TIMEOUT"} - */ - private Map triggerData; -} + + private Long projectId; + + /** + * 触发来源(IOT_SIGNAL_LOSS=信号丢失超时) + */ + private String triggerSource; + + /** + * 触发数据(JSON 格式的附加信息) + *

+ * 示例:{"durationMs":1800000,"lastLossTime":1704067200000,"completionReason":"SIGNAL_LOSS_TIMEOUT"} + */ + private Map triggerData; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCreateEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCreateEventDTO.java index c4967777..eeccd102 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCreateEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderCreateEventDTO.java @@ -1,67 +1,72 @@ -package com.viewsh.module.ops.environment.integration.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -/** - * 保洁工单创建事件 DTO - *

- * 由 IoT 模块发布,Ops 模块消费 - * - * @author AI - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CleanOrderCreateEventDTO { - - /** - * 事件ID(UUID,用于幂等性控制) - */ - private String eventId; - - /** - * 工单类型(CLEAN=保洁) - */ - private String orderType; - - /** - * 区域ID - */ - private Long areaId; - - /** - * 触发来源(IOT_TRAFFIC=客流阈值/IOT_BEACON=蓝牙信标/IOT_SIGNAL_LOSS=信号丢失超时) - */ - private String triggerSource; - - /** - * 触发设备ID - */ - private Long triggerDeviceId; - - /** - * 触发设备Key - */ - private String triggerDeviceKey; - - /** - * 优先级(0=P0紧急 1=P1重要 2=P2普通) - */ +package com.viewsh.module.ops.environment.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 保洁工单创建事件 DTO + *

+ * 由 IoT 模块发布,Ops 模块消费 + * + * @author AI + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CleanOrderCreateEventDTO { + + /** + * 事件ID(UUID,用于幂等性控制) + */ + private String eventId; + + /** + * 工单类型(CLEAN=保洁) + */ + private String orderType; + + /** + * 区域ID + */ + private Long areaId; + + /** + * 触发来源(IOT_TRAFFIC=客流阈值/IOT_BEACON=蓝牙信标/IOT_SIGNAL_LOSS=信号丢失超时) + */ + private String triggerSource; + + /** + * 触发设备ID + */ + private Long triggerDeviceId; + + /** + * 触发设备Key + */ + private String triggerDeviceKey; + + /** + * 优先级(0=P0紧急 1=P1重要 2=P2普通) + */ private Integer priority; private Long tenantId; - - /** - * 触发数据(JSON 格式的附加信息) - *

- * 客流阈值触发示例:{"actualCount":150,"baseValue":1000,"threshold":100,"exceededCount":50} - * 信标检测触发示例:{"rssi":-65,"beaconMac":"F0:C8:60:1D:10:BB"} - */ - private Map triggerData; -} + + /** + * Project id. + */ + private Long projectId; + + /** + * 触发数据(JSON 格式的附加信息) + *

+ * 客流阈值触发示例:{"actualCount":150,"baseValue":1000,"threshold":100,"exceededCount":50} + * 信标检测触发示例:{"rssi":-65,"beaconMac":"F0:C8:60:1D:10:BB"} + */ + private Map triggerData; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/IotDeviceStatusChangedEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/IotDeviceStatusChangedEventDTO.java index 64c4fdc0..38e6b519 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/IotDeviceStatusChangedEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/IotDeviceStatusChangedEventDTO.java @@ -1,109 +1,111 @@ -package com.viewsh.module.ops.environment.integration.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -/** - * IoT 设备状态变更事件 DTO - *

- * 用于接收 IoT 模块发布的设备状态变更事件 - * 解耦 Ops 模块与 iot-core 的依赖 - * - * @author AI - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class IotDeviceStatusChangedEventDTO { - - /** - * 事件ID(唯一标识,用于幂等性处理) - */ - private String eventId; - - /** - * 设备ID - */ - private Long deviceId; - - /** - * 设备名称(deviceName) - */ - private String deviceName; - - /** - * 设备昵称(nickname,用户可读的显示名称) - */ - private String nickname; - - /** - * 产品ID - */ - private Long productId; - - /** - * 产品标识符(productKey) - */ - private String productKey; - - /** - * 租户ID - */ - private Long tenantId; - - /** - * 事件时间 - */ - private LocalDateTime eventTime; - - /** - * 旧状态(0=INACTIVE, 1=ONLINE, 2=OFFLINE) - */ - private Integer oldStatus; - - /** - * 新状态(0=INACTIVE, 1=ONLINE, 2=OFFLINE) - */ - private Integer newStatus; - - /** - * 状态变更原因 - */ - private String reason; - - // ==================== 状态常量 ==================== - - /** - * 设备状态:在线 - */ - public static final Integer STATUS_ONLINE = 1; - - /** - * 设备状态:离线 - */ - public static final Integer STATUS_OFFLINE = 2; - - /** - * 设备状态:未激活 - */ - public static final Integer STATUS_INACTIVE = 0; - - /** - * 判断是否上线 - */ - public boolean isOnline() { - return STATUS_ONLINE.equals(newStatus); - } - - /** - * 判断是否离线 - */ - public boolean isOffline() { - return STATUS_OFFLINE.equals(newStatus); - } -} +package com.viewsh.module.ops.environment.integration.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * IoT 设备状态变更事件 DTO + *

+ * 用于接收 IoT 模块发布的设备状态变更事件 + * 解耦 Ops 模块与 iot-core 的依赖 + * + * @author AI + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class IotDeviceStatusChangedEventDTO { + + /** + * 事件ID(唯一标识,用于幂等性处理) + */ + private String eventId; + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 设备名称(deviceName) + */ + private String deviceName; + + /** + * 设备昵称(nickname,用户可读的显示名称) + */ + private String nickname; + + /** + * 产品ID + */ + private Long productId; + + /** + * 产品标识符(productKey) + */ + private String productKey; + + /** + * 租户ID + */ + private Long tenantId; + + private Long projectId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + /** + * 旧状态(0=INACTIVE, 1=ONLINE, 2=OFFLINE) + */ + private Integer oldStatus; + + /** + * 新状态(0=INACTIVE, 1=ONLINE, 2=OFFLINE) + */ + private Integer newStatus; + + /** + * 状态变更原因 + */ + private String reason; + + // ==================== 状态常量 ==================== + + /** + * 设备状态:在线 + */ + public static final Integer STATUS_ONLINE = 1; + + /** + * 设备状态:离线 + */ + public static final Integer STATUS_OFFLINE = 2; + + /** + * 设备状态:未激活 + */ + public static final Integer STATUS_INACTIVE = 0; + + /** + * 判断是否上线 + */ + public boolean isOnline() { + return STATUS_ONLINE.equals(newStatus); + } + + /** + * 判断是否离线 + */ + public boolean isOffline() { + return STATUS_OFFLINE.equals(newStatus); + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryEnterEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryEnterEventDTO.java index 26ada6f1..c83d6905 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryEnterEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryEnterEventDTO.java @@ -62,4 +62,6 @@ public class TrajectoryEnterEventDTO { * 租户ID */ private Long tenantId; + + private Long projectId; } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryLeaveEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryLeaveEventDTO.java index 76fb87c8..ff211d3b 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryLeaveEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/TrajectoryLeaveEventDTO.java @@ -69,4 +69,6 @@ public class TrajectoryLeaveEventDTO { * 租户ID */ private Long tenantId; + + private Long projectId; }