粉尘涉爆定时任务

dev
liuchangjiu 2025-10-22 09:12:09 +08:00
parent f26a4428ce
commit bac8551228
7 changed files with 591 additions and 0 deletions

View File

@ -0,0 +1,39 @@
package com.zcloud.mapper.datasource.dust;
import com.zcloud.entity.PageData;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface DustSensorMapper {
/**
*
*
* @return
*/
List<PageData> selectAllActiveSensors();
/**
* ID
* @param deviceSensorId
* @return
*/
PageData selectRuleByDeviceSensorId(String deviceSensorId);
/**
* ID
* @param deviceId
* @param assocSensorType
* @return
*/
PageData findByDeviceIdAndSensorType(@Param("deviceId") String deviceId, @Param("assocSensorType") String assocSensorType);
/**
*
* @param alarmRecords
* @return
*/
int insertBatch(@Param("alarmRecords") List<PageData> alarmRecords);
List<PageData> getLatestData();
PageData getLatestDataById(String deviceSensorId);
}

View File

@ -0,0 +1,10 @@
package com.zcloud.mapper.dsno2.sensor;
import com.zcloud.entity.PageData;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface LastSensorDataMapper {
}

View File

@ -0,0 +1,295 @@
package com.zcloud.scheduled;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zcloud.entity.PageData;
import com.zcloud.mapper.dsno2.sensor.LastSensorDataMapper;
import com.zcloud.service.dust.DustSensorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class DustSensorAlarmScheduled {
private static final Logger log = LoggerFactory.getLogger(DustSensorAlarmScheduled.class);
@Resource
private DustSensorService sensorAlarmService;
@Resource
private LastSensorDataMapper lastSensorDataMapper;
// 缓存规则模板RULE_ID -> Rule
private final Map<String, PageData> ruleCache = new ConcurrentHashMap<>();
// 报警状态缓存DEVICE_SENSOR_ID -> AlarmState
private final Map<String, PageData> alarmStateCache = new ConcurrentHashMap<>();
@XxlJob(value = "sensorAlarmScheduled")
public void sensorAlarmScheduled() {
// 1. 获取所有启用的传感器(排除停用/删除)
List<PageData> allSensors = sensorAlarmService.selectAllActiveSensors();
// 2. 批量获取最新实时数据
List<PageData> latestDataMap = sensorAlarmService.getLatestData();
// 3.批量错误 数据
List<PageData> alarmRecords = new ArrayList<>();
allSensors.forEach(sensor -> {
PageData currentData = latestDataMap.stream()
.filter(item -> item.get("DEVICE_SENSOR_ID").equals(sensor.get("DEVICE_SENSOR_ID")))
.findFirst()
.orElse(null);
if (currentData == null) {
log.warn("未获取到传感器实时数据, DEVICE_SENSOR_ID={}", sensor.get("DEVICE_SENSOR_ID"));
}
evaluateSingleSensorAlarm(alarmRecords, sensor, currentData);
});
if (!alarmRecords.isEmpty()) {
// 4. 批量创建报警记录
sensorAlarmService.createAlarmRecord(alarmRecords);
}
}
/**
* /
*/
private void evaluateSingleSensorAlarm(List<PageData> alarmRecords, PageData sensor, PageData currentData) {
// 1. 获取规则(优先从缓存)
PageData rule = getRuleTemplate(sensor.getString("DEVICE_SENSOR_ID"));
if (rule == null || !"1".equals(rule.getString("IS_ACTIVE"))) {
return;
}
// 2. 检查传感器是否离线超时24小时
if (isOfflineTimeout(currentData, Integer.parseInt(rule.getString("OFFLINE_TIMEOUT_HOUR")))) {
handleOfflineClear(sensor, rule);
return;
}
// 3. 处理关联传感器(如启停信号)
if (!checkAssociatedCondition(sensor, rule, currentData)) {
// 不满足关联条件,不报警(但不清除已有报警!)
// 注意:业务规则中,关联不满足 ≠ 消警,只是不触发新报警
return;
}
// 4. 判断是否超限
boolean isOverLimit = isValueOverLimit(currentData, rule);
// 5. 获取当前报警状态
PageData state = alarmStateCache.computeIfAbsent(
sensor.getString("DEVICE_SENSOR_ID"),
k -> {
PageData stateInit = new PageData();
stateInit.put("IS_ALARMING", false);
stateInit.put("CONSECUTIVE_NORMAL_POINTS", 0);
return stateInit;
}
);
if (isOverLimit) {
// --- 触发逻辑 ---
handleTriggerLogic(alarmRecords, sensor, rule, currentData, state);
} else {
// --- 消警逻辑 ---
handleClearLogic(sensor, rule, currentData, state);
}
}
/**
*
*/
private void handleTriggerLogic(List<PageData> alarmRecords, PageData sensor, PageData rule,
PageData currentData, PageData state) {
LocalDateTime now = LocalDateTime.now();
if (!Boolean.parseBoolean(state.get("IS_ALARMING").toString())) {
// 判断规则是否存在 持续报警时间
if (rule.get("TRIGGER_DURATION_SEC") != null) {
// 首次超限
if (state.get("FIRST_TRIGGER_TIME") == null) {
state.put("FIRST_TRIGGER_TIME", now);
}
// 检查是否满足持续时间 单位:秒
// 暂时还没考虑数据点的情况如后期有需求在数据库的规则表中增加一个字段TRIGGER_DURATION_POINT
long durationSec = Duration.between(LocalDateTime.parse(state.get("FIRST_TRIGGER_TIME").toString()), now).getSeconds();
if (durationSec >= Integer.parseInt(rule.getString("TRIGGER_DURATION_SEC"))) {
// 触发报警
createAlarmRecord(alarmRecords, sensor, rule, currentData);
state.put("IS_ALARMING", true);
log.info("【满足持续时间报警触发】sensor={}, value={}", sensor.get("SENSOR_NAME"), currentData.getString("CURRENT_VALUE"));
}
}else {
// 触发报警
createAlarmRecord(alarmRecords, sensor, rule, currentData);
state.put("IS_ALARMING", true);
log.info("【报警触发】sensor={}, value={}", sensor.get("SENSOR_NAME"), currentData.getString("CURRENT_VALUE"));
}
}
}
/**
*
*/
private void createAlarmRecord(List<PageData> alarmRecords, PageData sensor, PageData rule, PageData currentData) {
PageData alarmRecord = new PageData();
alarmRecord.put("ALARM_RECORD_ID", IdUtil.fastSimpleUUID());
alarmRecord.put("RULE_ID", rule.get("RULE_ID"));
alarmRecord.put("DEVICE_SENSOR_ID", sensor.get("DEVICE_SENSOR_ID"));
alarmRecord.put("SENSOR_CODE", sensor.get("SENSOR_CODE"));
alarmRecord.put("DEVICE_ID", sensor.get("DEVICE_ID"));
alarmRecord.put("ALARM_TYPE", rule.get("ALARM_TYPE"));
alarmRecord.put("TRIGGER_REASON", currentData.get("TRIGGER_REASON"));
alarmRecord.put("ALARM_LEVEL", currentData.get("ALARM_LEVEL"));
alarmRecord.put("VALUE_AT_TRIGGER", currentData.getString("CURRENT_VALUE"));
alarmRecord.put("TRIGGERED_TIME", DateUtil.now());
alarmRecord.put("CREATOR", "scheduled");
alarmRecord.put("OPERATOR", "scheduled");
alarmRecord.put("CREATTIME", DateUtil.now());
alarmRecord.put("OPERATTIME", DateUtil.now());
alarmRecords.add(alarmRecord);
}
/**
*
*/
private void handleClearLogic(PageData sensor, PageData rule,
PageData currentData, PageData state) {
if (!Boolean.parseBoolean(state.get("IS_ALARMING").toString())) {
// 未报警,重置状态
state.put("FIRST_TRIGGER_TIME", null);
state.put("CONSECUTIVE_NORMAL_POINTS", 0);
return;
}
// 值已恢复正常,开始计数
int normalPoints = Integer.parseInt(state.get("CONSECUTIVE_NORMAL_POINTS").toString()) + 1;
state.put("CONSECUTIVE_NORMAL_POINTS", normalPoints);
// 假设1个数据点 = 1秒可根据实际调整
if (normalPoints >= Integer.parseInt(rule.get("CLEAR_DURATION_SEC").toString())) {
// TODO 消警逻辑
// sensorAlarmService.clearAlarmEvent(sensor.get("DEVICE_SENSOR_ID").toString(), "normal");
state.put("IS_ALARMING", false);
state.put("CONSECUTIVE_NORMAL_POINTS", 0);
log.info("【报警消警】sensor={}", sensor.get("SENSOR_NAME"));
}
}
/**
*
*/
private boolean isValueOverLimit(PageData data, PageData rule) {
BigDecimal value = new BigDecimal(data.getString("CURRENT_VALUE"));
if ("2".equals(rule.getString("SIGNAL_TYPE"))) { // 开关量
return value.compareTo(new BigDecimal(rule.getString("SWITCH_ALARM_VALUE"))) == 0;
} else { // 模拟量
if (rule.getString("HIGH_ALARM") != null && value.compareTo(new BigDecimal(rule.getString("HIGH_ALARM"))) > 0) {
data.put("TRIGGER_REASON", data.getString("TARGET_NAME") + "超过高报警");
data.put("ALARM_LEVEL", "2");
return true;
}
if (rule.getString("HIGH_HIGH_ALARM") != null && value.compareTo(new BigDecimal(rule.getString("HIGH_HIGH_ALARM"))) > 0) {
data.put("TRIGGER_REASON", data.getString("TARGET_NAME") + "超过高高报警");
data.put("ALARM_LEVEL", "3");
return true;
}
if (rule.getString("LOW_ALARM") != null && value.compareTo(new BigDecimal(rule.getString("LOW_ALARM"))) < 0) {
data.put("TRIGGER_REASON", data.getString("TARGET_NAME") + "超过低报警");
data.put("ALARM_LEVEL", "2");
return true;
}
if (rule.getString("LOW_LOW_ALARM") != null && value.compareTo(new BigDecimal(rule.getString("LOW_LOW_ALARM"))) < 0) {
data.put("TRIGGER_REASON", data.getString("TARGET_NAME") + "超过低低报警");
data.put("ALARM_LEVEL", "3");
return true;
}
}
return false;
}
/**
*
*/
private boolean checkAssociatedCondition(PageData sensor, PageData rule, PageData currentData) {
String assocSensorType = rule.getString("ASSOCIATED_SENSOR_TYPE");
if (assocSensorType == null) {
return true; // 无关联,直接通过
}
// 在同一设备下查找关联传感器
PageData associatedSensor = sensorAlarmService.findByDeviceIdAndSensorType(
sensor.getString("DEVICE_ID"), assocSensorType
);
if (associatedSensor == null) {
log.warn("未找到关联传感器, deviceId={}, type={}", sensor.getString("DEVICE_ID"), assocSensorType);
return false;
}
// 获取关联传感器的最新数据
PageData assocData = sensorAlarmService.getLatestDataById(associatedSensor.getString("DEVICE_SENSOR_ID"));
if (assocData == null) {
return true; // 注意:这里返回 true 表示“不阻断”,但实际不触发报警(由上层控制)
}
// 判断关联条件must_be_on → 值=1
if ("1".equals(rule.getString("ASSOCIATED_CONDITION"))) {
return BigDecimal.ONE.compareTo(new BigDecimal(assocData.getString("CURRENT_VALUE"))) == 0;
}
if ("0".equals(rule.getString("ASSOCIATED_CONDITION"))) {
return BigDecimal.ZERO.compareTo(new BigDecimal(assocData.getString("CURRENT_VALUE"))) == 0;
}
return true;
}
/**
* 线
*/
private void handleOfflineClear(PageData sensor, PageData rule) {
PageData state = alarmStateCache.get(sensor.getString("DEVICE_SENSOR_ID"));
if (state != null && Boolean.parseBoolean(state.get("IS_ALARMING").toString())) {
// TODO 消警逻辑
//alarmEventService.clearAlarmEvent(sensor.get("DEVICE_SENSOR_ID").toString(), "offline_timeout");
state.put("IS_ALARMING", false);
log.info("【离线超时消警】sensor={}", sensor.get("SENSOR_NAME"));
}
}
/**
* 线24
*/
private boolean isOfflineTimeout(PageData data, int offlineTimeoutHour) {
LocalDateTime insertTime = DateUtil.parseLocalDateTime(data.getString("INSERT_TIME"));
long offlineHours = Duration.between(insertTime, LocalDateTime.now()).toHours();
return offlineHours >= offlineTimeoutHour;
}
/**
*
*/
private PageData getRuleTemplate(String deviceSensorId) {
return ruleCache.computeIfAbsent(deviceSensorId, id -> {
PageData rule = sensorAlarmService.selectRuleByDeviceSensorId(deviceSensorId);
if (rule == null) {
log.error("规则模板不存在: deviceSensorId={}", deviceSensorId);
}
return rule;
});
}
}

