fix: 电话通知报警,逻辑完善,改为报警电话列表获取电话号码,通知成功后24小时不再打电话等等。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.intc.iot.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.intc.iot.domain.AquMapMessageWarnCallNotice;
|
||||
@@ -130,12 +131,6 @@ public class DeviceDataHandler {
|
||||
private static final String ALARM_TYPE_TEMPERATURE_LOW = "温度过低";
|
||||
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; // 紧急
|
||||
|
||||
/**
|
||||
* 消息未读状态
|
||||
@@ -369,9 +364,11 @@ public class DeviceDataHandler {
|
||||
StringBuilder alarmMessage = new StringBuilder();
|
||||
boolean hasAlarm = false;
|
||||
java.util.List<MessageWarn> warnList = new java.util.ArrayList<>();
|
||||
// 需要触发电话通知的告警列表(仅包含开启了电话通知开关的告警)
|
||||
java.util.List<MessageWarn> callWarnList = new java.util.ArrayList<>();
|
||||
|
||||
// 1. 检查溶解氧(使用设备配置的阈值)
|
||||
if (sensorData.getDissolvedOxygen() != null && device.getOxyWarnCallOpen() != null && device.getOxyWarnCallOpen() == 1) {
|
||||
// 1. 检查溶解氧(阈值判断与告警记录不受电话开关限制)
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
Double dissolvedOxygen = sensorData.getDissolvedOxygen();
|
||||
Double oxyWarnLower = device.getOxyWarnLower();
|
||||
|
||||
@@ -380,14 +377,19 @@ public class DeviceDataHandler {
|
||||
dissolvedOxygen, oxyWarnLower);
|
||||
alarmMessage.append(message).append("; ");
|
||||
|
||||
warnList.add(createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_DISSOLVED_OXYGEN, WARN_TYPE_DISSOLVED_OXYGEN, message));
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_DISSOLVED_OXYGEN, WARN_TYPE_DISSOLVED_OXYGEN, message);
|
||||
warnList.add(warn);
|
||||
// 仅在开启电话通知时加入电话通知列表
|
||||
if (device.getOxyWarnCallOpen() != null && device.getOxyWarnCallOpen() == 1) {
|
||||
callWarnList.add(warn);
|
||||
}
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查水温(使用设备配置的阈值)
|
||||
if (sensorData.getTemperature() != null && device.getTempWarnCallOpen() != null && device.getTempWarnCallOpen() == 1) {
|
||||
// 2. 检查水温(阈值判断与告警记录不受电话开关限制)
|
||||
if (sensorData.getTemperature() != null) {
|
||||
Double temperature = sensorData.getTemperature();
|
||||
Double tempWarnLower = device.getTempWarnLower();
|
||||
Double tempWarnUpper = device.getTempWarnUpper();
|
||||
@@ -397,22 +399,30 @@ public class DeviceDataHandler {
|
||||
temperature, tempWarnLower);
|
||||
alarmMessage.append(message).append("; ");
|
||||
|
||||
warnList.add(createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_TEMPERATURE_LOW, WARN_TYPE_TEMPERATURE, message));
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_TEMPERATURE_LOW, WARN_TYPE_TEMPERATURE, message);
|
||||
warnList.add(warn);
|
||||
if (device.getTempWarnCallOpen() != null && device.getTempWarnCallOpen() == 1) {
|
||||
callWarnList.add(warn);
|
||||
}
|
||||
hasAlarm = true;
|
||||
} else if (tempWarnUpper != null && temperature > tempWarnUpper) {
|
||||
String message = String.format("水温过高: %.2f °C (最高: %.2f)",
|
||||
temperature, tempWarnUpper);
|
||||
alarmMessage.append(message).append("; ");
|
||||
|
||||
warnList.add(createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_TEMPERATURE_HIGH, WARN_TYPE_TEMPERATURE, message));
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_TEMPERATURE_HIGH, WARN_TYPE_TEMPERATURE, message);
|
||||
warnList.add(warn);
|
||||
if (device.getTempWarnCallOpen() != null && device.getTempWarnCallOpen() == 1) {
|
||||
callWarnList.add(warn);
|
||||
}
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查电池电量(使用设备配置的阈值)
|
||||
if (sensorData.getBattery() != null && device.getBatteryWarnCallOpen() != null && device.getBatteryWarnCallOpen() == 1) {
|
||||
// 3. 检查电池电量(阈值判断与告警记录不受电话开关限制)
|
||||
if (sensorData.getBattery() != null) {
|
||||
Double battery = sensorData.getBattery();
|
||||
Double batteryWarnLower = device.getBatteryWarnLower() != null ? device.getBatteryWarnLower().doubleValue() : null;
|
||||
|
||||
@@ -421,23 +431,29 @@ public class DeviceDataHandler {
|
||||
battery, batteryWarnLower);
|
||||
alarmMessage.append(message).append("; ");
|
||||
|
||||
warnList.add(createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_BATTERY, WARN_TYPE_BATTERY, message));
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName,
|
||||
ALARM_TYPE_BATTERY, WARN_TYPE_BATTERY, message);
|
||||
warnList.add(warn);
|
||||
if (device.getBatteryWarnCallOpen() != null && device.getBatteryWarnCallOpen() == 1) {
|
||||
callWarnList.add(warn);
|
||||
}
|
||||
hasAlarm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有告警,保存报警消息并触发通知
|
||||
// 如果有告警,保存全部报警消息记录
|
||||
if (hasAlarm) {
|
||||
log.warn("[告警] {} - {}", deviceName, alarmMessage);
|
||||
|
||||
// 批量保存报警消息
|
||||
// 批量保存报警消息(无论是否开启电话通知,告警记录始终生成)
|
||||
for (MessageWarn warn : warnList) {
|
||||
messageWarnMapper.insert(warn);
|
||||
}
|
||||
|
||||
// 触发电话通知
|
||||
triggerAlarmNotification(device, alarmMessage.toString(), sensorData, warnList);
|
||||
// 仅对开启了电话通知开关的告警触发电话通知
|
||||
if (!callWarnList.isEmpty()) {
|
||||
triggerAlarmNotification(device, alarmMessage.toString(), sensorData, callWarnList);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -445,11 +461,24 @@ public class DeviceDataHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 电话通知间隔时间(分钟)
|
||||
* 参考 C# CallNoticeMinuteInterval = 3
|
||||
*/
|
||||
private static final int CALL_NOTICE_MINUTE_INTERVAL = 3;
|
||||
|
||||
/**
|
||||
* 电话通知重试次数
|
||||
* 参考 C# CallNoticeRetryCount = 3
|
||||
*/
|
||||
private static final int CALL_NOTICE_RETRY_COUNT = 3;
|
||||
|
||||
/**
|
||||
* 触发告警通知
|
||||
* 按 title 分组,每种类型独立判断告警间隔,避免不同类型告警互相屏蔽
|
||||
* 免打扰开启时:每个时段(08:00-14:00、14:00-20:00、20:00-08:00)内只通知一次
|
||||
* 免打扰关闭时:按固定间隔时间判断
|
||||
* 手机号从 warnPhoneJson 获取,支持多号码轮转规避限流
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param alarmMessage 告警信息
|
||||
@@ -462,23 +491,23 @@ public class DeviceDataHandler {
|
||||
Long deviceId = device.getId();
|
||||
String deviceName = device.getDeviceName();
|
||||
|
||||
// 一次性查询用户手机号
|
||||
// 从 warnPhoneJson 获取告警手机号列表
|
||||
Long userId = device.getUserId();
|
||||
String mobilePhone = null;
|
||||
java.util.List<String> phoneList = null;
|
||||
if (userId != null) {
|
||||
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||
if (aquUser != null) {
|
||||
mobilePhone = aquUser.getMobilePhone();
|
||||
phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
||||
}
|
||||
}
|
||||
if (userId == null || StrUtil.isBlank(mobilePhone)) {
|
||||
log.warn("[告警] {} - 信息不完整", deviceName);
|
||||
if (userId == null || phoneList == null || phoneList.isEmpty()) {
|
||||
log.warn("[告警] {} - 未配置告警手机号(warnPhoneJson为空)", deviceName);
|
||||
return;
|
||||
}
|
||||
final String finalMobilePhone = mobilePhone;
|
||||
|
||||
String pondName = warnList.isEmpty() ? deviceName : warnList.get(0).getPondName();
|
||||
LocalDateTime intervalTime = LocalDateTime.now().minusMinutes(aliyunIotProperties.getAlarmNotificationIntervalMinutes());
|
||||
// 抑制时间:根据配置的小时数展开,查找 callStatus=3(回执成功)的记录
|
||||
LocalDateTime intervalTime = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours());
|
||||
LocalDateTime periodStart = getCurrentPeriodStart();
|
||||
|
||||
// 按 title 分组,每种类型独立判断间隔并发送通知
|
||||
@@ -490,17 +519,16 @@ public class DeviceDataHandler {
|
||||
for (Map.Entry<String, java.util.List<MessageWarn>> entry : warnsByTitle.entrySet()) {
|
||||
String title = entry.getKey();
|
||||
java.util.List<MessageWarn> typeWarnList = entry.getValue();
|
||||
int warnType = typeWarnList.get(0).getWarnType();
|
||||
|
||||
// 判断该类型是否开启了免打扰
|
||||
// 判断该类型是否开启了免打扰(按 title 匹配,避免 warnType 值冲突导致判断失效)
|
||||
boolean noDisturbEnabled = false;
|
||||
if (WARN_TYPE_DISSOLVED_OXYGEN == warnType &&
|
||||
if (ALARM_TYPE_DISSOLVED_OXYGEN.equals(title) &&
|
||||
device.getOxyWarnCallNoDis() != null && device.getOxyWarnCallNoDis() == 1) {
|
||||
noDisturbEnabled = true;
|
||||
} else if (WARN_TYPE_TEMPERATURE == warnType &&
|
||||
} else if ((ALARM_TYPE_TEMPERATURE_HIGH.equals(title) || ALARM_TYPE_TEMPERATURE_LOW.equals(title)) &&
|
||||
device.getTempWarnCallNoDis() != null && device.getTempWarnCallNoDis() == 1) {
|
||||
noDisturbEnabled = true;
|
||||
} else if (WARN_TYPE_BATTERY == warnType &&
|
||||
} else if (ALARM_TYPE_BATTERY.equals(title) &&
|
||||
device.getBatteryWarnCallNoDis() != null && device.getBatteryWarnCallNoDis() == 1) {
|
||||
noDisturbEnabled = true;
|
||||
}
|
||||
@@ -515,37 +543,64 @@ public class DeviceDataHandler {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// 免打扰关闭:按固定间隔判断
|
||||
// 免打扰关闭:查找指定小时内是否已有回执成功的通知
|
||||
recentCount = warnCallNoticeMapper.countByDeviceAndTitleAfter(deviceId, title, intervalTime);
|
||||
if (recentCount > 0) {
|
||||
log.debug("[告警] {} title={} - {}min内已通知,跳过", deviceName, title, aliyunIotProperties.getAlarmNotificationIntervalMinutes());
|
||||
log.debug("[告警] {} title={} - {}h内已有回执成功的通知,跳过", deviceName, title, aliyunIotProperties.getCallSuccessSuppressHours());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建这个类型的通知记录
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(finalMobilePhone);
|
||||
callNotice.setDeviceId(deviceId);
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(LocalDateTime.now());
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
// 告警内容(取该类型第一条)
|
||||
String typeAlarmMessage = typeWarnList.get(0).getMessage();
|
||||
|
||||
// 保存报警消息与通知记录的关联关系
|
||||
for (MessageWarn warn : typeWarnList) {
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
// 为每个手机号 × 重试次数,创建通知记录,callTime 按间隔错开
|
||||
// 参考 C#: CallNoticeRetryCount=3, CallNoticeMinuteInterval=3
|
||||
// 立即尝试发送第一个号码;若失败则后续通知记录会在定时任务中按时间发送
|
||||
LocalDateTime baseTime = LocalDateTime.now();
|
||||
int index = 0;
|
||||
AquWarnCallNotice firstCallNotice = null;
|
||||
|
||||
for (int retry = 0; retry < CALL_NOTICE_RETRY_COUNT; retry++) {
|
||||
for (String phoneNum : phoneList) {
|
||||
if (StrUtil.isBlank(phoneNum) || phoneNum.length() < 11) {
|
||||
log.warn("[告警] 无效的手机号: {}", phoneNum);
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDateTime callTime = baseTime.plusMinutes((long) CALL_NOTICE_MINUTE_INTERVAL * index);
|
||||
index++;
|
||||
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(phoneNum);
|
||||
callNotice.setDeviceId(deviceId);
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(callTime);
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
|
||||
// 保存报警消息与通知记录的关联关系
|
||||
for (MessageWarn warn : typeWarnList) {
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCallNotice == null) {
|
||||
firstCallNotice = callNotice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送语音通知(取该类型第一条的告警内容为TTS参数)
|
||||
String typeAlarmMessage = typeWarnList.get(0).getMessage();
|
||||
sendVoiceNotification(callNotice, deviceName, typeAlarmMessage);
|
||||
// 立即尝试发送第一条通知(第一个手机号,第一次重试)
|
||||
// 若限流失败,后续记录的 callTime 到期后由定时任务扫描发送
|
||||
if (firstCallNotice != null) {
|
||||
sendVoiceNotification(firstCallNotice, deviceName, typeAlarmMessage);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -553,6 +608,36 @@ public class DeviceDataHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 warnPhoneJson 字段,返回有效手机号列表
|
||||
* 格式示例: ["13465511438","15726577528","13705354621"]
|
||||
*
|
||||
* @param warnPhoneJson JSON 字符串
|
||||
* @return 手机号列表,解析失败或为空返回 null
|
||||
*/
|
||||
private java.util.List<String> parseWarnPhoneList(String warnPhoneJson) {
|
||||
if (StrUtil.isBlank(warnPhoneJson)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONArray jsonArray = JSONUtil.parseArray(warnPhoneJson);
|
||||
// 使用 LinkedHashSet 去重,同时保持原始顺序
|
||||
java.util.LinkedHashSet<String> phoneSet = new java.util.LinkedHashSet<>();
|
||||
for (Object obj : jsonArray) {
|
||||
if (obj != null && StrUtil.isNotBlank(obj.toString())) {
|
||||
String phone = obj.toString().trim();
|
||||
if (!phoneSet.add(phone)) {
|
||||
log.warn("[告警] warnPhoneJson 中存在重复手机号,已自动去重: {}", phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
return phoneSet.isEmpty() ? null : new java.util.ArrayList<>(phoneSet);
|
||||
} catch (Exception e) {
|
||||
log.error("[告警] 解析 warnPhoneJson 失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前所在时段的起始时间
|
||||
* 时段划分:08:00-14:00 / 14:00-20:00 / 20:00-08:00(跨日)
|
||||
@@ -696,13 +781,17 @@ public class DeviceDataHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询手机号
|
||||
// 从 warnPhoneJson 获取告警手机号列表
|
||||
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||
if (aquUser == null || StrUtil.isBlank(aquUser.getMobilePhone())) {
|
||||
log.warn("[离线告警] 用户手机号不存在,跳过: {}", deviceName);
|
||||
if (aquUser == null) {
|
||||
log.warn("[离线告警] 用户不存在,跳过:{}", deviceName);
|
||||
return;
|
||||
}
|
||||
java.util.List<String> phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
||||
if (phoneList == null || phoneList.isEmpty()) {
|
||||
log.warn("[离线告警] 用户未配置告警手机号(warnPhoneJson为空),跳过:{}", deviceName);
|
||||
return;
|
||||
}
|
||||
String mobilePhone = aquUser.getMobilePhone();
|
||||
|
||||
// 检查告警间隔(防止频繁通知)
|
||||
LocalDateTime intervalTime = LocalDateTime.now().minusMinutes(aliyunIotProperties.getAlarmNotificationIntervalMinutes());
|
||||
@@ -730,28 +819,50 @@ public class DeviceDataHandler {
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, "设备离线", WARN_TYPE_OFFLINE, warnMsg);
|
||||
messageWarnMapper.insert(warn);
|
||||
|
||||
// 创建通知记录
|
||||
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);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
// 创建通知记录(为每个手机号 × 重试次数)
|
||||
LocalDateTime baseTime = LocalDateTime.now();
|
||||
int index = 0;
|
||||
AquWarnCallNotice firstCallNotice = null;
|
||||
|
||||
// 保存关联记录
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
for (int retry = 0; retry < CALL_NOTICE_RETRY_COUNT; retry++) {
|
||||
for (String phoneNum : phoneList) {
|
||||
if (StrUtil.isBlank(phoneNum) || phoneNum.length() < 11) {
|
||||
log.warn("[离线告警] 无效的手机号:{}", phoneNum);
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDateTime callTime = baseTime.plusMinutes((long) CALL_NOTICE_MINUTE_INTERVAL * index);
|
||||
index++;
|
||||
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(phoneNum);
|
||||
callNotice.setDeviceId(deviceId);
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(callTime);
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
|
||||
// 保存关联记录
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
}
|
||||
|
||||
if (firstCallNotice == null) {
|
||||
firstCallNotice = callNotice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送语音通知(TTS 参数用简短告警类型)
|
||||
sendVoiceNotification(callNotice, displayDeviceName, "设备离线");
|
||||
// 立即尝试发送第一条通知
|
||||
if (firstCallNotice != null) {
|
||||
sendVoiceNotification(firstCallNotice, displayDeviceName, "设备离线");
|
||||
}
|
||||
|
||||
log.info("[离线告警] 已触发通知 - 设备: {}, 塘口: {}, 手机: {}", deviceName, pondName, mobilePhone);
|
||||
log.info("[离线告警] 已触发通知 - 设备:{}, 塘口:{}, 手机号数:{}", deviceName, pondName, phoneList.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[离线告警] 处理失败: {}", e.getMessage(), e);
|
||||
|
||||
Reference in New Issue
Block a user