|
|
|
@ -1,25 +1,22 @@
|
|
|
|
|
package com.os.ems.record.service.impl;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import com.os.common.utils.DateUtils;
|
|
|
|
|
import com.os.common.utils.StringUtils;
|
|
|
|
|
import com.os.ems.base.domain.EmsBaseCollectDeviceInfo;
|
|
|
|
|
import com.os.ems.base.domain.EmsBaseMonitorThreshold;
|
|
|
|
|
import com.os.ems.base.mapper.EmsBaseCollectDeviceInfoMapper;
|
|
|
|
|
import com.os.ems.base.mapper.EmsBaseMonitorThresholdMapper;
|
|
|
|
|
import com.os.ems.base.domain.EmsBaseEnergyType;
|
|
|
|
|
import com.os.ems.base.domain.EmsBaseMonitorInfo;
|
|
|
|
|
import com.os.ems.base.mapper.EmsBaseEnergyTypeMapper;
|
|
|
|
|
import com.os.ems.base.mapper.EmsBaseMonitorInfoMapper;
|
|
|
|
|
import com.os.ems.record.domain.EmsRecordAlarmRule;
|
|
|
|
|
import com.os.ems.record.domain.EmsRecordDnbInstant;
|
|
|
|
|
import com.os.ems.record.domain.RecordIotenvInstant;
|
|
|
|
|
import com.os.ems.record.mapper.EmsRecordAlarmRuleMapper;
|
|
|
|
|
import com.os.ems.record.mapper.EmsRecordDnbInstantMapper;
|
|
|
|
|
import com.os.ems.report.domain.EmsReportPointDnb;
|
|
|
|
|
import com.os.ems.report.mapper.EmsReportPointDnbMapper;
|
|
|
|
|
import com.os.ems.record.mapper.RecordIotenvInstantMapper;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import com.os.ems.record.mapper.EmsRecordAlarmDataMapper;
|
|
|
|
|
import com.os.ems.record.domain.EmsRecordAlarmData;
|
|
|
|
@ -42,16 +39,38 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
private EmsRecordAlarmRuleMapper emsRecordAlarmRuleMapper;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private EmsBaseCollectDeviceInfoMapper emsBaseCollectDeviceInfoMapper;
|
|
|
|
|
private RecordIotenvInstantMapper recordIotenvInstantMapper;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private EmsRecordDnbInstantMapper emsRecordDnbInstantMapper;
|
|
|
|
|
private EmsBaseMonitorInfoMapper emsBaseMonitorInfoMapper;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private EmsBaseMonitorThresholdMapper emsBaseMonitorThresholdMapper;
|
|
|
|
|
private EmsBaseEnergyTypeMapper emsBaseEnergyTypeMapper;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private EmsReportPointDnbMapper emsReportPointDnbMapper;
|
|
|
|
|
/**
|
|
|
|
|
* 监测字段字典映射
|
|
|
|
|
* 将前端字典值映射到数据库字段名和中文描述
|
|
|
|
|
*/
|
|
|
|
|
private static final Map<Long, String> MONITOR_FIELD_TO_DB_FIELD = new HashMap<>();
|
|
|
|
|
private static final Map<Long, String> MONITOR_FIELD_TO_DESC = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
// 字典值 -> 数据库字段名映射
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(0L, "temperature"); // 温度
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(1L, "humidity"); // 湿度
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(2L, "vibration_speed"); // 振动-速度(mm/s)
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(3L, "vibration_displacement"); // 振动-位移(um)
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(4L, "vibration_acceleration"); // 振动-加速度(g)
|
|
|
|
|
MONITOR_FIELD_TO_DB_FIELD.put(5L, "vibration_temp"); // 振动-温度(℃)
|
|
|
|
|
|
|
|
|
|
// 字典值 -> 中文描述映射(用于异常记录的cause字段)
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(0L, "温度");
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(1L, "湿度");
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(2L, "振动-速度(mm/s)");
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(3L, "振动-位移(um)");
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(4L, "振动-加速度(g)");
|
|
|
|
|
MONITOR_FIELD_TO_DESC.put(5L, "振动-温度(℃)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询异常数据记录
|
|
|
|
@ -141,172 +160,279 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 采集设备告警定时任务
|
|
|
|
|
* 物联网数据阈值检查定时任务
|
|
|
|
|
* 每分钟执行一次,检查当天分表中的最新数据是否超过阈值
|
|
|
|
|
*
|
|
|
|
|
* 重构说明:
|
|
|
|
|
* 1. 改为基于monitor_field字段的精确匹配,而非按设备类型检查所有字段
|
|
|
|
|
* 2. 支持多字段设备的独立阈值规则(如振动设备的4个字段可设置不同阈值)
|
|
|
|
|
* 3. 优化异常记录的cause字段,使用字典描述而非数据库字段名
|
|
|
|
|
* 4. 修复数据关联:monitor_id存储的是monitor_code值,与采集数据分表保持一致
|
|
|
|
|
* 5. 完全重构去重逻辑:确保绝对不会产生重复数据
|
|
|
|
|
*/
|
|
|
|
|
public void collectDeviceAlarmsTask() {
|
|
|
|
|
long minuteValue = 1000 * 60;
|
|
|
|
|
Date date = new Date();
|
|
|
|
|
EmsRecordAlarmRule alarmRule = new EmsRecordAlarmRule();
|
|
|
|
|
alarmRule.setTriggerRule(1L);
|
|
|
|
|
List<EmsRecordAlarmRule> alarmRules = emsRecordAlarmRuleMapper.selectEmsRecordAlarmRuleList(alarmRule);
|
|
|
|
|
if (alarmRules.size() > 0) {
|
|
|
|
|
minuteValue = alarmRules.get(0).getDeviceOfflineTime() * minuteValue;
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@Scheduled(cron = "0 */1 * * * ?") // 每分钟执行一次
|
|
|
|
|
@Override
|
|
|
|
|
public void checkIotenvThresholdAlarms() {
|
|
|
|
|
try {
|
|
|
|
|
// 1. 获取当天的分表名
|
|
|
|
|
SimpleDateFormat tableFormat = new SimpleDateFormat("yyyyMMdd");
|
|
|
|
|
String tableName = "record_iotenv_instant_" + tableFormat.format(new Date());
|
|
|
|
|
|
|
|
|
|
EmsBaseCollectDeviceInfo collectDeviceInfo = new EmsBaseCollectDeviceInfo();
|
|
|
|
|
collectDeviceInfo.setIsFlag("1");
|
|
|
|
|
List<EmsBaseCollectDeviceInfo> deviceInfoList = emsBaseCollectDeviceInfoMapper.selectEmsBaseCollectDeviceInfoList(collectDeviceInfo);
|
|
|
|
|
// 检查表是否存在
|
|
|
|
|
if (!checkTableExists(tableName)) {
|
|
|
|
|
System.out.println("当天分表不存在: " + tableName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deviceInfoList.size() > 0) {
|
|
|
|
|
EmsRecordAlarmData recordAlarmData = new EmsRecordAlarmData();
|
|
|
|
|
recordAlarmData.setAlarmType(1L);
|
|
|
|
|
recordAlarmData.setAlarmStatus(1L);
|
|
|
|
|
List<EmsRecordAlarmData> alarmDataList = emsRecordAlarmDataMapper.selectEmsRecordAlarmDataList(recordAlarmData);
|
|
|
|
|
List<String> collectDeviceIdList = alarmDataList.stream().map(EmsRecordAlarmData::getCollectDeviceId).collect(Collectors.toList());
|
|
|
|
|
for (EmsBaseCollectDeviceInfo deviceInfo : deviceInfoList) {
|
|
|
|
|
if (collectDeviceIdList.contains(deviceInfo.getCollectDeviceId())) {
|
|
|
|
|
// 2. 查询所有启用的告警规则(trigger_rule=0 超过阈值)
|
|
|
|
|
EmsRecordAlarmRule queryRule = new EmsRecordAlarmRule();
|
|
|
|
|
queryRule.setTriggerRule(0L); // 0表示超过阈值
|
|
|
|
|
List<EmsRecordAlarmRule> alarmRules = emsRecordAlarmRuleMapper.selectEmsRecordAlarmRuleList(queryRule);
|
|
|
|
|
|
|
|
|
|
if (alarmRules.isEmpty()) {
|
|
|
|
|
System.out.println("没有启用的阈值告警规则");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 按设备编码分组告警规则,每个设备可能有多个字段的规则
|
|
|
|
|
// 注意:monitor_id字段存储的实际是monitor_code值,与采集数据分表的monitorId保持一致
|
|
|
|
|
Map<String, List<EmsRecordAlarmRule>> rulesByMonitor = alarmRules.stream()
|
|
|
|
|
.filter(rule -> StringUtils.isNotEmpty(rule.getMonitorId()))
|
|
|
|
|
.collect(Collectors.groupingBy(EmsRecordAlarmRule::getMonitorId));
|
|
|
|
|
|
|
|
|
|
// 4. 只查询最近2分钟的数据,确保每分钟执行时不重复处理
|
|
|
|
|
Date twoMinutesAgo = new Date(System.currentTimeMillis() - 2 * 60 * 1000);
|
|
|
|
|
List<RecordIotenvInstant> recentRecords = recordIotenvInstantMapper.selectRecentRecordsFromTable(tableName, twoMinutesAgo);
|
|
|
|
|
|
|
|
|
|
if (recentRecords.isEmpty()) {
|
|
|
|
|
System.out.println("当天分表最近2分钟无新数据: " + tableName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 获取所有相关设备信息(用于验证设备类型,确保规则合理性)
|
|
|
|
|
// 这里需要按monitor_code查询,因为rulesByMonitor的key是monitor_code
|
|
|
|
|
Set<String> monitorCodes = rulesByMonitor.keySet();
|
|
|
|
|
List<EmsBaseMonitorInfo> monitorInfos = emsBaseMonitorInfoMapper.selectMonitorInfoByCodes(new ArrayList<>(monitorCodes));
|
|
|
|
|
Map<String, EmsBaseMonitorInfo> monitorInfoMap = monitorInfos.stream()
|
|
|
|
|
.collect(Collectors.toMap(EmsBaseMonitorInfo::getMonitorCode, m -> m));
|
|
|
|
|
|
|
|
|
|
// 6. 处理每条记录,基于精确的字段规则进行阈值检查
|
|
|
|
|
List<EmsRecordAlarmData> candidateAlarms = new ArrayList<>();
|
|
|
|
|
for (RecordIotenvInstant record : recentRecords) {
|
|
|
|
|
String monitorId = record.getMonitorId(); // 采集数据中的monitorId实际是monitor_code
|
|
|
|
|
List<EmsRecordAlarmRule> rules = rulesByMonitor.get(monitorId);
|
|
|
|
|
|
|
|
|
|
if (rules == null || rules.isEmpty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ((deviceInfo.getUpdateTime().getTime() + minuteValue) < date.getTime()) {
|
|
|
|
|
EmsRecordAlarmData alarmData = new EmsRecordAlarmData();
|
|
|
|
|
alarmData.setCollectDeviceId(deviceInfo.getCollectDeviceId());
|
|
|
|
|
alarmData.setCollectTime(new Date());
|
|
|
|
|
alarmData.setAlarmType(1L);
|
|
|
|
|
alarmData.setAlarmStatus(1L);
|
|
|
|
|
alarmData.setAlarmData("设备离线已超过" + alarmRules.get(0).getDeviceOfflineTime() + "分钟");
|
|
|
|
|
this.insertEmsRecordAlarmData(alarmData);
|
|
|
|
|
|
|
|
|
|
EmsBaseMonitorInfo monitorInfo = monitorInfoMap.get(monitorId);
|
|
|
|
|
if (monitorInfo == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. 遍历该设备的所有规则,每个规则对应一个具体的监测字段
|
|
|
|
|
for (EmsRecordAlarmRule rule : rules) {
|
|
|
|
|
// 检查规则的基本有效性
|
|
|
|
|
if (rule.getTriggerValue() == null || rule.getMonitorField() == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证监测字段是否符合设备类型(可选的业务校验)
|
|
|
|
|
if (!isValidMonitorFieldForDeviceType(rule.getMonitorField(), monitorInfo.getMonitorType())) {
|
|
|
|
|
System.out.println("警告:设备类型与监测字段不匹配 - 设备:" + monitorId +
|
|
|
|
|
", 设备类型:" + monitorInfo.getMonitorType() +
|
|
|
|
|
", 监测字段:" + rule.getMonitorField());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 8. 根据规则的monitor_field精确检查对应的数据字段
|
|
|
|
|
BigDecimal threshold = rule.getTriggerValue();
|
|
|
|
|
BigDecimal actualValue = getFieldValue(record, rule.getMonitorField());
|
|
|
|
|
|
|
|
|
|
// 9. 阈值比较:实际值 > 阈值时触发异常
|
|
|
|
|
if (actualValue != null && actualValue.compareTo(threshold) > 0) {
|
|
|
|
|
EmsRecordAlarmData alarmData = createAlarmData(record, rule, actualValue.toString());
|
|
|
|
|
candidateAlarms.add(alarmData);
|
|
|
|
|
|
|
|
|
|
System.out.println("检测到阈值超标 - 设备:" + monitorId +
|
|
|
|
|
", 字段:" + MONITOR_FIELD_TO_DESC.get(rule.getMonitorField()) +
|
|
|
|
|
", 实际值:" + actualValue +
|
|
|
|
|
", 阈值:" + threshold +
|
|
|
|
|
", 采集时间:" + record.getCollectTime());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 超过电阈值定时任务
|
|
|
|
|
*/
|
|
|
|
|
public void exceedDnbThresholdAlarmsTask() {
|
|
|
|
|
EmsBaseMonitorThreshold monitorThreshold = new EmsBaseMonitorThreshold();
|
|
|
|
|
monitorThreshold.setMonitorType(2L);
|
|
|
|
|
List<EmsBaseMonitorThreshold> thresholdList = emsBaseMonitorThresholdMapper.selectEmsBaseMonitorThresholdList(monitorThreshold);
|
|
|
|
|
|
|
|
|
|
EmsRecordDnbInstant dnbInstant = new EmsRecordDnbInstant();
|
|
|
|
|
List<EmsRecordDnbInstant> dnbInstantList = emsRecordDnbInstantMapper.selectEmsRecordDnbInstantList(dnbInstant);
|
|
|
|
|
Map<String, String> thresholdMap = compareThresholdAndRecord(thresholdList, dnbInstantList);
|
|
|
|
|
//防止多次存入异常数据
|
|
|
|
|
EmsRecordAlarmData recordAlarmData = new EmsRecordAlarmData();
|
|
|
|
|
recordAlarmData.setAlarmType(0L);
|
|
|
|
|
recordAlarmData.setAlarmStatus(1L);
|
|
|
|
|
List<EmsRecordAlarmData> alarmDataList = emsRecordAlarmDataMapper.selectEmsRecordAlarmDataList(recordAlarmData);
|
|
|
|
|
List<String> monitorIdList = alarmDataList.stream().map(EmsRecordAlarmData::getMonitorId).collect(Collectors.toList());
|
|
|
|
|
for (String monitorId : thresholdMap.keySet()) {
|
|
|
|
|
if (monitorIdList.contains(monitorId)) {
|
|
|
|
|
continue;
|
|
|
|
|
// 10. 使用严格的去重逻辑插入异常数据
|
|
|
|
|
if (!candidateAlarms.isEmpty()) {
|
|
|
|
|
int insertedCount = insertAlarmDataWithStrictDuplicateCheck(candidateAlarms);
|
|
|
|
|
System.out.println("严格去重后实际插入异常数据: " + insertedCount + " 条");
|
|
|
|
|
} else {
|
|
|
|
|
System.out.println("未检测到异常数据");
|
|
|
|
|
}
|
|
|
|
|
EmsRecordAlarmData alarmData = new EmsRecordAlarmData();
|
|
|
|
|
alarmData.setMonitorId(monitorId);
|
|
|
|
|
alarmData.setCollectTime(new Date());
|
|
|
|
|
alarmData.setAlarmType(0L);
|
|
|
|
|
alarmData.setAlarmStatus(1L);
|
|
|
|
|
alarmData.setAlarmData(thresholdMap.get(monitorId));
|
|
|
|
|
this.insertEmsRecordAlarmData(alarmData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
System.err.println("阈值检查定时任务执行异常: " + e.getMessage());
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 超过电阈值 对比记录
|
|
|
|
|
* 根据监测字段获取记录中对应的数据值
|
|
|
|
|
* 这是关键的字段映射方法,将字典值映射到具体的数据库字段
|
|
|
|
|
*
|
|
|
|
|
* @param baseDnbThresholds
|
|
|
|
|
* @param recordDnbInstants
|
|
|
|
|
* @return
|
|
|
|
|
* @param record 物联网数据记录
|
|
|
|
|
* @param monitorField 监测字段字典值(0温度,1湿度,2振动-速度等)
|
|
|
|
|
* @return 对应字段的数据值,如果字段不存在返回null
|
|
|
|
|
*/
|
|
|
|
|
public Map<String, String> compareThresholdAndRecord(List<EmsBaseMonitorThreshold> baseDnbThresholds, List<EmsRecordDnbInstant> recordDnbInstants) {
|
|
|
|
|
Map<String, String> resultMap = new HashMap<>();
|
|
|
|
|
for (EmsRecordDnbInstant recordDnbInstant : recordDnbInstants) {
|
|
|
|
|
String monitorCode = recordDnbInstant.getMonitorCode();
|
|
|
|
|
EmsBaseMonitorThreshold baseDnbThreshold = null;
|
|
|
|
|
for (EmsBaseMonitorThreshold threshold : baseDnbThresholds) {
|
|
|
|
|
if (threshold.getMonitorCode().equals(monitorCode)) {
|
|
|
|
|
baseDnbThreshold = threshold;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (baseDnbThreshold != null) {
|
|
|
|
|
String reason = compare(recordDnbInstant, baseDnbThreshold);
|
|
|
|
|
if (reason != null) {
|
|
|
|
|
resultMap.put(monitorCode, reason);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private BigDecimal getFieldValue(RecordIotenvInstant record, Long monitorField) {
|
|
|
|
|
switch (monitorField.intValue()) {
|
|
|
|
|
case 0: // 温度
|
|
|
|
|
return record.getTemperature();
|
|
|
|
|
case 1: // 湿度
|
|
|
|
|
return record.getHumidity();
|
|
|
|
|
case 2: // 振动-速度(mm/s)
|
|
|
|
|
return record.getVibrationSpeed();
|
|
|
|
|
case 3: // 振动-位移(um)
|
|
|
|
|
return record.getVibrationDisplacement();
|
|
|
|
|
case 4: // 振动-加速度(g)
|
|
|
|
|
return record.getVibrationAcceleration();
|
|
|
|
|
case 5: // 振动-温度(℃)
|
|
|
|
|
return record.getVibrationTemp();
|
|
|
|
|
default:
|
|
|
|
|
System.out.println("未知的监测字段: " + monitorField);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return resultMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String compare(EmsRecordDnbInstant recordDnbInstant, EmsBaseMonitorThreshold baseDnbThreshold) {
|
|
|
|
|
if (StringUtils.isNull(recordDnbInstant.getIA())) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
BigDecimal zero = new BigDecimal("0.00");
|
|
|
|
|
if (recordDnbInstant.getVA().compareTo(zero) == 0 || recordDnbInstant.getVB().compareTo(zero) == 0
|
|
|
|
|
|| recordDnbInstant.getVC().compareTo(zero) == 0) {
|
|
|
|
|
return "缺项报警";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIA().compareTo(baseDnbThreshold.getiAMax()) > 0) {
|
|
|
|
|
return "超过A相电流最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIB().compareTo(baseDnbThreshold.getiBMax()) > 0) {
|
|
|
|
|
return "超过B相电流最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIC().compareTo(baseDnbThreshold.getiCMax()) > 0) {
|
|
|
|
|
return "超过C相电流最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVA().compareTo(baseDnbThreshold.getvAMax()) > 0) {
|
|
|
|
|
return "超过A相电压最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVB().compareTo(baseDnbThreshold.getvBMax()) > 0) {
|
|
|
|
|
return "超过B相电压最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVC().compareTo(baseDnbThreshold.getvCMax()) > 0) {
|
|
|
|
|
return "超过C相电压最大值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIA().compareTo(baseDnbThreshold.getiAMin()) < 0) {
|
|
|
|
|
return "小于A相电流最小值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIB().compareTo(baseDnbThreshold.getiBMin()) < 0) {
|
|
|
|
|
return "小于B相电流最小值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getIC().compareTo(baseDnbThreshold.getiCMin()) < 0) {
|
|
|
|
|
return "小于C相电流最小值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVA().compareTo(baseDnbThreshold.getvAMin()) < 0) {
|
|
|
|
|
return "小于A相电压最小值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVB().compareTo(baseDnbThreshold.getvBMin()) < 0) {
|
|
|
|
|
return "小于B相电压最小值";
|
|
|
|
|
}
|
|
|
|
|
if (recordDnbInstant.getVC().compareTo(baseDnbThreshold.getvCMin()) < 0) {
|
|
|
|
|
return "小于C相电压最小值";
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 小时耗量告警定时任务
|
|
|
|
|
* 验证监测字段是否符合设备类型
|
|
|
|
|
* 业务规则校验,确保规则配置的合理性
|
|
|
|
|
*
|
|
|
|
|
* @param monitorField 监测字段
|
|
|
|
|
* @param monitorType 设备类型
|
|
|
|
|
* @return 是否匹配
|
|
|
|
|
*/
|
|
|
|
|
public void hourlyConsumptionAlarmsTask() {
|
|
|
|
|
EmsBaseMonitorThreshold monitorThreshold = new EmsBaseMonitorThreshold();
|
|
|
|
|
monitorThreshold.setMonitorType(2L);
|
|
|
|
|
List<EmsBaseMonitorThreshold> thresholdList = emsBaseMonitorThresholdMapper.selectEmsBaseMonitorThresholdList(monitorThreshold);
|
|
|
|
|
for (EmsBaseMonitorThreshold threshold : thresholdList) {
|
|
|
|
|
BigDecimal consumption = threshold.getHourConsumption();
|
|
|
|
|
if (consumption.equals(new BigDecimal(0))){
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
EmsReportPointDnb reportPointDnb = new EmsReportPointDnb();
|
|
|
|
|
reportPointDnb.setMonitorCode(threshold.getMonitorCode());
|
|
|
|
|
List<EmsReportPointDnb> dnbList = emsReportPointDnbMapper.selectEmsReportPointDnbList(reportPointDnb);
|
|
|
|
|
|
|
|
|
|
private boolean isValidMonitorFieldForDeviceType(Long monitorField, Long monitorType) {
|
|
|
|
|
if (monitorField == null || monitorType == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 温湿度设备(6)应该只能选择温度(0)或湿度(1)
|
|
|
|
|
if (monitorType == 6L) {
|
|
|
|
|
return monitorField == 0L || monitorField == 1L;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 振动设备(10)应该只能选择振动相关字段(2,3,4,5)
|
|
|
|
|
if (monitorType == 10L) {
|
|
|
|
|
return monitorField >= 2L && monitorField <= 5L;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 纯温度设备(5)只能选择温度(0)
|
|
|
|
|
if (monitorType == 5L) {
|
|
|
|
|
return monitorField == 0L;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 其他设备类型暂不支持字段级规则
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建异常数据对象
|
|
|
|
|
* 使用字典描述作为cause,提高可读性
|
|
|
|
|
*
|
|
|
|
|
* @param record 物联网数据记录
|
|
|
|
|
* @param rule 告警规则
|
|
|
|
|
* @param actualValue 实际数值
|
|
|
|
|
* @return 异常数据对象
|
|
|
|
|
*/
|
|
|
|
|
private EmsRecordAlarmData createAlarmData(RecordIotenvInstant record, EmsRecordAlarmRule rule, String actualValue) {
|
|
|
|
|
EmsRecordAlarmData alarmData = new EmsRecordAlarmData();
|
|
|
|
|
alarmData.setMonitorId(record.getMonitorId());
|
|
|
|
|
alarmData.setCollectTime(record.getCollectTime());
|
|
|
|
|
alarmData.setAlarmType(0L); // 0表示超过阈值
|
|
|
|
|
alarmData.setAlarmStatus(1L); // 1表示未处理
|
|
|
|
|
alarmData.setAlarmData(actualValue); // 记录实际超标数值
|
|
|
|
|
|
|
|
|
|
// 使用字典描述作为异常原因,更加友好
|
|
|
|
|
String fieldDesc = MONITOR_FIELD_TO_DESC.get(rule.getMonitorField());
|
|
|
|
|
alarmData.setCause(fieldDesc != null ? fieldDesc : "未知字段");
|
|
|
|
|
|
|
|
|
|
alarmData.setCreateTime(new Date());
|
|
|
|
|
return alarmData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用严格的去重逻辑插入异常数据
|
|
|
|
|
* 防重复策略:同一设备的同一字段在30分钟内只记录一次异常
|
|
|
|
|
* 每条数据插入前都会检查数据库是否已存在相同记录
|
|
|
|
|
*
|
|
|
|
|
* @param alarmDataList 待插入的异常数据列表
|
|
|
|
|
* @return 实际插入的异常数据数量
|
|
|
|
|
*/
|
|
|
|
|
private int insertAlarmDataWithStrictDuplicateCheck(List<EmsRecordAlarmData> alarmDataList) {
|
|
|
|
|
int insertedCount = 0;
|
|
|
|
|
|
|
|
|
|
// 30分钟去重窗口(前后各15分钟)
|
|
|
|
|
long fifteenMinutesInMs = 15 * 60 * 1000;
|
|
|
|
|
|
|
|
|
|
for (EmsRecordAlarmData alarmData : alarmDataList) {
|
|
|
|
|
try {
|
|
|
|
|
// 为每条异常数据检查是否已存在重复记录
|
|
|
|
|
Date collectTime = alarmData.getCollectTime();
|
|
|
|
|
if (collectTime == null) {
|
|
|
|
|
continue; // 跳过没有采集时间的数据
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建30分钟的时间窗口
|
|
|
|
|
Date startTime = new Date(collectTime.getTime() - fifteenMinutesInMs);
|
|
|
|
|
Date endTime = new Date(collectTime.getTime() + fifteenMinutesInMs);
|
|
|
|
|
|
|
|
|
|
// 查询数据库是否已存在相同的异常记录
|
|
|
|
|
Integer existingCount = emsRecordAlarmDataMapper.checkDuplicateAlarmData(
|
|
|
|
|
alarmData.getMonitorId(),
|
|
|
|
|
alarmData.getCause(),
|
|
|
|
|
startTime,
|
|
|
|
|
endTime
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 如果不存在重复记录,则插入
|
|
|
|
|
if (existingCount == null || existingCount == 0) {
|
|
|
|
|
int result = insertEmsRecordAlarmData(alarmData);
|
|
|
|
|
if (result > 0) {
|
|
|
|
|
insertedCount++;
|
|
|
|
|
System.out.println("成功插入异常数据 - 设备:" + alarmData.getMonitorId() +
|
|
|
|
|
", 原因:" + alarmData.getCause() +
|
|
|
|
|
", 采集时间:" + alarmData.getCollectTime() +
|
|
|
|
|
", 数值:" + alarmData.getAlarmData());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
System.out.println("跳过重复异常数据 - 设备:" + alarmData.getMonitorId() +
|
|
|
|
|
", 原因:" + alarmData.getCause() +
|
|
|
|
|
", 采集时间:" + alarmData.getCollectTime() +
|
|
|
|
|
", 30分钟内已存在 " + existingCount + " 条相同记录");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
System.err.println("插入异常数据时发生错误: " + e.getMessage());
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return insertedCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查数据库表是否存在
|
|
|
|
|
*
|
|
|
|
|
* @param tableName 表名
|
|
|
|
|
* @return 是否存在
|
|
|
|
|
*/
|
|
|
|
|
private boolean checkTableExists(String tableName) {
|
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
|
|
|
params.put("tableName", tableName);
|
|
|
|
|
Integer count = recordIotenvInstantMapper.checkTableExists(params);
|
|
|
|
|
return count != null && count > 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|