diff --git a/src/main/java/com/zcloud/controller/AppPosiDeviceController.java b/src/main/java/com/zcloud/controller/AppPosiDeviceController.java index 608a0cd..5752a41 100644 --- a/src/main/java/com/zcloud/controller/AppPosiDeviceController.java +++ b/src/main/java/com/zcloud/controller/AppPosiDeviceController.java @@ -39,6 +39,24 @@ public class AppPosiDeviceController { // 用于跟踪设备节点的报警状态 private final Map alarmStatusMap = new ConcurrentHashMap<>(); + + // 用于报警延时确认机制的计数器 + private final Map alarmConfirmCount = new ConcurrentHashMap<>(); + + // 用于跟踪报警开始时间的映射 + private final Map alarmStartTimeMap = new ConcurrentHashMap<>(); + + // 用于跟踪报警恢复开始时间的映射 + private final Map alarmRecoverStartTimeMap = new ConcurrentHashMap<>(); + + // 用于存储当前报警信息的映射(避免存入过期的报警信息) + private final Map> currentAlarmInfo = new ConcurrentHashMap<>(); + + // 报警确认次数阈值 + private static final int ALARM_CONFIRM_THRESHOLD = 3; + + // 最小报警持续时间(毫秒),默认10秒 + private static final long MIN_ALARM_DURATION = 10 * 1000; @PostMapping("/test1") public R test1(@RequestBody HashMap parma) throws Exception { @@ -191,7 +209,7 @@ public class AppPosiDeviceController { // REPORT_ID不为空且不为空串时必须检查(即使OPEN_ALARM为1也检查) if (reportId != null && !"".equals(reportId.trim())) { - checkThreshold(data); + checkThresholdWithDelayConfirmation(data); } // OPEN_ALARM为1时不检查 else if ("1".equals(openAlarm)) { @@ -199,7 +217,7 @@ public class AppPosiDeviceController { } // 其他情况正常检查(OPEN_ALARM为0或其他值) else { - checkThreshold(data); + checkThresholdWithDelayConfirmation(data); } dataList.add(data); } @@ -285,63 +303,120 @@ public class AppPosiDeviceController { } /** - * 检查阈值是否超出范围 + * 检查阈值是否超出范围(带延时确认机制) * @param data 数据项 */ - private void checkThreshold(Map data) { + private void checkThresholdWithDelayConfirmation(Map data) { try { String currentValueStr = String.valueOf(data.get("CURRENT_VALUE")); String thresholdUpLimitStr = (String) data.get("THRESHOLD_UP_LIMIT"); String thresholdUpUpLimitStr = (String) data.get("THRESHOLD_UP_UP_LIMIT"); String thresholdDownLimitStr = (String) data.get("THRESHOLD_DOWN_LIMIT"); String thresholdDownDownLimitStr = (String) data.get("THRESHOLD_DOWN_DOWN_LIMIT"); + String equipmentId = (String) data.get("EQUIPMENT_ID"); + String plcId = (String) data.get("PLC_ID"); if (currentValueStr == null || "null".equals(currentValueStr)) { return; } double currentValue = Double.parseDouble(currentValueStr); + String equipmentKey = equipmentId + "_" + plcId; + + boolean isAlarm = false; + String alarmType = ""; // 检查高高报 if (thresholdUpUpLimitStr != null && !"".equals(thresholdUpUpLimitStr) && !"null".equals(thresholdUpUpLimitStr)) { double thresholdUpUpLimit = Double.parseDouble(thresholdUpUpLimitStr); if (currentValue >= thresholdUpUpLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "高高报警"); - return; + isAlarm = true; + alarmType = "高高报警"; } } // 检查高报 - if (thresholdUpLimitStr != null && !"".equals(thresholdUpLimitStr) && !"null".equals(thresholdUpLimitStr)) { + if (!isAlarm && thresholdUpLimitStr != null && !"".equals(thresholdUpLimitStr) && !"null".equals(thresholdUpLimitStr)) { double thresholdUpLimit = Double.parseDouble(thresholdUpLimitStr); if (currentValue >= thresholdUpLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "高报警"); - return; + isAlarm = true; + alarmType = "高报警"; } } // 检查低报 - if (thresholdDownLimitStr != null && !"".equals(thresholdDownLimitStr) && !"null".equals(thresholdDownLimitStr)) { + if (!isAlarm && thresholdDownLimitStr != null && !"".equals(thresholdDownLimitStr) && !"null".equals(thresholdDownLimitStr)) { double thresholdDownLimit = Double.parseDouble(thresholdDownLimitStr); if (currentValue >= thresholdDownLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "低报警"); - return; + isAlarm = true; + alarmType = "低报警"; } } // 检查低低报 - if (thresholdDownDownLimitStr != null && !"".equals(thresholdDownDownLimitStr) && !"null".equals(thresholdDownDownLimitStr)) { + if (!isAlarm && thresholdDownDownLimitStr != null && !"".equals(thresholdDownDownLimitStr) && !"null".equals(thresholdDownDownLimitStr)) { double thresholdDownDownLimit = Double.parseDouble(thresholdDownDownLimitStr); if (currentValue >= thresholdDownDownLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "低低报警"); - return; + isAlarm = true; + alarmType = "低低报警"; } } + long currentTimestamp = System.currentTimeMillis(); + + // 处理报警确认逻辑 + if (isAlarm) { + // 如果超过阈值,增加计数 + int currentCount = alarmConfirmCount.getOrDefault(equipmentKey, 0) + 1; + alarmConfirmCount.put(equipmentKey, currentCount); + + // 记录首次报警时间 + if (!alarmStartTimeMap.containsKey(equipmentKey)) { + alarmStartTimeMap.put(equipmentKey, currentTimestamp); + } + + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(equipmentKey); + + // 保存当前报警信息,确保后续处理的是最新的报警数据 + currentAlarmInfo.put(equipmentKey, new HashMap<>(data)); + + // 检查是否满足最小持续时间要求 + Long startTime = alarmStartTimeMap.get(equipmentKey); + if (startTime != null) { + long duration = currentTimestamp - startTime; + if (duration >= MIN_ALARM_DURATION) { + // 持续时间达到要求,真正触发报警 + if (currentCount >= ALARM_CONFIRM_THRESHOLD) { + data.put("WARNING", 1); + data.put("OVERVIEW_OF_ALERTS", alarmType); + XxlJobHelper.log("设备 {} 触发报警: {} (值: {},持续时间: {}ms)", + equipmentKey, alarmType, currentValue, duration); + } + } + } + } else { + // 数据恢复正常,检查是否需要开始恢复计时 + if (alarmStatusMap.containsKey(equipmentKey)) { + // 如果之前有报警状态,开始恢复计时 + if (!alarmRecoverStartTimeMap.containsKey(equipmentKey)) { + alarmRecoverStartTimeMap.put(equipmentKey, currentTimestamp); + XxlJobHelper.log("设备 {} 开始恢复计时", equipmentKey); + } + } else { + // 如果之前没有报警状态,直接清除计数器和开始时间 + alarmConfirmCount.remove(equipmentKey); + alarmStartTimeMap.remove(equipmentKey); + } + + // 清除当前报警信息 + currentAlarmInfo.remove(equipmentKey); + + // 确保WARNING字段为0,表示未报警 + data.put("WARNING", 0); + data.put("OVERVIEW_OF_ALERTS", ""); + } + } catch (Exception e) { XxlJobHelper.log("检查阈值时发生异常: {}", e.getMessage()); } @@ -356,11 +431,11 @@ public class AppPosiDeviceController { ArrayList activeAlarms = new ArrayList<>(); for (Map item : mesSaveList) { - // 检查是否有报警且是模拟量 + // 检查是否有报警 Object warningObj = item.get("WARNING"); if (warningObj != null && "1".equals(String.valueOf(warningObj))) { - // 有告警 且是模拟量 + // 只有当WARNING为1时才创建报警记录,这表示已经通过了延时确认机制 PageData itemPageData = new PageData(); itemPageData.put("ALARM_MONITORING_ID", UuidUtil.get32UUID()); itemPageData.put("PLC_NAME", item.get("PLC_NAME")); @@ -410,6 +485,7 @@ public class AppPosiDeviceController { private void processWarnInfo(ArrayList activeAlarms) { // 获取当前时间 String currentTime = DateUtil.date2Str(new Date()); + long currentTimestamp = System.currentTimeMillis(); // 处理新产生的报警 for (PageData alarm : activeAlarms) { @@ -448,6 +524,9 @@ public class AppPosiDeviceController { // 保存到alarmStatusMap中,用于跟踪状态 alarmStatusMap.put(key, warnInfo); + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(key); + XxlJobHelper.log("创建新的告警信息: 设备ID={}, PLC_ID={}, 报警内容={}", alarm.get("EQUIPMENT_ID"), alarm.get("PLC_ID"), alarm.get("OVERVIEW_OF_ALERTS")); } else { @@ -456,6 +535,9 @@ public class AppPosiDeviceController { existingWarn.put("MESSAGE", "设备名称:"+ alarm.get("DEVICE_ID")+",监测节点:"+ alarm.get("PLC_NAME")+",报警内容:"+ alarm.get("OVERVIEW_OF_ALERTS")); existingWarn.put("OPERATTIME", currentTime); alarmStatusMap.put(key, existingWarn); + + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(key); } } @@ -474,7 +556,24 @@ public class AppPosiDeviceController { PageData warnInfo = entry.getValue(); if (!activeAlarmKeys.contains(key)) { - // 该节点之前报警,但现在不报警了,需要更新状态为已消警 + // 检查是否满足最小恢复持续时间要求 + Long recoverStartTime = alarmRecoverStartTimeMap.get(key); + if (recoverStartTime != null) { + long recoverDuration = currentTimestamp - recoverStartTime; + if (recoverDuration < MIN_ALARM_DURATION) { + // 未达到最小恢复持续时间,暂不处理消警 + XxlJobHelper.log("设备 {} 恢复未达到最小持续时间 {}ms,当前持续时间 {}ms,暂不消警", + key, MIN_ALARM_DURATION, recoverDuration); + continue; + } + } else { + // 如果还没有开始恢复计时,开始计时 + alarmRecoverStartTimeMap.put(key, currentTimestamp); + XxlJobHelper.log("设备 {} 开始恢复计时", key); + continue; + } + + // 该节点之前报警,现在已经满足恢复条件,需要更新状态为已消警 warnInfo.put("WARN_STATUS", "1"); // 1表示已消警 warnInfo.put("END_TIME", currentTime); // 预警结束时间 warnInfo.put("OPERATTIME", currentTime); @@ -487,6 +586,12 @@ public class AppPosiDeviceController { // 从跟踪map中移除 iterator.remove(); + + // 同时清除报警确认计数器、开始时间记录和恢复开始时间记录 + alarmConfirmCount.remove(key); + alarmStartTimeMap.remove(key); + alarmRecoverStartTimeMap.remove(key); + currentAlarmInfo.remove(key); } } } diff --git a/src/main/java/com/zcloud/scheduled/dataDocking/MesDataScheduled.java b/src/main/java/com/zcloud/scheduled/dataDocking/MesDataScheduled.java index 5aef0cd..ef8074b 100644 --- a/src/main/java/com/zcloud/scheduled/dataDocking/MesDataScheduled.java +++ b/src/main/java/com/zcloud/scheduled/dataDocking/MesDataScheduled.java @@ -54,6 +54,24 @@ public class MesDataScheduled extends IJobHandler { // 用于跟踪设备节点的报警状态 private final Map alarmStatusMap = new ConcurrentHashMap<>(); + + // 用于报警延时确认机制的计数器 + private final Map alarmConfirmCount = new ConcurrentHashMap<>(); + + // 用于跟踪报警开始时间的映射 + private final Map alarmStartTimeMap = new ConcurrentHashMap<>(); + + // 用于跟踪报警恢复开始时间的映射 + private final Map alarmRecoverStartTimeMap = new ConcurrentHashMap<>(); + + // 用于存储当前报警信息的映射(避免存入过期的报警信息) + private final Map> currentAlarmInfo = new ConcurrentHashMap<>(); + + // 报警确认次数阈值 + private static final int ALARM_CONFIRM_THRESHOLD = 3; + + // 最小报警持续时间(毫秒),默认10秒 + private static final long MIN_ALARM_DURATION = 10 * 1000; @Override @XxlJob(value = "mesDataJob") @@ -325,7 +343,7 @@ public class MesDataScheduled extends IJobHandler { // REPORT_ID不为空且不为空串时必须检查(即使OPEN_ALARM为1也检查) if (reportId != null && !"".equals(reportId.trim())) { - checkThreshold(mesData); + checkThresholdWithDelayConfirmation(mesData); } // OPEN_ALARM为1时不检查 else if ("1".equals(openAlarm)) { @@ -333,7 +351,7 @@ public class MesDataScheduled extends IJobHandler { } // 其他情况正常检查(OPEN_ALARM为0或其他值) else { - checkThreshold(mesData); + checkThresholdWithDelayConfirmation(mesData); } } } else { @@ -418,10 +436,10 @@ public class MesDataScheduled extends IJobHandler { } /** - * 检查阈值是否超出范围 + * 检查阈值是否超出范围(带延时确认机制) * @param data 数据项 */ - private void checkThreshold(Map data) { + private void checkThresholdWithDelayConfirmation(Map data) { try { String signalType = (String) data.get("SIGNAL_TYPE"); // 只处理模拟量数据(信号类型为"1") @@ -431,52 +449,109 @@ public class MesDataScheduled extends IJobHandler { String thresholdUpUpLimitStr = (String) data.get("THRESHOLD_UP_UP_LIMIT"); String thresholdDownLimitStr = (String) data.get("THRESHOLD_DOWN_LIMIT"); String thresholdDownDownLimitStr = (String) data.get("THRESHOLD_DOWN_DOWN_LIMIT"); + String equipmentId = (String) data.get("EQUIPMENT_ID"); + String plcId = (String) data.get("PLC_ID"); if (currentValueStr == null || "null".equals(currentValueStr)) { return; } double currentValue = Double.parseDouble(currentValueStr); + String equipmentKey = equipmentId + "_" + plcId; + + boolean isAlarm = false; + String alarmType = ""; // 检查高高报 if (thresholdUpUpLimitStr != null && !"".equals(thresholdUpUpLimitStr) && !"null".equals(thresholdUpUpLimitStr)) { double thresholdUpUpLimit = Double.parseDouble(thresholdUpUpLimitStr); if (currentValue >= thresholdUpUpLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "高高报警"); - return; + isAlarm = true; + alarmType = "高高报警"; } } // 检查高报 - if (thresholdUpLimitStr != null && !"".equals(thresholdUpLimitStr) && !"null".equals(thresholdUpLimitStr)) { + if (!isAlarm && thresholdUpLimitStr != null && !"".equals(thresholdUpLimitStr) && !"null".equals(thresholdUpLimitStr)) { double thresholdUpLimit = Double.parseDouble(thresholdUpLimitStr); if (currentValue >= thresholdUpLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "高报警"); - return; + isAlarm = true; + alarmType = "高报警"; } } // 检查低报 - if (thresholdDownLimitStr != null && !"".equals(thresholdDownLimitStr) && !"null".equals(thresholdDownLimitStr)) { + if (!isAlarm && thresholdDownLimitStr != null && !"".equals(thresholdDownLimitStr) && !"null".equals(thresholdDownLimitStr)) { double thresholdDownLimit = Double.parseDouble(thresholdDownLimitStr); if (currentValue <= thresholdDownLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "低报警"); - return; + isAlarm = true; + alarmType = "低报警"; } } // 检查低低报 - if (thresholdDownDownLimitStr != null && !"".equals(thresholdDownDownLimitStr) && !"null".equals(thresholdDownDownLimitStr)) { + if (!isAlarm && thresholdDownDownLimitStr != null && !"".equals(thresholdDownDownLimitStr) && !"null".equals(thresholdDownDownLimitStr)) { double thresholdDownDownLimit = Double.parseDouble(thresholdDownDownLimitStr); if (currentValue <= thresholdDownDownLimit) { - data.put("WARNING", 1); - data.put("OVERVIEW_OF_ALERTS", "低低报警"); - return; + isAlarm = true; + alarmType = "低低报警"; } } + + long currentTimestamp = System.currentTimeMillis(); + + // 处理报警确认逻辑 + if (isAlarm) { + // 如果超过阈值,增加计数 + int currentCount = alarmConfirmCount.getOrDefault(equipmentKey, 0) + 1; + alarmConfirmCount.put(equipmentKey, currentCount); + + // 记录首次报警时间 + if (!alarmStartTimeMap.containsKey(equipmentKey)) { + alarmStartTimeMap.put(equipmentKey, currentTimestamp); + } + + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(equipmentKey); + + // 保存当前报警信息,确保后续处理的是最新的报警数据 + currentAlarmInfo.put(equipmentKey, new HashMap<>(data)); + + // 检查是否满足最小持续时间要求 + Long startTime = alarmStartTimeMap.get(equipmentKey); + if (startTime != null) { + long duration = currentTimestamp - startTime; + if (duration >= MIN_ALARM_DURATION) { + // 持续时间达到要求,真正触发报警 + if (currentCount >= ALARM_CONFIRM_THRESHOLD) { + data.put("WARNING", 1); + data.put("OVERVIEW_OF_ALERTS", alarmType); + XxlJobHelper.log("设备 {} 触发报警: {} (值: {},持续时间: {}ms)", + equipmentKey, alarmType, currentValue, duration); + } + } + } + } else { + // 数据恢复正常,检查是否需要开始恢复计时 + if (alarmStatusMap.containsKey(equipmentKey)) { + // 如果之前有报警状态,开始恢复计时 + if (!alarmRecoverStartTimeMap.containsKey(equipmentKey)) { + alarmRecoverStartTimeMap.put(equipmentKey, currentTimestamp); + XxlJobHelper.log("设备 {} 开始恢复计时", equipmentKey); + } + } else { + // 如果之前没有报警状态,直接清除计数器和开始时间 + alarmConfirmCount.remove(equipmentKey); + alarmStartTimeMap.remove(equipmentKey); + } + + // 清除当前报警信息 + currentAlarmInfo.remove(equipmentKey); + + // 确保WARNING字段为0,表示未报警 + data.put("WARNING", 0); + data.put("OVERVIEW_OF_ALERTS", ""); + } } else { // 处理开关量报警 String currentValueStr = String.valueOf(data.get("CURRENT_VALUE")); @@ -492,7 +567,15 @@ public class MesDataScheduled extends IJobHandler { if (currentValue == alarmValue) { data.put("WARNING", 1); data.put("OVERVIEW_OF_ALERTS", "联锁投入报警"); + } else { + // 确保WARNING字段为0,表示未报警 + data.put("WARNING", 0); + data.put("OVERVIEW_OF_ALERTS", ""); } + } else { + // 确保WARNING字段为0,表示未报警 + data.put("WARNING", 0); + data.put("OVERVIEW_OF_ALERTS", ""); } } } catch (Exception e) { @@ -513,7 +596,7 @@ public class MesDataScheduled extends IJobHandler { Object warningObj = item.get("WARNING"); if (warningObj != null && "1".equals(String.valueOf(warningObj))) { - // 有告警 且是模拟量 + // 只有当WARNING为1时才创建报警记录,这表示已经通过了延时确认机制 PageData itemPageData = new PageData(); itemPageData.put("ALARM_MONITORING_ID", UuidUtil.get32UUID()); itemPageData.put("PLC_NAME", item.get("PLC_NAME")); @@ -564,6 +647,7 @@ public class MesDataScheduled extends IJobHandler { private void processWarnInfo(ArrayList activeAlarms, ArrayList> allData) { // 获取当前时间 String currentTime = DateUtil.date2Str(new Date()); + long currentTimestamp = System.currentTimeMillis(); // 处理新产生的报警 for (PageData alarm : activeAlarms) { @@ -576,11 +660,11 @@ public class MesDataScheduled extends IJobHandler { warnInfo.put("WARN_ID", UuidUtil.get32UUID()); warnInfo.put("WARN_STATUS", "0"); // 0表示未消警 warnInfo.put("WARN_ORG", "山西新泰钢铁有限公司"); // 0表示未消警 - if (alarm.get("OVERVIEW_OF_ALERTS")=="低低报警" || alarm.get("OVERVIEW_OF_ALERTS")=="低报警"){ + if ("低低报警".equals(alarm.get("OVERVIEW_OF_ALERTS")) || "低报警".equals(alarm.get("OVERVIEW_OF_ALERTS"))){ warnInfo.put("WARN_RANK", "3"); - }else if (alarm.get("OVERVIEW_OF_ALERTS")=="高报警"){ + }else if ("高报警".equals(alarm.get("OVERVIEW_OF_ALERTS"))){ warnInfo.put("WARN_RANK", "2"); - }else if (alarm.get("OVERVIEW_OF_ALERTS")=="高高报警"){ + }else if ("高高报警".equals(alarm.get("OVERVIEW_OF_ALERTS"))){ warnInfo.put("WARN_RANK", "1"); } warnInfo.put("MESSAGE", "设备名称:"+ alarm.get("DEVICE_ID")+",监测节点:"+ alarm.get("PLC_NAME")+",报警内容:"+ alarm.get("OVERVIEW_OF_ALERTS")); @@ -602,6 +686,9 @@ public class MesDataScheduled extends IJobHandler { // 保存到alarmStatusMap中,用于跟踪状态 alarmStatusMap.put(key, warnInfo); + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(key); + XxlJobHelper.log("创建新的告警信息: 设备ID={}, PLC_ID={}, 报警内容={}", alarm.get("EQUIPMENT_ID"), alarm.get("PLC_ID"), alarm.get("OVERVIEW_OF_ALERTS")); } else { @@ -610,6 +697,9 @@ public class MesDataScheduled extends IJobHandler { existingWarn.put("MESSAGE", "设备名称:"+ alarm.get("DEVICE_ID")+",监测节点:"+ alarm.get("PLC_NAME")+",报警内容:"+ alarm.get("OVERVIEW_OF_ALERTS")); existingWarn.put("OPERATTIME", currentTime); alarmStatusMap.put(key, existingWarn); + + // 清除恢复计时(如果有的话) + alarmRecoverStartTimeMap.remove(key); } } @@ -628,7 +718,24 @@ public class MesDataScheduled extends IJobHandler { PageData warnInfo = entry.getValue(); if (!activeAlarmKeys.contains(key)) { - // 该节点之前报警,但现在不报警了,需要更新状态为已消警 + // 检查是否满足最小恢复持续时间要求 + Long recoverStartTime = alarmRecoverStartTimeMap.get(key); + if (recoverStartTime != null) { + long recoverDuration = currentTimestamp - recoverStartTime; + if (recoverDuration < MIN_ALARM_DURATION) { + // 未达到最小恢复持续时间,暂不处理消警 + XxlJobHelper.log("设备 {} 恢复未达到最小持续时间 {}ms,当前持续时间 {}ms,暂不消警", + key, MIN_ALARM_DURATION, recoverDuration); + continue; + } + } else { + // 如果还没有开始恢复计时,开始计时 + alarmRecoverStartTimeMap.put(key, currentTimestamp); + XxlJobHelper.log("设备 {} 开始恢复计时", key); + continue; + } + + // 该节点之前报警,现在已经满足恢复条件,需要更新状态为已消警 warnInfo.put("WARN_STATUS", "1"); // 1表示已消警 warnInfo.put("END_TIME", currentTime); // 预警结束时间 warnInfo.put("OPERATTIME", currentTime); @@ -641,6 +748,12 @@ public class MesDataScheduled extends IJobHandler { // 从跟踪map中移除 iterator.remove(); + + // 同时清除报警确认计数器、开始时间记录和恢复开始时间记录 + alarmConfirmCount.remove(key); + alarmStartTimeMap.remove(key); + alarmRecoverStartTimeMap.remove(key); + currentAlarmInfo.remove(key); } } }