package jnpf.task;
import jnpf.entity.EquipmentparameterEntity;
import jnpf.entity.UleabnormalrecordsEntity;
import jnpf.myutils.DateUtils;
import jnpf.service.EquipmentparameterService;
import jnpf.service.RealDataService;
import jnpf.service.UleabnormalrecordsService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.;
import java.time.format.DateTimeFormatter;
import java.util.;
import java.util.stream.Collectors;
@Slf4j
@Component
public class HAHFAnalysisTask {
private static final Logger logger = LoggerFactory.getLogger(HAHFAnalysisTask.class); @Autowired private EquipmentparameterService equipmentparameterService; @Autowired private UleabnormalrecordsService uleabnormalrecordsService; @Autowired private RealDataService realDataService; //@XxlJob("hahfAnalysis") public void hahfAnalysis() { logger.info("HAHF分析任务启动"); // 1. 获取TSP设备列表 List<EquipmentparameterEntity> equipmentList = equipmentparameterService.geHAHFList(); if (equipmentList.isEmpty()) { logger.info("没有可分析的TSP设备"); return; } // 2. 准备查询参数 Map<String, Object> params = new HashMap<>(); List<String> tags = equipmentList.stream() .map(EquipmentparameterEntity::getTag) .collect(Collectors.toList()); params.put("tags", tags); // 3. 获取日常值数据 params.put("begindate", DateUtils.dataTimeformat(new Date())); List<Map> dailyValueMaps = realDataService.selectLastmonthDailyvalue(params); Map<String, Map> dailyValueMap = dailyValueMaps.stream() .collect(Collectors.toMap( m -> (String) m.get("datag"), m -> m )); // 4. 获取小时数据(前一天到当前) params.put("begindate", timeCalculation()); params.put("enddate", DateUtils.dataTimeformat(new Date())); List<Map> hourDataMaps = realDataService.selectHourdataByTagIdsAndDate(params); // 5. 按tag分组小时数据 Map<String, List<Map>> hourDataByTag = hourDataMaps.stream() .collect(Collectors.groupingBy( m -> (String) m.get("datag") )); // 6. 获取当前报警中的记录 List<UleabnormalrecordsEntity> activeAlarms = uleabnormalrecordsService.getOnhahfList(); Map<String, UleabnormalrecordsEntity> activeAlarmMap = activeAlarms.stream() .collect(Collectors.toMap( UleabnormalrecordsEntity::getParacode, alarm -> alarm )); // 7. 处理每个设备 for (EquipmentparameterEntity equipment : equipmentList) { try { processEquipment( equipment, dailyValueMap, hourDataByTag, activeAlarmMap ); } catch (Exception e) { logger.error("处理设备{}失败: {}", equipment.getEncode(), e.getMessage()); } } logger.info("HAHF分析任务完成"); } private void processEquipment(EquipmentparameterEntity equipment, Map<String, Map> dailyValueMap, Map<String, List<Map>> hourDataByTag, Map<String, UleabnormalrecordsEntity> activeAlarmMap) { String tag = equipment.getTag(); String paraCode = equipment.getEncode(); // 获取该tag的日常值配置 Map dailyConfig = dailyValueMap.get(tag); if (dailyConfig == null) { logger.warn("设备{}缺少日常值配置", paraCode); return; } double farBeyondValue = ((Number) dailyConfig.get("farbeyondvalue")).doubleValue(); double limitingValue = ((Number) dailyConfig.get("limitingvalue")).doubleValue(); // 获取该tag的小时数据 List<Map> hourDataList = hourDataByTag.getOrDefault(tag, new ArrayList<>()); // 优化点3:检查阈值条件 if (hourDataList.isEmpty()) { logger.info("设备{}没有小时数据", paraCode); return; } double minValue = hourDataList.stream() .mapToDouble(map -> ((Number) map.get("avgdata")).doubleValue()) .min() .orElse(Double.MAX_VALUE); double maxValue = hourDataList.stream() .mapToDouble(map -> ((Number) map.get("avgdata")).doubleValue()) .max() .orElse(Double.MIN_VALUE); // 条件:最小值 > limitingValue 且 最大值 > farBeyondValue if (!(minValue > limitingValue && maxValue > farBeyondValue)) { logger.info("设备{}数据不满足阈值条件: minValue={} (需>{}), maxValue={} (需>{})", paraCode, minValue, limitingValue, maxValue, farBeyondValue); return; } // 处理报警状态 if (activeAlarmMap.containsKey(paraCode)) { handleActiveAlarm( activeAlarmMap.get(paraCode), hourDataList, farBeyondValue, limitingValue ); } else { handleNewAlarm( equipment, hourDataList, farBeyondValue, limitingValue ); } } /** * 处理新报警检测 */ private void handleNewAlarm(EquipmentparameterEntity equipment, List<Map> hourDataList, double farBeyondValue, double limitingValue) { // 1. 准备时间序列数据 List<DataPoint> dataPoints = hourDataList.stream() .map(map -> new DataPoint( (String) map.get("datadt"), ((Number) map.get("avgdata")).doubleValue() )) .sorted(Comparator.comparing(DataPoint::getDateTime)) .collect(Collectors.toList()); // 2. 寻找所有符合条件的连续时间段 List<TimePeriod> validPeriods = findValidPeriods(dataPoints, farBeyondValue, limitingValue); // 3. 合并重叠时间段 List<TimePeriod> mergedPeriods = mergePeriods(validPeriods); // 4. 创建报警记录 for (TimePeriod period : mergedPeriods) { // 优化点4:使用实际发生的小时时间作为报警时间 LocalDateTime actualStart = findActualStartTime(dataPoints, period.start, farBeyondValue, limitingValue); LocalDateTime actualEnd = period.end != null ? findActualEndTime(dataPoints, period.end, farBeyondValue, limitingValue) : null; UleabnormalrecordsEntity record = new UleabnormalrecordsEntity(); record.setParacode(equipment.getEncode()); record.setAbnormaltime(toDate(actualStart)); // 设置实际开始时间 record.setCompletetime(toDate(actualEnd)); // 设置实际结束时间 // 设置其他必要字段 record.setAbnormaltype("HAHF"); // 高频高幅报警 record.setAbnormalstate(actualEnd == null ? "0" : "1"); // 0-报警中, 1-已结束 uleabnormalrecordsService.save(record); logger.info("创建新报警记录: {} - {} 至 {}", equipment.getEncode(), actualStart, actualEnd); } } /** * 处理活跃报警的恢复检测 */ private void handleActiveAlarm(UleabnormalrecordsEntity activeAlarm, List<Map> hourDataList, double farBeyondValue, double limitingValue) { // 1. 将Date转换为LocalDateTime LocalDateTime alarmStartTime = toLocalDateTime(activeAlarm.getAbnormaltime()); // 2. 准备时间序列数据(从报警开始时间起) List<DataPoint> dataPoints = hourDataList.stream() .map(map -> new DataPoint( (String) map.get("datadt"), ((Number) map.get("avgdata")).doubleValue() )) .filter(point -> !point.dateTime.isBefore(alarmStartTime)) .sorted(Comparator.comparing(DataPoint::getDateTime)) .collect(Collectors.toList()); // 3. 检查是否恢复 LocalDateTime recoveryTime = checkRecovery( dataPoints, alarmStartTime, farBeyondValue, limitingValue ); // 优化点2:仅当报警结束才更新记录 if (recoveryTime != null) { // 优化点4:使用实际恢复的小时时间 LocalDateTime actualRecoveryTime = findActualEndTime(dataPoints, recoveryTime, farBeyondValue, limitingValue); activeAlarm.setCompletetime(toDate(actualRecoveryTime)); activeAlarm.setAbnormalstate("1"); // 设置为已结束 uleabnormalrecordsService.updateById(activeAlarm); logger.info("报警{}已恢复,结束时间: {}", activeAlarm.getId(), actualRecoveryTime); } else { logger.info("报警{}仍在持续中", activeAlarm.getId()); } } /** * 核心算法:寻找有效时间段(处理数据缺失) */ private List<TimePeriod> findValidPeriods(List<DataPoint> dataPoints, double farBeyondValue, double limitingValue) { List<TimePeriod> validPeriods = new ArrayList<>(); if (dataPoints.size() < 24) return validPeriods; // 按日期分组数据(处理数据缺失) Map<LocalDate, List<DataPoint>> dataByDate = dataPoints.stream() .collect(Collectors.groupingBy( point -> point.dateTime.toLocalDate() )); List<LocalDate> sortedDates = new ArrayList<>(dataByDate.keySet()); Collections.sort(sortedDates); // 使用滑动窗口检测连续日期 int startDateIdx = 0; while (startDateIdx <= sortedDates.size() - 1) { LocalDate startDate = sortedDates.get(startDateIdx); LocalDate endDate = startDate.plusDays(1); // 获取24小时时间段内的数据 List<DataPoint> windowData = new ArrayList<>(); for (int i = startDateIdx; i < sortedDates.size(); i++) { LocalDate date = sortedDates.get(i); if (date.isAfter(endDate)) break; windowData.addAll(dataByDate.get(date)); } // 按时间排序窗口数据 windowData.sort(Comparator.comparing(DataPoint::getDateTime)); // 优化点1:处理数据缺失的分桶分析 if (isValidPeriod(windowData, farBeyondValue, limitingValue)) { LocalDateTime periodStart = findActualStartTime(windowData, windowData.get(0).dateTime, farBeyondValue, limitingValue); LocalDateTime periodEnd = windowData.get(windowData.size() - 1).dateTime; // 对于当前时间仍在持续的报警,结束时间为null LocalDateTime lastPossibleTime = LocalDateTime.now().minusHours(1); LocalDateTime actualEnd = periodEnd.isBefore(lastPossibleTime) ? periodEnd : null; validPeriods.add(new TimePeriod(periodStart, actualEnd)); startDateIdx += 1; // 移动到下一天 } else { startDateIdx++; } } return validPeriods; } /** * 检查24小时窗口是否有效(处理数据缺失) */ private boolean isValidPeriod(List<DataPoint> dataPoints, double farBeyondValue, double limitingValue) { if (dataPoints.isEmpty()) return false; // 检查限制值条件 boolean hasLimitingValue = dataPoints.stream() .anyMatch(point -> point.value >= limitingValue); if (!hasLimitingValue) return false; // 优化点1:处理数据缺失的分桶分析 // 按6小时分桶组织数据 Map<Integer, List<DataPoint>> bucketMap = new HashMap<>(); for (DataPoint point : dataPoints) { int bucketIdx = (point.dateTime.getHour() / 6) % 4; bucketMap.computeIfAbsent(bucketIdx, k -> new ArrayList<>()).add(point); } // 检查每个桶是否满足条件(允许数据缺失) for (int bucketIdx = 0; bucketIdx < 4; bucketIdx++) { List<DataPoint> bucketData = bucketMap.getOrDefault(bucketIdx, new ArrayList<>()); // 如果桶内有数据,检查是否有超过远高于日常值的点 if (!bucketData.isEmpty()) { boolean hasValidPoint = bucketData.stream() .anyMatch(point -> point.value >= farBeyondValue); if (!hasValidPoint) { return false; } } else { // 优化点1:如果桶内没有数据,视为条件不满足 return false; } } return true; } /** * 合并重叠时间段 */ private List<TimePeriod> mergePeriods(List<TimePeriod> periods) { if (periods.isEmpty()) return periods; List<TimePeriod> merged = new ArrayList<>(); periods.sort(Comparator.comparing(p -> p.start)); TimePeriod current = periods.get(0); for (int i = 1; i < periods.size(); i++) { TimePeriod next = periods.get(i); // 检查时间段是否重叠或连续 boolean shouldMerge = false; if (current.end != null && next.start != null) { shouldMerge = !next.start.isAfter(current.end.plusHours(1)); } else if (current.end == null) { // 当前时间段仍在持续,合并所有后续时间段 shouldMerge = true; } if (shouldMerge) { // 合并时间段 if (next.end == null) { current.end = null; // 合并后仍在持续 } else if (current.end == null || next.end.isAfter(current.end)) { current.end = next.end; } } else { merged.add(current); current = next; } } merged.add(current); return merged; } /** * 检查报警恢复(处理数据缺失) */ private LocalDateTime checkRecovery(List<DataPoint> dataPoints, LocalDateTime startTime, double farBeyondValue, double limitingValue) { if (dataPoints.isEmpty()) return null; // 按6小时分桶组织数据 Map<Integer, List<DataPoint>> bucketMap = new HashMap<>(); for (DataPoint point : dataPoints) { int bucketIdx = (point.dateTime.getHour() / 6) % 4; bucketMap.computeIfAbsent(bucketIdx, k -> new ArrayList<>()).add(point); } // 检查每个桶是否仍有有效数据点 for (int bucketIdx = 0; bucketIdx < 4; bucketIdx++) { List<DataPoint> bucketData = bucketMap.getOrDefault(bucketIdx, new ArrayList<>()); // 优化点1:处理数据缺失 - 如果桶内没有数据,视为未恢复 if (bucketData.isEmpty()) { return null; } // 检查桶内是否有有效点 boolean hasValidPoint = bucketData.stream() .anyMatch(point -> point.value >= farBeyondValue); // 如果某个桶没有有效点,取该桶最早时间作为恢复时间候选 if (!hasValidPoint) { // 找到该桶中最后一个数据点的时间 LocalDateTime bucketEnd = bucketData.get(bucketData.size() - 1).dateTime; // 验证在恢复时间后是否还有有效点 boolean laterValid = dataPoints.stream() .filter(point -> point.dateTime.isAfter(bucketEnd)) .anyMatch(point -> point.value >= farBeyondValue); if (!laterValid) { return bucketEnd; } } } return null; } /** * 优化点4:找到实际报警开始时间(第一个超过阈值的小时) */ private LocalDateTime findActualStartTime(List<DataPoint> dataPoints, LocalDateTime windowStart, double farBeyondValue, double limitingValue) { return dataPoints.stream() .filter(point -> !point.dateTime.isBefore(windowStart) && (point.value >= farBeyondValue || point.value >= limitingValue)) .min(Comparator.comparing(DataPoint::getDateTime)) .map(point -> point.dateTime) .orElse(windowStart); } /** * 优化点4:找到实际报警结束时间(最后一个超过阈值的小时) */ private LocalDateTime findActualEndTime(List<DataPoint> dataPoints, LocalDateTime windowEnd, double farBeyondValue, double limitingValue) { return dataPoints.stream() .filter(point -> !point.dateTime.isAfter(windowEnd) && (point.value >= farBeyondValue || point.value >= limitingValue)) .max(Comparator.comparing(DataPoint::getDateTime)) .map(point -> point.dateTime) .orElse(windowEnd); } /** * 辅助类:数据点 */ private static class DataPoint { LocalDateTime dateTime; double value; DataPoint(String dateStr, double value) { // 处理可能的时间格式差异 if (dateStr.length() == 13) { // "yyyy-MM-dd HH" this.dateTime = LocalDateTime.parse(dateStr + ":00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } else { this.dateTime = LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } this.value = value; } LocalDateTime getDateTime() { return dateTime; } } /** * 辅助类:时间段 */ private static class TimePeriod { LocalDateTime start; LocalDateTime end; // null表示仍在持续 TimePeriod(LocalDateTime start, LocalDateTime end) { this.start = start; this.end = end; } } /** * 获取前一天开始时间 */ private String timeCalculation() { LocalDate yesterday = LocalDate.now().minusDays(1); LocalDateTime startOfYesterday = yesterday.atStartOfDay(); return startOfYesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } /** * LocalDateTime 转 Date */ private Date toDate(LocalDateTime localDateTime) { if (localDateTime == null) return null; return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } /** * Date 转 LocalDateTime */ private LocalDateTime toLocalDateTime(Date date) { if (date == null) return null; return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); }
}
设备在时间区间内是一条完整报警记录,是否有该情况处理