fix(iot,ops): 修复退出检测停滞、TTS多租户重复播报,精简语音通知
1. 蓝牙信号缺失补偿:设备属性上报不含 bluetoothDevices 时注入 null, 避免 RSSI 滑动窗口因无数据停滞导致退出检测延迟 2. TTS 多租户去重:TtsQueueMessage 携带 tenantId,processSingleQueue 过滤非当前租户消息,解决 @TenantJob 导致同一播报被不同租户重复下发 3. 循环播报日志精简:仅在 broadcastLoop 启动时记录一次 TTS_SENT, 后续重复播报不再写入 ops_business_event_log 4. 移除离岗 TTS 警告和入队语音播报,减少不必要的设备干扰 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,13 @@ public class CleanRuleProcessorManager {
|
||||
// 属性上报:直接遍历 key-value
|
||||
data.forEach((identifier, value) ->
|
||||
processDataSafely(deviceId, identifier, value));
|
||||
|
||||
// 4. 蓝牙信号缺失补偿:当设备上报了属性但不含 bluetoothDevices 时,
|
||||
// 主动注入一次 null 调用,使 BeaconDetectionRuleProcessor 能写入 -999(信号缺失),
|
||||
// 避免退出检测窗口因无数据而停滞
|
||||
if (!data.containsKey("bluetoothDevices")) {
|
||||
beaconDetectionRuleProcessor.processPropertyChange(deviceId, "bluetoothDevices", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -226,12 +226,7 @@ public class BeaconDetectionRuleProcessor {
|
||||
// 首次丢失
|
||||
signalLossRedisDAO.recordFirstLoss(deviceId, areaId, System.currentTimeMillis());
|
||||
|
||||
// 2. 发送警告
|
||||
publishTtsEvent(deviceId, "你已离开当前区域," +
|
||||
(exitConfig.getLossTimeoutMinutes() > 0 ? exitConfig.getLossTimeoutMinutes() + "分钟内工单将自动结算"
|
||||
: "工单将自动结算"));
|
||||
|
||||
// 3. 发布审<E5B883><E5AEA1><EFBFBD>日志
|
||||
// 2. 发布审计日志
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("firstLossTime", System.currentTimeMillis());
|
||||
data.put("rssi", window.isEmpty() ? -999 : window.get(window.size() - 1));
|
||||
|
||||
@@ -242,18 +242,8 @@ public class CleanOrderEventListener {
|
||||
}
|
||||
|
||||
if (deviceId != null) {
|
||||
try {
|
||||
// 获取等待中的任务列表 - 从 MySQL 读取确保包含刚入队的任务
|
||||
var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId);
|
||||
int queueCount = waitingTasks != null ? waitingTasks.size() : 0;
|
||||
|
||||
// 发送待办增加通知
|
||||
sendQueuedOrderNotification(deviceId, queueCount, orderId);
|
||||
|
||||
log.info("[CleanOrderEventListener] 入队语音播报已发送: deviceId={}, queueCount={}", deviceId, queueCount);
|
||||
} catch (Exception e) {
|
||||
log.warn("[CleanOrderEventListener] 播报入队语音失败: deviceId={}, orderId={}", deviceId, orderId);
|
||||
}
|
||||
// TODO 入队语音播报暂时关闭,后续按需开启
|
||||
log.info("[CleanOrderEventListener] 工单入队: deviceId={}, orderId={}", deviceId, orderId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.viewsh.framework.tenant.core.context.TenantContextHolder;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -185,6 +186,17 @@ public class TtsQueueConsumer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 多租户过滤:@TenantJob 会为每个租户执行一次,但 TTS 队列是全局的(非租户隔离)。
|
||||
// 只有消息所属租户才应处理,避免同一消息被不同租户重复播报。
|
||||
Long currentTenantId = TenantContextHolder.getTenantId();
|
||||
if (message.getTenantId() != null && currentTenantId != null
|
||||
&& !message.getTenantId().equals(currentTenantId)) {
|
||||
// 不是当前租户的消息,放回队列头部,释放锁让正确的租户处理
|
||||
redisTemplate.opsForList().leftPush(queueKey, messageObj);
|
||||
redisTemplate.delete(lockKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查消息是否过期
|
||||
if (message.isExpired()) {
|
||||
log.info("[TTS队列] 消息已过期: deviceId={}, text={}",
|
||||
@@ -301,8 +313,8 @@ public class TtsQueueConsumer {
|
||||
|
||||
iotDeviceControlApi.invokeService(reqDTO);
|
||||
|
||||
// 记录日志
|
||||
if (message.getOrderId() != null) {
|
||||
// 记录日志(循环消息只在启动时记录一次,重复播报不再写日志)
|
||||
if (message.getOrderId() != null && !message.isLoopable()) {
|
||||
eventLogRecorder.info("clean", EventDomain.DEVICE, "TTS_SENT",
|
||||
"语音播报: " + message.getText(), message.getOrderId(), message.getDeviceId(), null);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import com.viewsh.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -93,6 +95,11 @@ public class TtsQueueMessage implements Serializable {
|
||||
*/
|
||||
private Boolean loopable;
|
||||
|
||||
/**
|
||||
* 租户ID(用于多租户 Job 场景下匹配正确的租户上下文)
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 创建按序消息(FIFO,rightPush 追加到尾部)
|
||||
* <p>
|
||||
@@ -109,6 +116,7 @@ public class TtsQueueMessage implements Serializable {
|
||||
.createTime(System.currentTimeMillis())
|
||||
.retryCount(0)
|
||||
.maxRetry(2)
|
||||
.tenantId(TenantContextHolder.getTenantId())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -128,6 +136,7 @@ public class TtsQueueMessage implements Serializable {
|
||||
.createTime(System.currentTimeMillis())
|
||||
.retryCount(0)
|
||||
.maxRetry(3)
|
||||
.tenantId(TenantContextHolder.getTenantId())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -148,6 +157,7 @@ public class TtsQueueMessage implements Serializable {
|
||||
.retryCount(0)
|
||||
.maxRetry(2)
|
||||
.loopable(true)
|
||||
.tenantId(TenantContextHolder.getTenantId())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,10 @@ public class VoiceBroadcastService {
|
||||
ttsQueueConsumer.startLoop(deviceId, orderId);
|
||||
// 入队循环消息
|
||||
enqueueOrFallback(TtsQueueMessage.loopMessage(deviceId, text, orderId));
|
||||
// 记录一次循环开始日志(后续重复播报不再写日志)
|
||||
if (orderId != null) {
|
||||
recordLog(deviceId, text, orderId, true, null);
|
||||
}
|
||||
} else {
|
||||
broadcastDirect(deviceId, text, TtsQueueMessage.TTS_FLAG_URGENT, orderId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user