diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/mapper/MessageWarnMapper.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/mapper/MessageWarnMapper.java index 37ae332..768382c 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/mapper/MessageWarnMapper.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/mapper/MessageWarnMapper.java @@ -1,8 +1,12 @@ -package com.intc.fishery.mapper; + package com.intc.fishery.mapper; import com.intc.common.mybatis.core.mapper.BaseMapperPlusJoin; import com.intc.fishery.domain.MessageWarn; import com.intc.fishery.domain.vo.MessageWarnVo; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.Date; /** * 设备告警记录Mapper接口 @@ -12,4 +16,18 @@ import com.intc.fishery.domain.vo.MessageWarnVo; */ public interface MessageWarnMapper extends BaseMapperPlusJoin { + /** + * 查询指定设备、告警标题、时间范围内的告警消息数量 + * 用于对未开启电话通知的告警类型做时间窗口抑制,避免每次超阈值都重复写入记录 + * + * @param deviceId 设备ID + * @param title 告警标题 + * @param afterTime 起始时间 + * @return 满足条件的告警消息数量 + */ + @Select("SELECT COUNT(1) FROM aqu_message_warn WHERE device_id = #{deviceId} AND title = #{title} AND create_time >= #{afterTime}") + long countByDeviceAndTitleAfter(@Param("deviceId") Long deviceId, + @Param("title") String title, + @Param("afterTime") Date afterTime); + } 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 a935611..0eb979c 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 @@ -441,16 +441,55 @@ public class DeviceDataHandler { } } - // 如果有告警,保存全部报警消息记录 + // 如果有告警,处理报警消息记录 if (hasAlarm) { log.warn("[告警] {} - {}", deviceName, alarmMessage); - // 批量保存报警消息(无论是否开启电话通知,告警记录始终生成) + // 计算抑制时间窗口(与电话通知保持一致) + LocalDateTime suppressTime = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours()); + java.util.Date suppressDate = java.util.Date.from(suppressTime.atZone(java.time.ZoneId.systemDefault()).toInstant()); + int maxBatchMinutesForCheck = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2; + LocalDateTime activeBatchStartForCheck = LocalDateTime.now().minusMinutes(maxBatchMinutesForCheck); + LocalDateTime periodStartForCheck = getCurrentPeriodStart(); + + // 对未开启电话通知的告警类型,做时间窗口抑制后再插入消息记录 + // 对开启了电话通知的类型,MessageWarn 由 triggerAlarmNotification 在通过抑制判断后统一插入 for (MessageWarn warn : warnList) { - messageWarnMapper.insert(warn); + // 跳过已加入电话通知列表的告警(由 triggerAlarmNotification 负责插入) + if (callWarnList.contains(warn)) { + continue; + } + // 判断该类型告警是否开启了免打扰 + boolean noDisturbForWarn = false; + if (ALARM_TYPE_DISSOLVED_OXYGEN.equals(warn.getTitle()) && + device.getOxyWarnCallNoDis() != null && device.getOxyWarnCallNoDis() == 1) { + noDisturbForWarn = true; + } else if ((ALARM_TYPE_TEMPERATURE_HIGH.equals(warn.getTitle()) || ALARM_TYPE_TEMPERATURE_LOW.equals(warn.getTitle())) && + device.getTempWarnCallNoDis() != null && device.getTempWarnCallNoDis() == 1) { + noDisturbForWarn = true; + } else if (ALARM_TYPE_BATTERY.equals(warn.getTitle()) && + device.getBatteryWarnCallNoDis() != null && device.getBatteryWarnCallNoDis() == 1) { + noDisturbForWarn = true; + } + // 按抑制规则判断是否应跳过 + boolean shouldSkip; + if (noDisturbForWarn) { + java.util.Date periodStartDate = java.util.Date.from(periodStartForCheck.atZone(java.time.ZoneId.systemDefault()).toInstant()); + shouldSkip = messageWarnMapper.countByDeviceAndTitleAfter(deviceId, warn.getTitle(), periodStartDate) > 0; + } else { + java.util.Date activeBatchStartDate = java.util.Date.from(activeBatchStartForCheck.atZone(java.time.ZoneId.systemDefault()).toInstant()); + shouldSkip = messageWarnMapper.countByDeviceAndTitleAfter(deviceId, warn.getTitle(), activeBatchStartDate) > 0 + || messageWarnMapper.countByDeviceAndTitleAfter(deviceId, warn.getTitle(), suppressDate) > 0; + } + if (!shouldSkip) { + messageWarnMapper.insert(warn); + log.debug("[告警] {} title={} 新增消息记录", deviceName, warn.getTitle()); + } else { + log.debug("[告警] {} title={} 抑制期内已有记录,跳过消息写入", deviceName, warn.getTitle()); + } } - // 仅对开启了电话通知开关的告警触发电话通知 + // 仅对开启了电话通知开关的告警触发电话通知(MessageWarn 由通知方法内部插入) if (!callWarnList.isEmpty()) { triggerAlarmNotification(device, alarmMessage.toString(), sensorData, callWarnList); } @@ -565,6 +604,12 @@ public class DeviceDataHandler { // 告警内容(取该类型第一条) String typeAlarmMessage = typeWarnList.get(0).getMessage(); + // 通过抑制判断,先插入 MessageWarn 记录(每次电话报警才新增一条消息) + for (MessageWarn warn : typeWarnList) { + messageWarnMapper.insert(warn); + } + log.debug("[告警] {} title={} 通过抑制,已插入 {} 条告警消息记录", deviceName, title, typeWarnList.size()); + // 为每个手机号 × 重试次数,创建通知记录,callTime 按间隔错开 // 参考 C#: CallNoticeRetryCount=3, CallNoticeMinuteInterval=3 // 立即尝试发送第一个号码;若失败则后续通知记录会在定时任务中按时间发送