fix: 物联网平台,amqp数据接入并插入TD数据库相关逻辑编码。
This commit is contained in:
@@ -1,10 +1,29 @@
|
||||
package com.intc.iot.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.intc.iot.domain.AquAlarmHistory;
|
||||
import com.intc.iot.domain.AquWarnCallNotice;
|
||||
import com.intc.iot.domain.VmsNoticeResponse;
|
||||
import com.intc.iot.mapper.AquAlarmHistoryMapper;
|
||||
import com.intc.iot.mapper.AquWarnCallNoticeMapper;
|
||||
import com.intc.iot.mapper.IotDeviceMapper;
|
||||
import com.intc.iot.service.VmsNoticeService;
|
||||
import com.intc.fishery.domain.Device;
|
||||
import com.intc.fishery.mapper.DeviceMapper;
|
||||
import com.intc.tdengine.domain.DeviceSensorData;
|
||||
import com.intc.tdengine.service.IDeviceSensorDataService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备数据处理器
|
||||
*
|
||||
@@ -12,8 +31,76 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnBean(IDeviceSensorDataService.class)
|
||||
public class DeviceDataHandler {
|
||||
|
||||
/**
|
||||
* TDengine 服务,用于存储设备时序数据
|
||||
*/
|
||||
private final IDeviceSensorDataService deviceSensorDataService;
|
||||
|
||||
/**
|
||||
* 告警通知 Mapper
|
||||
*/
|
||||
private final AquWarnCallNoticeMapper warnCallNoticeMapper;
|
||||
|
||||
/**
|
||||
* 告警历史记录 Mapper
|
||||
*/
|
||||
private final AquAlarmHistoryMapper alarmHistoryMapper;
|
||||
|
||||
/**
|
||||
* IoT设备 Mapper
|
||||
*/
|
||||
private final IotDeviceMapper iotDeviceMapper;
|
||||
|
||||
/**
|
||||
* 设备 Mapper
|
||||
*/
|
||||
private final DeviceMapper deviceMapper;
|
||||
|
||||
/**
|
||||
* VMS 语音通知服务
|
||||
*/
|
||||
private final VmsNoticeService vmsNoticeService;
|
||||
|
||||
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 呼叫状态:未呼叫
|
||||
*/
|
||||
private static final int CALL_STATUS_NO_CALL = 0;
|
||||
|
||||
/**
|
||||
* 告警状态:未处理
|
||||
*/
|
||||
private static final int ALARM_STATUS_PENDING = 0;
|
||||
|
||||
/**
|
||||
* 告警状态:已恢复
|
||||
*/
|
||||
private static final int ALARM_STATUS_RECOVERED = 2;
|
||||
|
||||
/**
|
||||
* 告警间隔时间(分钟)- 同一设备同一类型告警在此时间内不重复发送
|
||||
*/
|
||||
private static final int ALARM_NOTIFICATION_INTERVAL = 30;
|
||||
|
||||
/**
|
||||
* 告警类型常量
|
||||
*/
|
||||
private static final String ALARM_TYPE_DISSOLVED_OXYGEN = "溶解氧";
|
||||
private static final String ALARM_TYPE_TEMPERATURE = "温度";
|
||||
private static final String ALARM_TYPE_BATTERY = "电池电量";
|
||||
|
||||
/**
|
||||
* 告警级别
|
||||
*/
|
||||
private static final int ALARM_LEVEL_NORMAL = 1; // 一般
|
||||
private static final int ALARM_LEVEL_IMPORTANT = 2; // 重要
|
||||
private static final int ALARM_LEVEL_URGENT = 3; // 紧急
|
||||
|
||||
/**
|
||||
* 处理设备属性上报数据
|
||||
*
|
||||
@@ -21,32 +108,60 @@ public class DeviceDataHandler {
|
||||
* @param payload 消息内容
|
||||
*/
|
||||
public void handlePropertyPost(String topic, String payload) {
|
||||
log.info("收到设备属性上报,Topic: {}", topic);
|
||||
|
||||
try {
|
||||
JSONObject data = JSONUtil.parseObj(payload);
|
||||
|
||||
|
||||
// 解析飞燕平台消息格式
|
||||
String method = data.getStr("method");
|
||||
String id = data.getStr("id");
|
||||
JSONObject params = data.getJSONObject("params");
|
||||
|
||||
log.info("设备属性数据 - Method: {}, ID: {}, Params: {}", method, id, params);
|
||||
|
||||
// TODO: 这里添加您的业务逻辑
|
||||
// 1. 存储到数据库
|
||||
// 2. 触发告警
|
||||
// 3. 推送到前端(通过 WebSocket/SSE)
|
||||
// 4. 数据分析处理
|
||||
|
||||
if (params != null) {
|
||||
params.forEach((key, value) -> {
|
||||
log.info("属性: {} = {}", key, value);
|
||||
// 根据不同属性进行不同处理
|
||||
handleProperty(key, value);
|
||||
});
|
||||
|
||||
// 从Topic中解析产品Key和设备名称
|
||||
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/property/post
|
||||
String[] topicParts = topic.split("/");
|
||||
if (topicParts.length < 4) {
|
||||
log.warn("设备属性数据 Topic 格式不正确: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String productKey = topicParts[2];
|
||||
String deviceName = topicParts[3];
|
||||
|
||||
if (productKey == null || productKey.isEmpty() || deviceName == null || deviceName.isEmpty()) {
|
||||
log.warn("设备属性数据 ProductKey 或 DeviceName 为空: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存到 TDengine 时序数据库
|
||||
DeviceSensorData sensorData = null;
|
||||
if (params != null && params.size() > 3) {
|
||||
// // 检查 sensorerrorcode 是否为 6
|
||||
// Object errorCode = params.get("sensorerrorcode");
|
||||
// if (errorCode == null || !"6".equals(String.valueOf(errorCode))) {
|
||||
// log.debug("数据误码不为6,已过滤: {} (errorCode: {})", deviceName, errorCode);
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
sensorData = convertToSensorData(productKey, deviceName, params);
|
||||
if (sensorData != null) {
|
||||
// 1. 插入到 TDengine
|
||||
deviceSensorDataService.batchInsertDeviceSensorData(java.util.Collections.singletonList(sensorData));
|
||||
log.debug("数据已保存: {}", deviceName);
|
||||
|
||||
// 2. 更新设备表的实时数据
|
||||
updateDeviceRealTimeData(deviceName, sensorData);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存数据失败[{}]: {}", deviceName, e.getMessage());
|
||||
}
|
||||
} else if (params != null && params.size() < 3) {
|
||||
log.debug("数据字段不足3个,已过滤: {} (字段数: {})", deviceName, params.size());
|
||||
}
|
||||
//
|
||||
// // 检查是否触发报警
|
||||
// if (params != null && sensorData != null) {
|
||||
// checkAndTriggerAlarm(productKey, deviceName, sensorData);
|
||||
// }
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理设备属性数据失败", e);
|
||||
}
|
||||
@@ -59,76 +174,602 @@ public class DeviceDataHandler {
|
||||
* @param payload 消息内容
|
||||
*/
|
||||
public void handleEventPost(String topic, String payload) {
|
||||
log.info("收到设备事件上报,Topic: {}", topic);
|
||||
|
||||
try {
|
||||
JSONObject data = JSONUtil.parseObj(payload);
|
||||
|
||||
String method = data.getStr("method");
|
||||
String id = data.getStr("id");
|
||||
|
||||
JSONObject params = data.getJSONObject("params");
|
||||
|
||||
log.info("设备事件数据 - Method: {}, ID: {}, Params: {}", method, id, params);
|
||||
|
||||
|
||||
// 从Topic中解析产品Key、设备名称和事件标识符
|
||||
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/{EventIdentifier}/post
|
||||
String[] topicParts = topic.split("/");
|
||||
if (topicParts.length < 7) {
|
||||
log.warn("设备事件数据 Topic 格式不正确: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
String productKey = topicParts[2];
|
||||
String deviceName = topicParts[3];
|
||||
String eventIdentifier = topicParts[6];
|
||||
|
||||
if (productKey == null || productKey.isEmpty() ||
|
||||
deviceName == null || deviceName.isEmpty() ||
|
||||
eventIdentifier == null || eventIdentifier.isEmpty()) {
|
||||
log.warn("设备事件数据 ProductKey、DeviceName 或 EventIdentifier 为空: {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存到 TDengine 时序数据库
|
||||
DeviceSensorData sensorData = new DeviceSensorData();
|
||||
|
||||
// 时间字段
|
||||
String now = LocalDateTime.now().format(DATETIME_FORMATTER);
|
||||
sensorData.setTime(now);
|
||||
sensorData.setCreateTime(now);
|
||||
|
||||
// 设备标识
|
||||
sensorData.setSerialNum(deviceName);
|
||||
sensorData.setDeviceName(deviceName);
|
||||
|
||||
// 存储完整的事件数据(可以根据需要扩展)
|
||||
// 目前暂不处理事件数据的详细字段映射
|
||||
|
||||
try {
|
||||
deviceSensorDataService.batchInsertDeviceSensorData(java.util.Collections.singletonList(sensorData));
|
||||
log.debug("事件已保存: {} - {}", deviceName, eventIdentifier);
|
||||
} catch (Exception e) {
|
||||
log.error("保存事件失败[{}]: {}", deviceName, e.getMessage());
|
||||
}
|
||||
|
||||
// TODO: 处理设备事件
|
||||
// 例如:故障告警、状态变化等
|
||||
|
||||
handleDeviceEvent(productKey, deviceName, eventIdentifier, params);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理设备事件数据失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理单个属性
|
||||
* 处理设备事件
|
||||
*
|
||||
* @param propertyName 属性名称
|
||||
* @param propertyValue 属性值
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @param eventIdentifier 事件标识符
|
||||
* @param params 事件参数
|
||||
*/
|
||||
private void handleProperty(String propertyName, Object propertyValue) {
|
||||
// 根据属性名称进行不同的业务处理
|
||||
switch (propertyName) {
|
||||
case "temperature":
|
||||
handleTemperature(propertyValue);
|
||||
private void handleDeviceEvent(String productKey, String deviceName, String eventIdentifier, JSONObject params) {
|
||||
// 根据事件类型进行不同处理
|
||||
switch (eventIdentifier) {
|
||||
case "error":
|
||||
// 故障事件
|
||||
log.warn("设备故障告警: {}", params);
|
||||
// 直接触发电话告警
|
||||
triggerDeviceErrorAlarm(deviceName, params);
|
||||
break;
|
||||
case "humidity":
|
||||
handleHumidity(propertyValue);
|
||||
break;
|
||||
case "status":
|
||||
handleStatus(propertyValue);
|
||||
case "alert":
|
||||
// 告警事件
|
||||
log.warn("设备告警: {}", params);
|
||||
break;
|
||||
default:
|
||||
log.debug("未处理的属性: {} = {}", propertyName, propertyValue);
|
||||
log.debug("未处理的事件类型: {}", eventIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 报警逻辑 ====================
|
||||
|
||||
/**
|
||||
* 检查并触发报警
|
||||
* 根据传感器数据判断是否超过阈值,并触发电话通知
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @param sensorData 传感器数据
|
||||
*/
|
||||
private void checkAndTriggerAlarm(String productKey, String deviceName, DeviceSensorData sensorData) {
|
||||
try {
|
||||
// 查询设备信息,获取设备配置和告警阈值
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long deviceId = device.getId();
|
||||
StringBuilder alarmMessage = new StringBuilder();
|
||||
boolean hasAlarm = false;
|
||||
java.util.List<AquAlarmHistory> alarmList = new java.util.ArrayList<>();
|
||||
|
||||
// 1. 检查溶解氧(使用设备配置的阈值)
|
||||
if (sensorData.getDissolvedOxygen() != null && device.getOxyWarnCallOpen() != null && device.getOxyWarnCallOpen() == 1) {
|
||||
Double dissolvedOxygen = sensorData.getDissolvedOxygen();
|
||||
Double oxyWarnLower = device.getOxyWarnLower();
|
||||
|
||||
if (oxyWarnLower != null && dissolvedOxygen < oxyWarnLower) {
|
||||
String content = String.format("溶解氧过低: %.2f mg/L (最低: %.2f)",
|
||||
dissolvedOxygen, oxyWarnLower);
|
||||
alarmMessage.append(content).append("; ");
|
||||
|
||||
AquAlarmHistory alarm = createAlarmHistory(deviceId, deviceName, ALARM_TYPE_DISSOLVED_OXYGEN,
|
||||
ALARM_LEVEL_URGENT, content, dissolvedOxygen, oxyWarnLower);
|
||||
alarmList.add(alarm);
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查水温(使用设备配置的阈值)
|
||||
if (sensorData.getTemperature() != null && device.getTempWarnCallOpen() != null && device.getTempWarnCallOpen() == 1) {
|
||||
Double temperature = sensorData.getTemperature();
|
||||
Double tempWarnLower = device.getTempWarnLower();
|
||||
Double tempWarnUpper = device.getTempWarnUpper();
|
||||
|
||||
if (tempWarnLower != null && temperature < tempWarnLower) {
|
||||
String content = String.format("水温过低: %.2f °C (最低: %.2f)",
|
||||
temperature, tempWarnLower);
|
||||
alarmMessage.append(content).append("; ");
|
||||
|
||||
AquAlarmHistory alarm = createAlarmHistory(deviceId, deviceName, ALARM_TYPE_TEMPERATURE,
|
||||
ALARM_LEVEL_IMPORTANT, content, temperature, tempWarnLower);
|
||||
alarmList.add(alarm);
|
||||
hasAlarm = true;
|
||||
} else if (tempWarnUpper != null && temperature > tempWarnUpper) {
|
||||
String content = String.format("水温过高: %.2f °C (最高: %.2f)",
|
||||
temperature, tempWarnUpper);
|
||||
alarmMessage.append(content).append("; ");
|
||||
|
||||
AquAlarmHistory alarm = createAlarmHistory(deviceId, deviceName, ALARM_TYPE_TEMPERATURE,
|
||||
ALARM_LEVEL_IMPORTANT, content, temperature, tempWarnUpper);
|
||||
alarmList.add(alarm);
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查电池电量(使用设备配置的阈值)
|
||||
if (sensorData.getBattery() != null && device.getBatteryWarnCallOpen() != null && device.getBatteryWarnCallOpen() == 1) {
|
||||
Double battery = sensorData.getBattery();
|
||||
Double batteryWarnLower = device.getBatteryWarnLower() != null ? device.getBatteryWarnLower().doubleValue() : null;
|
||||
|
||||
if (batteryWarnLower != null && battery < batteryWarnLower) {
|
||||
String content = String.format("电池电量低: %.2f%% (最低: %.2f%%)",
|
||||
battery, batteryWarnLower);
|
||||
alarmMessage.append(content).append("; ");
|
||||
|
||||
AquAlarmHistory alarm = createAlarmHistory(deviceId, deviceName, ALARM_TYPE_BATTERY,
|
||||
ALARM_LEVEL_NORMAL, content, battery, batteryWarnLower);
|
||||
alarmList.add(alarm);
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有告警,保存告警历史并触发通知
|
||||
if (hasAlarm) {
|
||||
log.warn("[告警] {} - {}", deviceName, alarmMessage);
|
||||
|
||||
// 保存告警历史
|
||||
for (AquAlarmHistory alarm : alarmList) {
|
||||
alarmHistoryMapper.insert(alarm);
|
||||
}
|
||||
|
||||
// 触发通知(只对紧急和重要告警发送通知)
|
||||
boolean shouldNotify = alarmList.stream()
|
||||
.anyMatch(alarm -> alarm.getAlarmLevel() >= ALARM_LEVEL_IMPORTANT);
|
||||
|
||||
if (shouldNotify) {
|
||||
triggerAlarmNotification(device, alarmMessage.toString(), sensorData, alarmList);
|
||||
}
|
||||
} else {
|
||||
// 没有告警,检查是否有未恢复的告警需要标记为已恢复
|
||||
checkAndRecoverAlarms(deviceId, device, sensorData);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查报警失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理温度数据
|
||||
* 触发告警通知
|
||||
* 创建电话通知记录,并发送语音通知
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param alarmMessage 告警信息
|
||||
* @param sensorData 传感器数据
|
||||
* @param alarmList 告警列表
|
||||
*/
|
||||
private void handleTemperature(Object value) {
|
||||
// 示例:温度告警
|
||||
if (value instanceof Number) {
|
||||
double temp = ((Number) value).doubleValue();
|
||||
if (temp > 80) {
|
||||
log.warn("温度过高告警: {}°C", temp);
|
||||
// TODO: 发送告警通知
|
||||
private void triggerAlarmNotification(Device device, String alarmMessage,
|
||||
DeviceSensorData sensorData, java.util.List<AquAlarmHistory> alarmList) {
|
||||
try {
|
||||
Long deviceId = device.getId();
|
||||
String deviceName = device.getDeviceName();
|
||||
|
||||
// 检查设备的免打扰设置
|
||||
boolean shouldSkip = false;
|
||||
for (AquAlarmHistory alarm : alarmList) {
|
||||
if (ALARM_TYPE_DISSOLVED_OXYGEN.equals(alarm.getAlarmType()) &&
|
||||
device.getOxyWarnCallNoDis() != null && device.getOxyWarnCallNoDis() == 1) {
|
||||
shouldSkip = true;
|
||||
break;
|
||||
}
|
||||
if (ALARM_TYPE_TEMPERATURE.equals(alarm.getAlarmType()) &&
|
||||
device.getTempWarnCallNoDis() != null && device.getTempWarnCallNoDis() == 1) {
|
||||
shouldSkip = true;
|
||||
break;
|
||||
}
|
||||
if (ALARM_TYPE_BATTERY.equals(alarm.getAlarmType()) &&
|
||||
device.getBatteryWarnCallNoDis() != null && device.getBatteryWarnCallNoDis() == 1) {
|
||||
shouldSkip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否在告警间隔时间内已经发送过通知(防止频繁通知)
|
||||
LocalDateTime intervalTime = LocalDateTime.now().minusMinutes(ALARM_NOTIFICATION_INTERVAL);
|
||||
|
||||
// 查询最近的通知记录
|
||||
long recentNoticeCount = warnCallNoticeMapper.selectCount(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AquWarnCallNotice>()
|
||||
.eq(AquWarnCallNotice::getDeviceId, deviceId)
|
||||
.ge(AquWarnCallNotice::getCallTime, intervalTime)
|
||||
);
|
||||
|
||||
if (recentNoticeCount > 0) {
|
||||
log.debug("[告警] {} - {}分钟内已通知", deviceName, ALARM_NOTIFICATION_INTERVAL);
|
||||
|
||||
// 更新告警历史为已通知,但不发送实际通知
|
||||
for (AquAlarmHistory alarm : alarmList) {
|
||||
alarm.setNotified(1);
|
||||
alarm.setUpdatedTime(LocalDateTime.now());
|
||||
alarmHistoryMapper.updateById(alarm);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 从设备信息获取用户信息
|
||||
Long userId = device.getUserId();
|
||||
// TODO: 从用户表查询手机号
|
||||
String mobilePhone = sensorData.getMobilePhone(); // 临时使用传感器数据中的手机号
|
||||
// TODO: 从塘口表查询塘口名称
|
||||
String pondName = deviceName; // 临时使用设备名
|
||||
|
||||
// 验证必要信息
|
||||
if (userId == null || StrUtil.isBlank(mobilePhone)) {
|
||||
log.warn("[告警] {} - 信息不完整", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建告警通知记录
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(mobilePhone);
|
||||
callNotice.setDeviceId(deviceId);
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(LocalDateTime.now());
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
callNotice.setCreatedTime(LocalDateTime.now());
|
||||
callNotice.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
// 保存到数据库
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
|
||||
// 更新告警历史中的通知ID
|
||||
for (AquAlarmHistory alarm : alarmList) {
|
||||
alarm.setNoticeId(callNotice.getId());
|
||||
alarm.setNotified(1);
|
||||
alarm.setUpdatedTime(LocalDateTime.now());
|
||||
alarmHistoryMapper.updateById(alarm);
|
||||
}
|
||||
|
||||
// 发送语音通知
|
||||
sendVoiceNotification(callNotice, alarmMessage);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[告警通知] 触发告警通知失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// 发送语音通知
|
||||
* @param callNotice 通知记录
|
||||
* @param alarmMessage 告警信息
|
||||
*/
|
||||
private void sendVoiceNotification(AquWarnCallNotice callNotice, String alarmMessage) {
|
||||
try {
|
||||
// 构建语音通知参数
|
||||
Map<String, String> ttsParams = new HashMap<>();
|
||||
ttsParams.put("pond_name", callNotice.getPondName() != null ? callNotice.getPondName() : "未知塘口");
|
||||
ttsParams.put("alarm_info", alarmMessage);
|
||||
|
||||
// 生成业务标识(用于回执关联)
|
||||
String outId = "ALARM_" + callNotice.getId() + "_" + System.currentTimeMillis();
|
||||
|
||||
// 调用 VMS 发送语音通知
|
||||
VmsNoticeResponse response = vmsNoticeService.sendTtsCall(
|
||||
callNotice.getMobilePhone(),
|
||||
ttsParams,
|
||||
outId
|
||||
);
|
||||
|
||||
if (response != null && response.isSuccess()) {
|
||||
// 更新通知记录状态
|
||||
callNotice.setCallStatus(1); // 呼叫成功,等待结果反馈
|
||||
callNotice.setCallId(response.getCallId());
|
||||
callNotice.setOutId(outId);
|
||||
callNotice.setUpdatedTime(LocalDateTime.now());
|
||||
warnCallNoticeMapper.updateById(callNotice);
|
||||
|
||||
log.info("[告警] 呼叫成功: {}", callNotice.getMobilePhone());
|
||||
} else {
|
||||
// 呼叫失败
|
||||
callNotice.setCallStatus(2); // 呼叫失败
|
||||
callNotice.setStatusMsg(response != null ? response.getMessage() : "呼叫失败");
|
||||
callNotice.setUpdatedTime(LocalDateTime.now());
|
||||
warnCallNoticeMapper.updateById(callNotice);
|
||||
|
||||
log.error("[告警] 呼叫失败: {} - {}", callNotice.getMobilePhone(), callNotice.getStatusMsg());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[告警] 语音通知异常: {}", e.getMessage());
|
||||
|
||||
// 更新为呼叫失败
|
||||
try {
|
||||
callNotice.setCallStatus(2);
|
||||
callNotice.setStatusMsg("发送异常: " + e.getMessage());
|
||||
callNotice.setUpdatedTime(LocalDateTime.now());
|
||||
warnCallNoticeMapper.updateById(callNotice);
|
||||
} catch (Exception ex) {
|
||||
log.error("[告警] 更新失败: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理湿度数据
|
||||
* 触发设备故障告警
|
||||
* 针对设备事件(error)的紧急告警
|
||||
*
|
||||
* @param deviceName 设备名称
|
||||
* @param params 事件参数
|
||||
*/
|
||||
private void handleHumidity(Object value) {
|
||||
// 示例:湿度处理逻辑
|
||||
log.debug("湿度数据: {}", value);
|
||||
private void triggerDeviceErrorAlarm(String deviceName, JSONObject params) {
|
||||
try {
|
||||
String errorMsg = params != null ? params.toString() : "设备故障";
|
||||
log.error("[设备故障] 设备: {}, 故障信息: {}", deviceName, errorMsg);
|
||||
|
||||
// TODO: 实现紧急告警逻辑
|
||||
// 1. 查询设备信息和用户
|
||||
// 2. 创建紧急告警记录
|
||||
// 3. 立即发送语音通知
|
||||
// 4. 可选:发送短信、推送消息等多种通知方式
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[设备故障] 触发故障告警失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理状态数据
|
||||
* 创建告警历史记录
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param deviceName 设备名称
|
||||
* @param alarmType 告警类型
|
||||
* @param alarmLevel 告警级别
|
||||
* @param alarmContent 告警内容
|
||||
* @param alarmValue 告警值
|
||||
* @param thresholdValue 阈值
|
||||
* @return 告警历史对象
|
||||
*/
|
||||
private void handleStatus(Object value) {
|
||||
// 示例:设备状态变化
|
||||
log.info("设备状态变化: {}", value);
|
||||
private AquAlarmHistory createAlarmHistory(Long deviceId, String deviceName, String alarmType,
|
||||
int alarmLevel, String alarmContent,
|
||||
Double alarmValue, Double thresholdValue) {
|
||||
AquAlarmHistory alarm = new AquAlarmHistory();
|
||||
alarm.setDeviceId(deviceId);
|
||||
alarm.setDeviceName(deviceName);
|
||||
alarm.setAlarmType(alarmType);
|
||||
alarm.setAlarmLevel(alarmLevel);
|
||||
alarm.setAlarmContent(alarmContent);
|
||||
alarm.setAlarmValue(alarmValue);
|
||||
alarm.setThresholdValue(thresholdValue);
|
||||
alarm.setAlarmStatus(ALARM_STATUS_PENDING);
|
||||
alarm.setAlarmTime(LocalDateTime.now());
|
||||
alarm.setNotified(0);
|
||||
alarm.setCreatedTime(LocalDateTime.now());
|
||||
alarm.setUpdatedTime(LocalDateTime.now());
|
||||
return alarm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并标记已恢复的告警
|
||||
* 当传感器数据恢复正常时,将未恢复的告警标记为已恢复
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param device 设备信息
|
||||
* @param sensorData 传感器数据
|
||||
*/
|
||||
private void checkAndRecoverAlarms(Long deviceId, Device device, DeviceSensorData sensorData) {
|
||||
try {
|
||||
// 查询该设备所有未恢复的告警
|
||||
java.util.List<AquAlarmHistory> pendingAlarms = alarmHistoryMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AquAlarmHistory>()
|
||||
.eq(AquAlarmHistory::getDeviceId, deviceId)
|
||||
.ne(AquAlarmHistory::getAlarmStatus, ALARM_STATUS_RECOVERED)
|
||||
);
|
||||
|
||||
if (pendingAlarms.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for (AquAlarmHistory alarm : pendingAlarms) {
|
||||
boolean recovered = false;
|
||||
|
||||
// 根据告警类型检查是否已恢复(使用设备配置的阈值)
|
||||
switch (alarm.getAlarmType()) {
|
||||
case ALARM_TYPE_DISSOLVED_OXYGEN:
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
Double value = sensorData.getDissolvedOxygen();
|
||||
Double oxyWarnLower = device.getOxyWarnLower();
|
||||
recovered = oxyWarnLower != null && value >= oxyWarnLower;
|
||||
}
|
||||
break;
|
||||
|
||||
case ALARM_TYPE_TEMPERATURE:
|
||||
if (sensorData.getTemperature() != null) {
|
||||
Double value = sensorData.getTemperature();
|
||||
Double tempWarnLower = device.getTempWarnLower();
|
||||
Double tempWarnUpper = device.getTempWarnUpper();
|
||||
recovered = (tempWarnLower == null || value >= tempWarnLower) &&
|
||||
(tempWarnUpper == null || value <= tempWarnUpper);
|
||||
}
|
||||
break;
|
||||
|
||||
case ALARM_TYPE_BATTERY:
|
||||
if (sensorData.getBattery() != null) {
|
||||
Double batteryWarnLower = device.getBatteryWarnLower() != null ? device.getBatteryWarnLower().doubleValue() : null;
|
||||
recovered = batteryWarnLower != null && sensorData.getBattery() >= batteryWarnLower;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果告警已恢复,更新状态
|
||||
if (recovered) {
|
||||
alarm.setAlarmStatus(ALARM_STATUS_RECOVERED);
|
||||
alarm.setRecoveryTime(now);
|
||||
alarm.setUpdatedTime(now);
|
||||
alarmHistoryMapper.updateById(alarm);
|
||||
log.info("[恢复] {} - {}", alarm.getDeviceName(), alarm.getAlarmType());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查告警恢复失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 TDengine 传感器数据格式
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceName 设备名称
|
||||
* @param params 设备参数
|
||||
* @return DeviceSensorData
|
||||
*/
|
||||
private DeviceSensorData convertToSensorData(String productKey, String deviceName, JSONObject params) {
|
||||
try {
|
||||
DeviceSensorData sensorData = new DeviceSensorData();
|
||||
|
||||
// 时间字段
|
||||
String now = LocalDateTime.now().format(DATETIME_FORMATTER);
|
||||
sensorData.setTime(now);
|
||||
sensorData.setCreateTime(now);
|
||||
|
||||
// 设备标识(使用 deviceName 作为 serialNum)
|
||||
sensorData.setSerialNum(deviceName);
|
||||
sensorData.setDeviceName(deviceName);
|
||||
|
||||
// 解析传感器数据(根据实际字段映射)
|
||||
if (params.containsKey("dissolvedoxygen")) {
|
||||
sensorData.setDissolvedOxygen(params.getDouble("dissolvedoxygen"));
|
||||
}
|
||||
if (params.containsKey("temperature")) {
|
||||
sensorData.setTemperature(params.getDouble("temperature"));
|
||||
}
|
||||
if (params.containsKey("saturability")) {
|
||||
sensorData.setSaturability(params.getDouble("saturability"));
|
||||
}
|
||||
if (params.containsKey("ph")) {
|
||||
sensorData.setPh(params.getDouble("ph"));
|
||||
}
|
||||
if (params.containsKey("salinity")) {
|
||||
sensorData.setSalinity(params.getDouble("salinity"));
|
||||
}
|
||||
if (params.containsKey("treference")) {
|
||||
sensorData.setTreference(params.getDouble("treference"));
|
||||
}
|
||||
if (params.containsKey("tfluorescence")) {
|
||||
sensorData.setTfluorescence(params.getDouble("tfluorescence"));
|
||||
}
|
||||
if (params.containsKey("phasedifference")) {
|
||||
sensorData.setPhaseDifference(params.getDouble("phasedifference"));
|
||||
}
|
||||
if (params.containsKey("battery")) {
|
||||
sensorData.setBattery(params.getDouble("battery"));
|
||||
}
|
||||
|
||||
// TODO: 如果需要设置 deviceId、userId 等标签字段,需要从设备管理系统查询
|
||||
// 可以通过 deviceName 或 productKey 查询设备信息
|
||||
// DeviceInfo deviceInfo = deviceService.getByDeviceName(deviceName);
|
||||
// if (deviceInfo != null) {
|
||||
// sensorData.setDeviceId(deviceInfo.getId());
|
||||
// sensorData.setUserId(deviceInfo.getUserId());
|
||||
// sensorData.setUserName(deviceInfo.getUserName());
|
||||
// sensorData.setMobilePhone(deviceInfo.getMobilePhone());
|
||||
// sensorData.setDeviceType(deviceInfo.getDeviceType());
|
||||
// }
|
||||
|
||||
return sensorData;
|
||||
} catch (Exception e) {
|
||||
log.error("转换传感器数据失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备表的实时数据
|
||||
*
|
||||
* @param deviceName 设备名称
|
||||
* @param sensorData 传感器数据
|
||||
*/
|
||||
private void updateDeviceRealTimeData(String deviceName, DeviceSensorData sensorData) {
|
||||
try {
|
||||
// 查询设备
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (device == null) {
|
||||
log.debug("设备不存在,无法更新实时数据: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新实时数据
|
||||
Device updateDevice = new Device();
|
||||
updateDevice.setId(device.getId());
|
||||
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
updateDevice.setValueDissolvedOxygen(sensorData.getDissolvedOxygen());
|
||||
}
|
||||
if (sensorData.getTemperature() != null) {
|
||||
updateDevice.setValueTemperature(sensorData.getTemperature());
|
||||
}
|
||||
if (sensorData.getSaturability() != null) {
|
||||
updateDevice.setValueSaturability(sensorData.getSaturability());
|
||||
}
|
||||
if (sensorData.getPh() != null) {
|
||||
updateDevice.setValuePh(sensorData.getPh());
|
||||
}
|
||||
if (sensorData.getSalinity() != null) {
|
||||
updateDevice.setValueSalinity(sensorData.getSalinity());
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
int updated = deviceMapper.updateById(updateDevice);
|
||||
if (updated > 0) {
|
||||
log.debug("设备实时数据已更新: {}", deviceName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新设备实时数据失败[{}]: {}", deviceName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user