View File

@ -0,0 +1,45 @@
package com.zcloud.service.dust;
import com.zcloud.entity.PageData;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
public interface DustSensorService {
/**
*
* @return
*/
List<PageData> selectAllActiveSensors();
/**
* ID
* @param deviceSensorId
* @return
*/
PageData selectRuleByDeviceSensorId(String deviceSensorId);
/**
* ID
* @param deviceId
* @param assocSensorType
* @return
*/
PageData findByDeviceIdAndSensorType(String deviceId, String assocSensorType);
/**
*
* @param alarmRecords
*/
void createAlarmRecord(List<PageData> alarmRecords);
/**
*
* @return
*/
List<PageData> getLatestData();
/**
* ID
* @param deviceSensorId
* @return
*/
PageData getLatestDataById(@Param("deviceSensorId") String deviceSensorId);
}

View File

@ -0,0 +1,43 @@
package com.zcloud.service.dust.impl;
import com.zcloud.entity.PageData;
import com.zcloud.mapper.datasource.dust.DustSensorMapper;
import com.zcloud.service.dust.DustSensorService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class DustSensorServiceImpl implements DustSensorService {
@Resource
private DustSensorMapper dustSensorMapper;
@Override
public List<PageData> selectAllActiveSensors() {
return dustSensorMapper.selectAllActiveSensors();
}
@Override
public PageData selectRuleByDeviceSensorId(String deviceSensorId) {
return dustSensorMapper.selectRuleByDeviceSensorId(deviceSensorId);
}
@Override
public PageData findByDeviceIdAndSensorType(String deviceId, String assocSensorType) {
return dustSensorMapper.findByDeviceIdAndSensorType(deviceId, assocSensorType);
}
@Override
public void createAlarmRecord(List<PageData> alarmRecords) {
dustSensorMapper.insertBatch(alarmRecords);
}
@Override
public List<PageData> getLatestData() {
return dustSensorMapper.getLatestData();
}
@Override
public PageData getLatestDataById(String deviceSensorId) {
return dustSensorMapper.getLatestDataById(deviceSensorId);
}
}

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.mapper.datasource.dust.DustSensorMapper">
<select id="selectAllActiveSensors" resultType="com.zcloud.entity.PageData">
select DEVICE_SENSOR_ID,
DEVICE_ID,
SENSOR_CODE,
SENSOR_NAME,
SENSOR_TYPE,
SENSOR_CATEGORY,
SIGNAL_TYPE,
DISABLED_STATUS,
ISDELETE
from dust_device_sensor
where DISABLED_STATUS = 0 AND ISDELETE = 0
</select>
<select id="selectRuleByDeviceSensorId" resultType="com.zcloud.entity.PageData">
select RULE_ID,
DEVICE_SENSOR_ID,
RULE_NAME,
DEVICE_TYPE,
SENSOR_TYPE,
SIGNAL_TYPE,
HIGH_ALARM,
HIGH_HIGH_ALARM,
LOW_ALARM,
LOW_LOW_ALARM,
SWITCH_ALARM_VALUE,
TRIGGER_DURATION_SEC,
CLEAR_DURATION_SEC,
ASSOCIATED_SENSOR_TYPE,
ASSOCIATED_CONDITION,
OFFLINE_TIMEOUT_HOUR,
IS_ACTIVE,
SPECIAL_LOGIC_TYPE,
CREATOR,
CREATTIME,
`OPERATOR`,
OPERATTIME,
ISDELETE,
CORPINFO_ID
from dust_device_sensor_alarm_rule
where DEVICE_SENSOR_ID = #{deviceSensorId}
</select>
<select id="findByDeviceIdAndSensorType" resultType="com.zcloud.entity.PageData">
select DEVICE_SENSOR_ID,
DEVICE_ID,
SENSOR_CODE,
SENSOR_NAME,
SENSOR_TYPE,
SENSOR_CATEGORY,
SIGNAL_TYPE,
DISABLED_STATUS,
ISDELETE
from dust_device_sensor
where DEVICE_ID = #{deviceId} AND SENSOR_TYPE = #{assocSensorType}
</select>
<insert id="insertBatch">
insert into dust_device_sensor_alarm_record
(ALARM_RECORD_ID,
DEVICE_ID,
RULE_ID,
DEVICE_SENSOR_ID,
SENSOR_CODE,
ALARM_TYPE,
TRIGGER_REASON,
ALARM_LEVEL,
VALUE_AT_TRIGGER,
TRIGGERED_TIME,
CREATOR,
CREATTIME,
OPERATTIME,
OPERATOR)
values
<foreach collection="alarmRecords" item="item" separator=",">
(#{item.ALARM_RECORD_ID},
#{item.DEVICE_ID},
#{item.RULE_ID},
#{item.DEVICE_SENSOR_ID},
#{item.SENSOR_CODE},
#{item.ALARM_TYPE},
#{item.TRIGGER_REASON},
#{item.ALARM_LEVEL},
#{item.VALUE_AT_TRIGGER},
#{item.TRIGGERED_TIME},
#{item.CREATOR},
#{item.CREATTIME},
#{item.OPERATTIME},
#{item.OPERATOR})
</foreach>
</insert>
<select id="getLatestData" resultType="com.zcloud.entity.PageData">
select MONITORING_ID,
TARGET_NAME,
TARGET_PLACE,
TARGET_UNIT,
SIGNAL_TYPE,
CURRENT_VALUE,
ALARM_VALUE,
THRESHOLD_UP_LIMIT,
THRESHOLD_UP_UP_LIM,
THRESHOLD_DOWN_LIMI,
THRESHOLD_DOWN_DOWN,
RANGE_UP,
RANGE_DOWN,
PROCESSING_TIME,
INSERT_TIME,
PLC_ID,
WARNING,
GATHER_TIME,
CORPINFO_ID,
IPCDEVICE_ID,
PLC_NAME,
PROCESSING_BATCH_ID,
OVERVIEW_OF_ALERTS,
EQUIPMENT_ID
from tb_iron_device_realtime_sxgt
</select>
<select id="getLatestDataById" resultType="com.zcloud.entity.PageData">
select MONITORING_ID,
TARGET_NAME,
TARGET_PLACE,
TARGET_UNIT,
SIGNAL_TYPE,
CURRENT_VALUE,
ALARM_VALUE,
THRESHOLD_UP_LIMIT,
THRESHOLD_UP_UP_LIM,
THRESHOLD_DOWN_LIMI,
THRESHOLD_DOWN_DOWN,
RANGE_UP,
RANGE_DOWN,
PROCESSING_TIME,
INSERT_TIME,
PLC_ID,
WARNING,
GATHER_TIME,
CORPINFO_ID,
IPCDEVICE_ID,
PLC_NAME,
PROCESSING_BATCH_ID,
OVERVIEW_OF_ALERTS,
EQUIPMENT_ID
from tb_iron_device_realtime_sxgt
where PLC_ID = #{deviceSensorId} limit 1
</select>
</mapper>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcloud.mapper.dsno2.sensor.LastSensorDataMapper">
</mapper>