fix(ops): 恢复集成消费项目上下文

This commit is contained in:
lzh
2026-04-29 22:21:02 +08:00
parent 026a126824
commit f080cff7b7
17 changed files with 842 additions and 666 deletions

View File

@@ -46,6 +46,9 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener<String> {
@Resource @Resource
private AreaDeviceService areaDeviceService; private AreaDeviceService areaDeviceService;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -66,12 +69,16 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener<String> {
} }
private void handleDeviceStatusChange(IotDeviceStatusChangedEventDTO event) { private void handleDeviceStatusChange(IotDeviceStatusChangedEventDTO event) {
if (!isBadgeDevice(event.getDeviceId())) { OpsAreaDeviceRelationDO relation = getBadgeRelation(event.getDeviceId());
log.debug("[BadgeDeviceStatusEventHandler] 非工牌设备,忽略状态同步: deviceId={}", event.getDeviceId()); if (relation == null) {
log.debug("[BadgeDeviceStatusEventHandler] ignore non-badge device status event: deviceId={}", event.getDeviceId());
return; 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()) { if (event.isOnline()) {
badgeDeviceStatusService.updateBadgeOnlineStatus( badgeDeviceStatusService.updateBadgeOnlineStatus(
event.getDeviceId(), event.getDeviceName(), event.getNickname(), areaId, event.getDeviceId(), event.getDeviceName(), event.getNickname(), areaId,
@@ -84,39 +91,22 @@ public class BadgeDeviceStatusEventHandler implements RocketMQListener<String> {
BadgeDeviceStatusEnum.OFFLINE, "设备离线"); BadgeDeviceStatusEnum.OFFLINE, "设备离线");
return; return;
} }
log.debug("[BadgeDeviceStatusEventHandler] 忽略未处理的状态变更: deviceId={}, newStatus={}", log.debug("[BadgeDeviceStatusEventHandler] ignore device status event: deviceId={}, newStatus={}",
event.getDeviceId(), event.getNewStatus()); event.getDeviceId(), event.getNewStatus());
} }
private boolean isBadgeDevice(Long deviceId) { private OpsAreaDeviceRelationDO getBadgeRelation(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) {
if (deviceId == null) { if (deviceId == null) {
return null; return null;
} }
try { try {
OpsAreaDeviceRelationDO relation = areaDeviceService.getByDeviceId(deviceId); OpsAreaDeviceRelationDO relation = areaDeviceService.getByDeviceId(deviceId);
if (relation != null && "BADGE".equals(relation.getRelationType())) { return relation != null && "BADGE".equals(relation.getRelationType()) ? relation : null;
return relation.getAreaId();
}
return null;
} catch (Exception e) { } catch (Exception e) {
log.warn("[BadgeDeviceStatusEventHandler] 查询设备所属区域失败: deviceId={}", deviceId, e); log.warn("[BadgeDeviceStatusEventHandler] query badge relation failed: deviceId={}", deviceId, e);
return null; return null;
} }
} }
private void executeInTenantContext(Long tenantId, Runnable runnable) { private void executeInTenantContext(Long tenantId, Runnable runnable) {
Long currentTenantId = TenantContextHolder.getTenantId(); Long currentTenantId = TenantContextHolder.getTenantId();
if (tenantId == null || Objects.equals(currentTenantId, tenantId)) { if (tenantId == null || Objects.equals(currentTenantId, tenantId)) {

View File

@@ -50,6 +50,9 @@ public class CleanOrderArriveEventHandler implements RocketMQListener<String> {
@Resource @Resource
private OrderLifecycleManager orderLifecycleManager; private OrderLifecycleManager orderLifecycleManager;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -61,7 +64,9 @@ public class CleanOrderArriveEventHandler implements RocketMQListener<String> {
log.debug("[CleanOrderArriveEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); log.debug("[CleanOrderArriveEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId());
return; 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) { } catch (Exception e) {
log.error("[CleanOrderArriveEventHandler] 消息处理失败: message={}", message, e); log.error("[CleanOrderArriveEventHandler] 消息处理失败: message={}", message, e);
throw new RuntimeException("保洁工单到岗事件处理失败", e); throw new RuntimeException("保洁工单到岗事件处理失败", e);

View File

@@ -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.fasterxml.jackson.databind.ObjectMapper;
import com.viewsh.framework.tenant.core.context.TenantContextHolder; import com.viewsh.framework.tenant.core.context.TenantContextHolder;
import com.viewsh.framework.tenant.core.util.TenantUtils; import com.viewsh.framework.tenant.core.util.TenantUtils;
import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum; import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.environment.constants.CleanNotificationConstants; import com.viewsh.module.ops.environment.constants.CleanNotificationConstants;
import com.viewsh.module.ops.environment.integration.dto.CleanOrderAuditEventDTO; 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.TtsQueueMessage;
import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService; 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.EventDomain;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; 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.LogModule;
import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; 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.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import com.viewsh.module.ops.infrastructure.redis.OpsRedisKeyBuilder; import com.viewsh.module.ops.infrastructure.redis.OpsRedisKeyBuilder;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode; import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener; import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 保洁工单审计事件消费者 * 保洁工单审计事件消费者
* <p> * <p>
* 订阅 IoT 模块发布的保洁工单审计事件 * 订阅 IoT 模块发布的保洁工单审计事件
* 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等)
* <p> * <p>
* RocketMQ 配置: * RocketMQ 配置:
* - Topic: ops-order-audit * - Topic: ops-order-audit
* - ConsumerGroup: ops-clean-order-audit-group * - ConsumerGroup: ops-clean-order-audit-group
* *
* @author AI * @author AI
*/ */
@Slf4j @Slf4j
@Component @Component
@RocketMQMessageListener( @RocketMQMessageListener(
topic = "ops-order-audit", topic = "ops-order-audit",
consumerGroup = "ops-clean-order-audit-group", consumerGroup = "ops-clean-order-audit-group",
consumeMode = ConsumeMode.CONCURRENTLY, consumeMode = ConsumeMode.CONCURRENTLY,
selectorExpression = "*", selectorExpression = "*",
accessKey = "${rocketmq.consumer.access-key:}", accessKey = "${rocketmq.consumer.access-key:}",
secretKey = "${rocketmq.consumer.secret-key:}" secretKey = "${rocketmq.consumer.secret-key:}"
) )
public class CleanOrderAuditEventHandler implements RocketMQListener<String> { public class CleanOrderAuditEventHandler implements RocketMQListener<String> {
/** /**
* 幂等性控制 Key 模式 * 幂等性控制 Key 模式
*/ */
private static final String DEDUP_KEY_PATTERN = "ops:clean:dedup:audit:%s"; private static final String DEDUP_KEY_PATTERN = "ops:clean:dedup:audit:%s";
/** /**
* 幂等性控制 TTL * 幂等性控制 TTL
*/ */
private static final int DEDUP_TTL_SECONDS = 300; private static final int DEDUP_TTL_SECONDS = 300;
private static final String TRIGGER_SOURCE_QUERY = "IOT_BUTTON_QUERY"; private static final String TRIGGER_SOURCE_QUERY = "IOT_BUTTON_QUERY";
@Resource @Resource
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@Resource @Resource
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Resource @Resource
private EventLogRecorder eventLogRecorder; private EventLogRecorder eventLogRecorder;
@Resource @Resource
private VoiceBroadcastService voiceBroadcastService; private VoiceBroadcastService voiceBroadcastService;
@Resource @Resource
private OpsOrderMapper opsOrderMapper; private OpsOrderMapper opsOrderMapper;
@Override @Resource
public void onMessage(String message) { private IntegrationProjectContextExecutor projectContextExecutor;
try {
// 1. JSON 反序列化 @Override
CleanOrderAuditEventDTO event = objectMapper.readValue(message, CleanOrderAuditEventDTO.class); public void onMessage(String message) {
try {
// 2. 幂等性检查 // 1. JSON 反序列化
CleanOrderAuditEventDTO event = objectMapper.readValue(message, CleanOrderAuditEventDTO.class);
// 2. 幂等性检查
String dedupKey = OpsRedisKeyBuilder.eventDedup(event.getTenantId(), "audit", event.getEventId()); String dedupKey = OpsRedisKeyBuilder.eventDedup(event.getTenantId(), "audit", event.getEventId());
Boolean firstTime = stringRedisTemplate.opsForValue() Boolean firstTime = stringRedisTemplate.opsForValue()
.setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS); .setIfAbsent(dedupKey, "1", DEDUP_TTL_SECONDS, TimeUnit.SECONDS);
if (!firstTime) { if (!firstTime) {
log.debug("[CleanOrderAuditEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); log.debug("[CleanOrderAuditEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId());
return; return;
} }
// 3. 业务处理 // 3. 业务处理
executeInTenantContext(event.getTenantId(), () -> handleAuditEvent(event)); executeInTenantContext(event.getTenantId(), () -> projectContextExecutor.execute(
event.getProjectId(), event.getOrderId(), event.getAreaId(), event.getDeviceId(),
} catch (Exception e) { event.getEventId(), () -> handleAuditEvent(event)));
log.error("[CleanOrderAuditEventHandler] 消息处理失败: message={}", message, e);
// 审计日志失败不抛出异常,避免影响主流程 } 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()); private void handleAuditEvent(CleanOrderAuditEventDTO event) {
log.debug("[CleanOrderAuditEventHandler] 收到审计事件: eventId={}, auditType={}, message={}",
if (TRIGGER_SOURCE_QUERY.equals(event.getTriggerSource())) { event.getEventId(), event.getAuditType(), event.getMessage());
handleQueryEvent(event);
return; if (TRIGGER_SOURCE_QUERY.equals(event.getTriggerSource())) {
} handleQueryEvent(event);
return;
// 1. 跳过与状态变更日志重复的审计事件(到岗确认/自动完成请求已由 CleanOrderEventListener 记录) }
String auditType = event.getAuditType();
if (LogType.BEACON_ARRIVE_CONFIRMED.getCode().equals(auditType) // 1. 跳过与状态变更日志重复的审计事件(到岗确认/自动完成请求已由 CleanOrderEventListener 记录)
|| LogType.BEACON_COMPLETE_REQUESTED.getCode().equals(auditType)) { String auditType = event.getAuditType();
log.debug("[CleanOrderAuditEventHandler] 跳过重复审计事件: eventId={}, auditType={}", if (LogType.BEACON_ARRIVE_CONFIRMED.getCode().equals(auditType)
event.getEventId(), auditType); || LogType.BEACON_COMPLETE_REQUESTED.getCode().equals(auditType)) {
return; log.debug("[CleanOrderAuditEventHandler] 跳过重复审计事件: eventId={}, auditType={}",
} event.getEventId(), auditType);
return;
// 2. 确定日志级别和域 }
EventDomain domain = determineDomain(auditType);
EventLevel level = determineLevel(auditType); // 2. 确定日志级别和域
LogType logType = auditType != null ? LogType.getByCode(auditType) : null; EventDomain domain = determineDomain(auditType);
String eventType = logType != null ? logType.getCode() : (auditType != null ? auditType : "AUDIT"); EventLevel level = determineLevel(auditType);
LogType logType = auditType != null ? LogType.getByCode(auditType) : null;
// 3. 记录审计日志 String eventType = logType != null ? logType.getCode() : (auditType != null ? auditType : "AUDIT");
eventLogRecorder.record(
EventLogRecord.builder() // 3. 记录审计日志
.module(LogModule.CLEAN) eventLogRecorder.record(
.domain(domain) EventLogRecord.builder()
.eventType(eventType) .module(LogModule.CLEAN)
.message(event.getMessage()) .domain(domain)
.targetId(event.getOrderId()) .eventType(eventType)
.targetType(event.getOrderId() != null ? "order" : null) .message(event.getMessage())
.deviceId(event.getDeviceId()) .targetId(event.getOrderId())
.level(level) .targetType(event.getOrderId() != null ? "order" : null)
.build() .deviceId(event.getDeviceId())
); .level(level)
.build()
log.debug("[CleanOrderAuditEventHandler] 审计日志已记录: eventId={}, auditType={}", );
event.getEventId(), auditType);
log.debug("[CleanOrderAuditEventHandler] 审计日志已记录: eventId={}, auditType={}",
// 2. 如果是 TTS 请求,调用 IoT 模块下发语音 event.getEventId(), auditType);
if (LogType.TTS_REQUEST.getCode().equals(auditType) && event.getDeviceId() != null) {
handleTtsRequest(event); // 2. 如果是 TTS 请求,调用 IoT 模块下发语音
} if (LogType.TTS_REQUEST.getCode().equals(auditType) && event.getDeviceId() != null) {
} handleTtsRequest(event);
}
/** }
* 处理 TTS 请求
* <p> /**
* 调用 IoT 模块的设备控制接口,下发语音播报到工牌设备 * 处理 TTS 请求
* * <p>
* @param event 审计事件 * 调用 IoT 模块的设备控制接口,下发语音播报到工牌设备
*/ *
private void handleTtsRequest(CleanOrderAuditEventDTO event) { * @param event 审计事件
// 1. 从审计数据中提取 TTS 文本 */
String ttsText = null; private void handleTtsRequest(CleanOrderAuditEventDTO event) {
if (event.getData() != null && event.getData().containsKey("tts")) { // 1. 从审计数据中提取 TTS 文本
ttsText = (String) event.getData().get("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; if (ttsText == null || ttsText.isEmpty()) {
} log.warn("[CleanOrderAuditEventHandler] TTS 文本为空,跳过下发: eventId={}", event.getEventId());
return;
sendTts(event.getDeviceId(), ttsText, event.getOrderId()); }
}
sendTts(event.getDeviceId(), ttsText, event.getOrderId());
/** }
* 处理查询事件 (IOT_BUTTON_QUERY)
* <p> /**
* 查询事件仅播报当前工单地点信息,不涉及循环播报停止和状态流转。 * 处理查询事件 (IOT_BUTTON_QUERY)
* 循环播报的停止由确认路径CleanOrderEventListener.handleConfirmed负责。 * <p>
*/ * 查询事件仅播报当前工单地点信息,不涉及循环播报停止和状态流转。
private void handleQueryEvent(CleanOrderAuditEventDTO event) { * 循环播报的停止由确认路径CleanOrderEventListener.handleConfirmed负责。
log.info("[CleanOrderAuditEventHandler] Handling query event: eventId={}", event.getEventId()); */
private void handleQueryEvent(CleanOrderAuditEventDTO event) {
Long deviceId = event.getDeviceId(); log.info("[CleanOrderAuditEventHandler] Handling query event: eventId={}", event.getEventId());
if (deviceId == null) {
log.warn("[CleanOrderAuditEventHandler] Query event missing deviceId: eventId={}", event.getEventId()); Long deviceId = event.getDeviceId();
return; 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<OpsOrderDO>() // 1. 获取当前正在处理的工单DISPATCHED, CONFIRMED, ARRIVED 状态)
.eq(OpsOrderDO::getAssigneeDeviceId, deviceId) String currentAreaName = null;
.in(OpsOrderDO::getStatus, Arrays.asList( OpsOrderDO currentOrder = opsOrderMapper.selectOne(new LambdaQueryWrapperX<OpsOrderDO>()
WorkOrderStatusEnum.DISPATCHED.getStatus(), .eq(OpsOrderDO::getAssigneeDeviceId, deviceId)
WorkOrderStatusEnum.CONFIRMED.getStatus(), .in(OpsOrderDO::getStatus, Arrays.asList(
WorkOrderStatusEnum.ARRIVED.getStatus())) WorkOrderStatusEnum.DISPATCHED.getStatus(),
.orderByAsc(OpsOrderDO::getId) WorkOrderStatusEnum.CONFIRMED.getStatus(),
.last("LIMIT 1")); WorkOrderStatusEnum.ARRIVED.getStatus()))
.orderByAsc(OpsOrderDO::getId)
if (currentOrder != null && currentOrder.getLocation() != null) { .last("LIMIT 1"));
currentAreaName = currentOrder.getLocation();
} if (currentOrder != null && currentOrder.getLocation() != null) {
currentAreaName = currentOrder.getLocation();
// 2. 查询待办工单数量QUEUED 状态,不含当前处理中工单) }
Long pendingCount = opsOrderMapper.selectCount(new LambdaQueryWrapperX<OpsOrderDO>()
.eq(OpsOrderDO::getAssigneeDeviceId, deviceId) // 2. 查询待办工单数量QUEUED 状态,不含当前处理中工单)
.eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.QUEUED.getStatus())); Long pendingCount = opsOrderMapper.selectCount(new LambdaQueryWrapperX<OpsOrderDO>()
.eq(OpsOrderDO::getAssigneeDeviceId, deviceId)
// 3. 构建 TTS 文本(使用统一模板构建器) .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.QUEUED.getStatus()));
String ttsText = CleanNotificationConstants.VoiceBuilder.buildQuery(currentAreaName, pendingCount.intValue());
// 3. 构建 TTS 文本(使用统一模板构建器)
// 4. 直接下发 TTS按键响应需立即播报不走队列 String ttsText = CleanNotificationConstants.VoiceBuilder.buildQuery(currentAreaName, pendingCount.intValue());
Long orderId = currentOrder != null ? currentOrder.getId() : null;
sendTts(deviceId, ttsText, orderId); // 4. 直接下发 TTS按键响应需立即播报不走队列
} Long orderId = currentOrder != null ? currentOrder.getId() : null;
sendTts(deviceId, ttsText, orderId);
/** }
* 下发 TTS 语音播报(直接发送,不走队列,按键响应需立即播报)
*/ /**
private void sendTts(Long deviceId, String text, Long orderId) { * 下发 TTS 语音播报(直接发送,不走队列,按键响应需立即播报)
try { */
voiceBroadcastService.broadcastDirect(deviceId, text, private void sendTts(Long deviceId, String text, Long orderId) {
TtsQueueMessage.TTS_FLAG_URGENT, orderId); try {
log.info("[CleanOrderAuditEventHandler] TTS 直接下发成功: deviceId={}, text={}", deviceId, text); voiceBroadcastService.broadcastDirect(deviceId, text,
} catch (Exception e) { TtsQueueMessage.TTS_FLAG_URGENT, orderId);
log.error("[CleanOrderAuditEventHandler] TTS 直接下发异常: deviceId={}", deviceId, e); 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; private EventDomain determineDomain(String auditType) {
} if (auditType == null) {
if (auditType.startsWith("BEACON_") || auditType.contains("BEACON")) { return EventDomain.SYSTEM;
return EventDomain.BEACON; }
} else if (LogType.TTS_REQUEST.getCode().equals(auditType)) { if (auditType.startsWith("BEACON_") || auditType.contains("BEACON")) {
return EventDomain.DEVICE; return EventDomain.BEACON;
} else { } else if (LogType.TTS_REQUEST.getCode().equals(auditType)) {
return EventDomain.AUDIT; return EventDomain.DEVICE;
} } else {
} return EventDomain.AUDIT;
}
/** }
* 确定日志级别
*/ /**
private EventLevel determineLevel(String auditType) { * 确定日志级别
if (auditType != null && (auditType.endsWith("_WARNING") || */
auditType.endsWith("_SUPPRESSED") || private EventLevel determineLevel(String auditType) {
auditType.endsWith("_REJECTED"))) { if (auditType != null && (auditType.endsWith("_WARNING") ||
return EventLevel.WARN; auditType.endsWith("_SUPPRESSED") ||
} auditType.endsWith("_REJECTED"))) {
return EventLevel.INFO; return EventLevel.WARN;
}
return EventLevel.INFO;
} }
private void executeInTenantContext(Long tenantId, Runnable runnable) { private void executeInTenantContext(Long tenantId, Runnable runnable) {

View File

@@ -50,6 +50,9 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener<String>
@Resource @Resource
private OrderLifecycleManager orderLifecycleManager; private OrderLifecycleManager orderLifecycleManager;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -61,7 +64,9 @@ public class CleanOrderCompleteEventHandler implements RocketMQListener<String>
log.debug("[CleanOrderCompleteEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); log.debug("[CleanOrderCompleteEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId());
return; 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) { } catch (Exception e) {
log.error("[CleanOrderCompleteEventHandler] 消息处理失败: message={}", message, e); log.error("[CleanOrderCompleteEventHandler] 消息处理失败: message={}", message, e);
throw new RuntimeException("保洁工单完单事件处理失败", e); throw new RuntimeException("保洁工单完单事件处理失败", e);

View File

@@ -47,6 +47,9 @@ public class CleanOrderConfirmEventHandler implements RocketMQListener<String> {
@Resource @Resource
private OrderLifecycleManager orderLifecycleManager; private OrderLifecycleManager orderLifecycleManager;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -58,7 +61,9 @@ public class CleanOrderConfirmEventHandler implements RocketMQListener<String> {
log.debug("[CleanOrderConfirmEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId()); log.debug("[CleanOrderConfirmEventHandler] 重复消息,跳过处理: eventId={}", event.getEventId());
return; 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) { } catch (Exception e) {
log.error("[CleanOrderConfirmEventHandler] 消息处理失败: message={}", message, e); log.error("[CleanOrderConfirmEventHandler] 消息处理失败: message={}", message, e);
throw new RuntimeException("保洁工单确认事件处理失败", e); throw new RuntimeException("保洁工单确认事件处理失败", e);

View File

@@ -1,11 +1,15 @@
package com.viewsh.module.ops.environment.integration.consumer; package com.viewsh.module.ops.environment.integration.consumer;
import com.fasterxml.jackson.databind.ObjectMapper; 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.context.TenantContextHolder;
import com.viewsh.framework.tenant.core.util.ProjectUtils;
import com.viewsh.framework.tenant.core.util.TenantUtils; import com.viewsh.framework.tenant.core.util.TenantUtils;
import com.viewsh.module.iot.api.device.IotDeviceControlApi; import com.viewsh.module.iot.api.device.IotDeviceControlApi;
import com.viewsh.module.iot.api.device.dto.ResetTrafficCounterReqDTO; 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.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.dal.mysql.workorder.OpsOrderMapper;
import com.viewsh.module.ops.enums.PriorityEnum; import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum; import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
@@ -103,6 +107,9 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
@Resource @Resource
private OpsOrderMapper opsOrderMapper; private OpsOrderMapper opsOrderMapper;
@Resource
private OpsBusAreaMapper opsBusAreaMapper;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -120,7 +127,8 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
} }
// 3. 业务处理 // 3. 业务处理
executeInTenantContext(event.getTenantId(), () -> handleOrderCreate(event)); executeInTenantContext(event.getTenantId(), () ->
executeInProjectContext(event, () -> handleOrderCreate(event)));
} catch (Exception e) { } catch (Exception e) {
log.error("[CleanOrderCreateEventHandler] 消息处理失败: message={}", message, e); log.error("[CleanOrderCreateEventHandler] 消息处理失败: message={}", message, e);
@@ -538,6 +546,41 @@ public class CleanOrderCreateEventHandler implements RocketMQListener<String> {
return null; 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) { private void executeInTenantContext(Long tenantId, Runnable runnable) {
Long currentTenantId = TenantContextHolder.getTenantId(); Long currentTenantId = TenantContextHolder.getTenantId();
if (tenantId == null || Objects.equals(currentTenantId, tenantId)) { if (tenantId == null || Objects.equals(currentTenantId, tenantId)) {

View File

@@ -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;
}
}

View File

@@ -42,6 +42,9 @@ public class TrajectoryEnterEventHandler implements RocketMQListener<String> {
@Resource @Resource
private DeviceTrajectoryService trajectoryService; private DeviceTrajectoryService trajectoryService;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -57,17 +60,8 @@ public class TrajectoryEnterEventHandler implements RocketMQListener<String> {
event.getEventId(), event.getDeviceId(), event.getAreaId()); event.getEventId(), event.getDeviceId(), event.getAreaId());
// 解析事件时间 // 解析事件时间
LocalDateTime enterTime = parseEventTime(event.getEventTime()); projectContextExecutor.execute(event.getProjectId(), null, event.getAreaId(), event.getDeviceId(),
event.getEventId(), () -> handleTrajectoryEnter(event));
// 创建轨迹记录
trajectoryService.recordEnter(
event.getDeviceId(),
event.getDeviceName(),
event.getNickname(),
event.getAreaId(),
event.getBeaconMac(),
event.getEnterRssi(),
enterTime);
} catch (Exception e) { } catch (Exception e) {
log.error("[TrajectoryEnterHandler] 消息处理失败message={}", message, e); log.error("[TrajectoryEnterHandler] 消息处理失败message={}", message, e);
@@ -75,6 +69,18 @@ public class TrajectoryEnterEventHandler implements RocketMQListener<String> {
} }
} }
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) { private LocalDateTime parseEventTime(String eventTime) {
if (eventTime == null || eventTime.isEmpty()) { if (eventTime == null || eventTime.isEmpty()) {
return LocalDateTime.now(); return LocalDateTime.now();

View File

@@ -42,6 +42,9 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener<String> {
@Resource @Resource
private DeviceTrajectoryService trajectoryService; private DeviceTrajectoryService trajectoryService;
@Resource
private IntegrationProjectContextExecutor projectContextExecutor;
@Override @Override
public void onMessage(String message) { public void onMessage(String message) {
try { try {
@@ -57,15 +60,8 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener<String> {
event.getEventId(), event.getDeviceId(), event.getAreaId(), event.getLeaveReason()); event.getEventId(), event.getDeviceId(), event.getAreaId(), event.getLeaveReason());
// 解析事件时间 // 解析事件时间
LocalDateTime leaveTime = parseEventTime(event.getEventTime()); projectContextExecutor.execute(event.getProjectId(), null, event.getAreaId(), event.getDeviceId(),
event.getEventId(), () -> handleTrajectoryLeave(event));
// 更新轨迹记录
trajectoryService.recordLeave(
event.getDeviceId(),
event.getAreaId(),
event.getLeaveReason(),
event.getEnterTimestamp(),
leaveTime);
} catch (Exception e) { } catch (Exception e) {
log.error("[TrajectoryLeaveHandler] 消息处理失败message={}", message, e); log.error("[TrajectoryLeaveHandler] 消息处理失败message={}", message, e);
@@ -73,6 +69,16 @@ public class TrajectoryLeaveEventHandler implements RocketMQListener<String> {
} }
} }
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) { private LocalDateTime parseEventTime(String eventTime) {
if (eventTime == null || eventTime.isEmpty()) { if (eventTime == null || eventTime.isEmpty()) {
return LocalDateTime.now(); return LocalDateTime.now();

View File

@@ -51,6 +51,9 @@ public class BaseDeviceEventDTO {
@JsonProperty("tenantId") @JsonProperty("tenantId")
private Long tenantId; private Long tenantId;
@JsonProperty("projectId")
private Long projectId;
/** /**
* 事件时间 * 事件时间
*/ */

View File

@@ -1,66 +1,68 @@
package com.viewsh.module.ops.environment.integration.dto; package com.viewsh.module.ops.environment.integration.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map; import java.util.Map;
/** /**
* 保洁工单到岗事件 DTO * 保洁工单到岗事件 DTO
* <p> * <p>
* 由 IoT 模块发布Ops 模块消费 * 由 IoT 模块发布Ops 模块消费
* *
* @author AI * @author AI
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CleanOrderArriveEventDTO { public class CleanOrderArriveEventDTO {
/** /**
* 事件IDUUID用于幂等性控制 * 事件IDUUID用于幂等性控制
*/ */
private String eventId; private String eventId;
/** /**
* 工单类型CLEAN=保洁) * 工单类型CLEAN=保洁)
*/ */
private String orderType; private String orderType;
/** /**
* 工单ID * 工单ID
*/ */
private Long orderId; private Long orderId;
/** /**
* 设备ID保洁员工牌设备ID * 设备ID保洁员工牌设备ID
*/ */
private Long deviceId; private Long deviceId;
/** /**
* 设备Key * 设备Key
*/ */
private String deviceKey; private String deviceKey;
/** /**
* 区域ID * 区域ID
*/ */
private Long areaId; private Long areaId;
private Long tenantId; private Long tenantId;
/** private Long projectId;
* 触发来源IOT_BEACON=蓝牙信标检测)
*/ /**
private String triggerSource; * 触发来源IOT_BEACON=蓝牙信标检测)
*/
/** private String triggerSource;
* 触发数据JSON 格式的附加信息)
* <p> /**
* 示例:{"beaconMac":"F0:C8:60:1D:10:BB","rssi":-65,"windowSnapshot":[-70,-68,-65,-64,-66]} * 触发数据JSON 格式的附加信息)
*/ * <p>
private Map<String, Object> triggerData; * 示例:{"beaconMac":"F0:C8:60:1D:10:BB","rssi":-65,"windowSnapshot":[-70,-68,-65,-64,-66]}
} */
private Map<String, Object> triggerData;
}

View File

@@ -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 com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map; import java.util.Map;
/** /**
* 保洁工单审计事件 DTO * 保洁工单审计事件 DTO
* <p> * <p>
* 由 IoT 模块发布Ops 模块消费 * 由 IoT 模块发布Ops 模块消费
* 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等) * 用于记录非状态变更的业务审计日志(如警告发送、抑制操作等)
* *
* @author AI * @author AI
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CleanOrderAuditEventDTO { public class CleanOrderAuditEventDTO {
/** /**
* 事件IDUUID用于幂等性控制 * 事件IDUUID用于幂等性控制
*/ */
private String eventId; private String eventId;
/** /**
* 工单ID可选部分审计事件可能没有工单ID * 工单ID可选部分审计事件可能没有工单ID
*/ */
private Long orderId; private Long orderId;
/** /**
* 触发来源 (如 IOT_BUTTON_QUERY) * 触发来源 (如 IOT_BUTTON_QUERY)
*/ */
@@ -40,40 +40,42 @@ public class CleanOrderAuditEventDTO {
/** /**
* 审计类型 * 审计类型
* <p> * <p>
* - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认 * - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认
* - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送 * - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送
* - COMPLETE_SUPPRESSED_INVALID: 作业时长不足,抑制自动完成 * - COMPLETE_SUPPRESSED_INVALID: 作业时长不足,抑制自动完成
* - BEACON_COMPLETE_REQUESTED: 信号丢失超时自动完成请求 * - BEACON_COMPLETE_REQUESTED: 信号丢失超时自动完成请求
* - TTS_REQUEST: TTS 语音播报请求 * - TTS_REQUEST: TTS 语音播报请求
* - ARRIVE_REJECTED: 到岗请求被拒绝(状态不符) * - ARRIVE_REJECTED: 到岗请求被拒绝(状态不符)
*/ */
private String auditType; private String auditType;
/** /**
* 设备ID * 设备ID
*/ */
private Long deviceId; private Long deviceId;
/** /**
* 设备Key * 设备Key
*/ */
private String deviceKey; private String deviceKey;
/** /**
* 区域ID * 区域ID
*/ */
private Long areaId; private Long areaId;
private Long tenantId; private Long tenantId;
/** private Long projectId;
* 消息内容
*/ /**
private String message; * 消息内容
*/
/** private String message;
* 审计数据JSON 格式的附加信息)
*/ /**
private Map<String, Object> data; * 审计数据JSON 格式的附加信息)
} */
private Map<String, Object> data;
}

View File

@@ -1,66 +1,68 @@
package com.viewsh.module.ops.environment.integration.dto; package com.viewsh.module.ops.environment.integration.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map; import java.util.Map;
/** /**
* 保洁工单完成事件 DTO * 保洁工单完成事件 DTO
* <p> * <p>
* 由 IoT 模块发布Ops 模块消费 * 由 IoT 模块发布Ops 模块消费
* *
* @author AI * @author AI
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CleanOrderCompleteEventDTO { public class CleanOrderCompleteEventDTO {
/** /**
* 事件IDUUID用于幂等性控制 * 事件IDUUID用于幂等性控制
*/ */
private String eventId; private String eventId;
/** /**
* 工单类型CLEAN=保洁) * 工单类型CLEAN=保洁)
*/ */
private String orderType; private String orderType;
/** /**
* 工单ID * 工单ID
*/ */
private Long orderId; private Long orderId;
/** /**
* 设备ID保洁员工牌设备ID * 设备ID保洁员工牌设备ID
*/ */
private Long deviceId; private Long deviceId;
/** /**
* 设备Key * 设备Key
*/ */
private String deviceKey; private String deviceKey;
/** /**
* 区域ID * 区域ID
*/ */
private Long areaId; private Long areaId;
private Long tenantId; private Long tenantId;
/** private Long projectId;
* 触发来源IOT_SIGNAL_LOSS=信号丢失超时)
*/ /**
private String triggerSource; * 触发来源IOT_SIGNAL_LOSS=信号丢失超时)
*/
/** private String triggerSource;
* 触发数据JSON 格式的附加信息)
* <p> /**
* 示例:{"durationMs":1800000,"lastLossTime":1704067200000,"completionReason":"SIGNAL_LOSS_TIMEOUT"} * 触发数据JSON 格式的附加信息)
*/ * <p>
private Map<String, Object> triggerData; * 示例:{"durationMs":1800000,"lastLossTime":1704067200000,"completionReason":"SIGNAL_LOSS_TIMEOUT"}
} */
private Map<String, Object> triggerData;
}

View File

@@ -1,67 +1,72 @@
package com.viewsh.module.ops.environment.integration.dto; package com.viewsh.module.ops.environment.integration.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map; import java.util.Map;
/** /**
* 保洁工单创建事件 DTO * 保洁工单创建事件 DTO
* <p> * <p>
* 由 IoT 模块发布Ops 模块消费 * 由 IoT 模块发布Ops 模块消费
* *
* @author AI * @author AI
*/ */
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CleanOrderCreateEventDTO { public class CleanOrderCreateEventDTO {
/** /**
* 事件IDUUID用于幂等性控制 * 事件IDUUID用于幂等性控制
*/ */
private String eventId; private String eventId;
/** /**
* 工单类型CLEAN=保洁) * 工单类型CLEAN=保洁)
*/ */
private String orderType; private String orderType;
/** /**
* 区域ID * 区域ID
*/ */
private Long areaId; private Long areaId;
/** /**
* 触发来源IOT_TRAFFIC=客流阈值/IOT_BEACON=蓝牙信标/IOT_SIGNAL_LOSS=信号丢失超时) * 触发来源IOT_TRAFFIC=客流阈值/IOT_BEACON=蓝牙信标/IOT_SIGNAL_LOSS=信号丢失超时)
*/ */
private String triggerSource; private String triggerSource;
/** /**
* 触发设备ID * 触发设备ID
*/ */
private Long triggerDeviceId; private Long triggerDeviceId;
/** /**
* 触发设备Key * 触发设备Key
*/ */
private String triggerDeviceKey; private String triggerDeviceKey;
/** /**
* 优先级0=P0紧急 1=P1重要 2=P2普通 * 优先级0=P0紧急 1=P1重要 2=P2普通
*/ */
private Integer priority; private Integer priority;
private Long tenantId; private Long tenantId;
/** /**
* 触发数据JSON 格式的附加信息) * Project id.
* <p> */
* 客流阈值触发示例:{"actualCount":150,"baseValue":1000,"threshold":100,"exceededCount":50} private Long projectId;
* 信标检测触发示例:{"rssi":-65,"beaconMac":"F0:C8:60:1D:10:BB"}
*/ /**
private Map<String, Object> triggerData; * 触发数据JSON 格式的附加信息)
} * <p>
* 客流阈值触发示例:{"actualCount":150,"baseValue":1000,"threshold":100,"exceededCount":50}
* 信标检测触发示例:{"rssi":-65,"beaconMac":"F0:C8:60:1D:10:BB"}
*/
private Map<String, Object> triggerData;
}

View File

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

View File

@@ -62,4 +62,6 @@ public class TrajectoryEnterEventDTO {
* 租户ID * 租户ID
*/ */
private Long tenantId; private Long tenantId;
private Long projectId;
} }

View File

@@ -69,4 +69,6 @@ public class TrajectoryLeaveEventDTO {
* 租户ID * 租户ID
*/ */
private Long tenantId; private Long tenantId;
private Long projectId;
} }