From fe85fb2b75a6a1d9f88860ad1c55d257c6d94e2b Mon Sep 17 00:00:00 2001 From: tianyongbao Date: Tue, 10 Mar 2026 00:02:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=94=B5=E8=AF=9D=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../intc/iot/handler/DeviceDataHandler.java | 38 ++++++++++++++----- .../iot/mapper/AquWarnCallNoticeMapper.java | 36 ++++++++++++++---- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java index 64dabb7..a935611 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java @@ -506,8 +506,12 @@ public class DeviceDataHandler { } String pondName = warnList.isEmpty() ? deviceName : warnList.get(0).getPondName(); - // 抑制时间:根据配置的小时数展开,查找 callStatus=3(回执成功)的记录 - LocalDateTime intervalTime = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours()); + // 抑制时间:查找 callStatus=3(回执成功)的记录 + LocalDateTime suppressTime = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours()); + // 活跍批次查找窗口:一个完整通知批次的最大时长 = 重试次数 * 手机号数,每次间隔 CALL_NOTICE_MINUTE_INTERVAL 分钟 + // 这里简化用 2 倍的全周期时间保证导式内等待记录能被查到 + int maxBatchMinutes = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2; + LocalDateTime activeBatchStart = LocalDateTime.now().minusMinutes(maxBatchMinutes); LocalDateTime periodStart = getCurrentPeriodStart(); // 按 title 分组,每种类型独立判断间隔并发送通知 @@ -533,18 +537,25 @@ public class DeviceDataHandler { noDisturbEnabled = true; } - // 按设备+title检查是否已通知 + // 按设备+title检查是否已通知(双重抑制) long recentCount; if (noDisturbEnabled) { - // 免打扰开启:当前时段内已通知过则跳过 + // 免打扰开启:当前时段内已有任意状态的通知就跳过 recentCount = warnCallNoticeMapper.countByDeviceAndTitleInPeriod(deviceId, title, periodStart); if (recentCount > 0) { log.debug("[告警] {} title={} 免打扰:当前时段内已通知,跳过", deviceName, title); continue; } } else { - // 免打扰关闭:查找指定小时内是否已有回执成功的通知 - recentCount = warnCallNoticeMapper.countByDeviceAndTitleAfter(deviceId, title, intervalTime); + // 免打扰关闭:双重抑制 + // 1. 先查活跍批次(未完成的通知批次),防止当前轮重复创建 + long activeCount = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, title, activeBatchStart); + if (activeCount > 0) { + log.debug("[告警] {} title={} - 当前有活跍通知批次在进行,跳过", deviceName, title); + continue; + } + // 2. 再查N小时内是否已有回执成功的记录 + recentCount = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, title, suppressTime); if (recentCount > 0) { log.debug("[告警] {} title={} - {}h内已有回执成功的通知,跳过", deviceName, title, aliyunIotProperties.getCallSuccessSuppressHours()); continue; @@ -793,11 +804,18 @@ public class DeviceDataHandler { return; } - // 检查告警间隔(防止频繁通知) - LocalDateTime intervalTime = LocalDateTime.now().minusMinutes(aliyunIotProperties.getAlarmNotificationIntervalMinutes()); - long recentCount = warnCallNoticeMapper.countByDeviceAndTitleAfter(deviceId, "设备离线", intervalTime); + // 双重抑制:1) 活跃批次检查(防止当前轮重复); 2) 成功通知抑制(N小时内已成功则不再打) + int maxBatchMinutesOff = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2; + LocalDateTime activeBatchStartOff = LocalDateTime.now().minusMinutes(maxBatchMinutesOff); + long activeCountOff = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, "设备离线", activeBatchStartOff); + if (activeCountOff > 0) { + log.debug("[离线告警] 当前有活跃通知批次在进行,跳过: {}", deviceName); + return; + } + LocalDateTime suppressTimeOff = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours()); + long recentCount = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, "设备离线", suppressTimeOff); if (recentCount > 0) { - log.debug("[离线告警] {}分钟内已通知,跳过: {}", aliyunIotProperties.getAlarmNotificationIntervalMinutes(), deviceName); + log.debug("[离线告警] {}h内已有回执成功的通知,跳过: {}", aliyunIotProperties.getCallSuccessSuppressHours(), deviceName); return; } diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/mapper/AquWarnCallNoticeMapper.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/mapper/AquWarnCallNoticeMapper.java index 32ea53f..e473354 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/mapper/AquWarnCallNoticeMapper.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/mapper/AquWarnCallNoticeMapper.java @@ -19,10 +19,10 @@ public interface AquWarnCallNoticeMapper extends BaseMapper { /** * 查询指定设备、告警标题、时间范围内「回执成功」的通知数量 - * 只有 callStatus=3(回执成功)的记录才算真正通知到人,才会触发抑制 + * 只有 callStatus=3(回执成功)的记录才算真正通知到人,用于长时间(N小时)抑制判断 * * @param deviceId 设备ID - * @param title 告警标题(如"溶解氧"、"设备离线"等) + * @param title 告警标题 * @param afterTime 起始时间 * @return 满足条件的通知数量 */ @@ -33,14 +33,34 @@ public interface AquWarnCallNoticeMapper extends BaseMapper { "AND w.title = #{title} " + "AND n.call_status = 3 " + "AND n.call_time >= #{afterTime}") - long countByDeviceAndTitleAfter(@Param("deviceId") Long deviceId, - @Param("title") String title, - @Param("afterTime") LocalDateTime afterTime); + long countSuccessByDeviceAndTitleAfter(@Param("deviceId") Long deviceId, + @Param("title") String title, + @Param("afterTime") LocalDateTime afterTime); /** - * 查询指定设备、告警标题、时段范围内「回执成功」的通知数量(免打扰模式:每个时段只通知一次) + * 查询指定设备、告警标题、时间范围内「活跍通知批次」的数量 + * 活跍 = callStatus IN (0,1,2),即本轮通知进行中,用于防止在当前属于未处理完成的通知批次内重复创建 + * + * @param deviceId 设备ID + * @param title 告警标题 + * @param afterTime 起始时间 + * @return 满足条件的通知数量 + */ + @Select("SELECT COUNT(1) FROM aqu_call_notice n " + + "INNER JOIN aqu_map_message_warn_call_notice m ON m.call_notice_id = n.id " + + "INNER JOIN aqu_message_warn w ON w.id = m.message_warn_id " + + "WHERE n.device_id = #{deviceId} " + + "AND w.title = #{title} " + + "AND n.call_status IN (0, 1, 2) " + + "AND n.call_time >= #{afterTime}") + long countActiveByDeviceAndTitleAfter(@Param("deviceId") Long deviceId, + @Param("title") String title, + @Param("afterTime") LocalDateTime afterTime); + + /** + * 查询指定设备、告警标题、时段范围内「活跍或成功」的通知数量(免打扰模式) * 时段划分:08:00-14:00、14:00-20:00、20:00-08:00 - * 只有 callStatus=3(回执成功)的记录才算真正通知到人,才会触发抑制 + * 包含 callStatus IN (0,1,2,3),只要当前时段内有任意状态的通知就不再重复创建 * * @param deviceId 设备ID * @param title 告警标题 @@ -52,7 +72,7 @@ public interface AquWarnCallNoticeMapper extends BaseMapper { "INNER JOIN aqu_message_warn w ON w.id = m.message_warn_id " + "WHERE n.device_id = #{deviceId} " + "AND w.title = #{title} " + - "AND n.call_status = 3 " + + "AND n.call_status IN (0, 1, 2, 3) " + "AND n.call_time >= #{periodStart}") long countByDeviceAndTitleInPeriod(@Param("deviceId") Long deviceId, @Param("title") String title,