fix: 设备24小时内到期,电话通知修改。
This commit is contained in:
@@ -157,6 +157,7 @@ public class DeviceDataHandler {
|
|||||||
private static final String ALARM_TYPE_BATTERY = "电池电量";
|
private static final String ALARM_TYPE_BATTERY = "电池电量";
|
||||||
private static final String ALARM_TYPE_SATURA_HIGH = "饱和度过高";
|
private static final String ALARM_TYPE_SATURA_HIGH = "饱和度过高";
|
||||||
private static final String ALARM_TYPE_DETECTOR_OXY_OFFLINE = "溶解氧离线";
|
private static final String ALARM_TYPE_DETECTOR_OXY_OFFLINE = "溶解氧离线";
|
||||||
|
private static final String ALARM_TYPE_DEVICE_EXPIRATION = "设备即将到期";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1371,6 +1372,127 @@ public class DeviceDataHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发设备即将到期告警(由定时任务调用)
|
||||||
|
* 设备到期时间距离当前时间不到24小时时,打电话通知用户提醒设备即将到期。
|
||||||
|
*
|
||||||
|
* @param device 设备信息(由定时任务查询后传入)
|
||||||
|
*/
|
||||||
|
public void triggerDeviceExpirationAlarm(Device device) {
|
||||||
|
// 全局电话通知开关检查
|
||||||
|
if (!aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
|
log.debug("[到期告警] 电话通知已全局关闭,跳过: {}", device.getSerialNum());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Long deviceId = device.getId();
|
||||||
|
Long userId = device.getUserId();
|
||||||
|
String deviceName = device.getSerialNum();
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
log.debug("[到期告警] 设备未绑定用户,跳过: {}", deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 warnPhoneJson 获取告警手机号列表
|
||||||
|
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双重抑制:1) 活跃批次检查(防止当前轮重复); 2) 成功通知抑制(24h内已成功则不再打)
|
||||||
|
int maxBatchMinutes = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2;
|
||||||
|
LocalDateTime activeBatchStart = LocalDateTime.now().minusMinutes(maxBatchMinutes);
|
||||||
|
long activeCount = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, ALARM_TYPE_DEVICE_EXPIRATION, activeBatchStart);
|
||||||
|
if (activeCount > 0) {
|
||||||
|
log.debug("[到期告警] 当前有活跃通知批次在进行,跳过: {}", deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LocalDateTime suppressTime = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours());
|
||||||
|
long recentCount = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, ALARM_TYPE_DEVICE_EXPIRATION, suppressTime);
|
||||||
|
if (recentCount > 0) {
|
||||||
|
log.debug("[到期告警] {}h内已有回执成功的通知,跳过: {}", aliyunIotProperties.getCallSuccessSuppressHours(), deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询塘口名称(设备未绑定塘口时使用"未绑定塘口")
|
||||||
|
String pondName = "未绑定塘口";
|
||||||
|
if (device.getPondId() != null) {
|
||||||
|
Pond pond = pondMapper.selectById(device.getPondId());
|
||||||
|
if (pond != null) {
|
||||||
|
pondName = pond.getPondName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String displayDeviceName = device.getDeviceName() != null ? device.getDeviceName() : deviceName;
|
||||||
|
|
||||||
|
// 计算剩余小时数
|
||||||
|
long remainingHours = java.time.Duration.between(
|
||||||
|
LocalDateTime.now(),
|
||||||
|
device.getDeadTime().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()
|
||||||
|
).toHours();
|
||||||
|
|
||||||
|
// 告警消息内容
|
||||||
|
String warnMsg = String.format("%s(%s)服务即将到期,剩余不到%d小时,请及时续费",
|
||||||
|
displayDeviceName, deviceName, Math.max(remainingHours, 1));
|
||||||
|
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, ALARM_TYPE_DEVICE_EXPIRATION, WARN_TYPE_OFFLINE, warnMsg);
|
||||||
|
messageWarnMapper.insert(warn);
|
||||||
|
|
||||||
|
// 创建通知记录(为每个手机号 × 重试次数)
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 保存关联记录
|
||||||
|
if (warn.getId() != null) {
|
||||||
|
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||||
|
mapRecord.setMessageWarnId(warn.getId());
|
||||||
|
mapRecord.setCallNoticeId(callNotice.getId());
|
||||||
|
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstCallNotice == null) {
|
||||||
|
firstCallNotice = callNotice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即尝试发送第一条通知
|
||||||
|
if (firstCallNotice != null) {
|
||||||
|
sendVoiceNotification(firstCallNotice, displayDeviceName, ALARM_TYPE_DEVICE_EXPIRATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[到期告警] 已触发通知 - 设备:{}, 手机号数:{}, 剩余小时:{}", deviceName, phoneList.size(), remainingHours);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[到期告警] 处理失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 触发设备故障告警(对应 IoT 事件路径 error 事件)
|
* 触发设备故障告警(对应 IoT 事件路径 error 事件)
|
||||||
* 参考 C# CreateNoticeCall 逻辑:查设备→查塘口→写告警消息→创建电话通知记录→立即发送
|
* 参考 C# CreateNoticeCall 逻辑:查设备→查塘口→写告警消息→创建电话通知记录→立即发送
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.intc.iot.task;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.intc.fishery.domain.Device;
|
||||||
|
import com.intc.fishery.mapper.DeviceMapper;
|
||||||
|
import com.intc.iot.handler.DeviceDataHandler;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备即将到期告警定时任务
|
||||||
|
* 每小时扫描一次设备表,对到期时间距离当前不到24小时的设备触发电话通知,提醒用户续费。
|
||||||
|
*
|
||||||
|
* @author intc-iot
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ConditionalOnBean(DeviceDataHandler.class)
|
||||||
|
public class DeviceExpirationWarnTask {
|
||||||
|
|
||||||
|
private final DeviceDataHandler deviceDataHandler;
|
||||||
|
|
||||||
|
private final DeviceMapper deviceMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每2小时执行一次,扫描即将到期(24小时内)的设备并触发电话通知
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedDelay = 7200000)
|
||||||
|
public void checkDeviceExpirationAndNotify() {
|
||||||
|
try {
|
||||||
|
Date now = new Date();
|
||||||
|
// 24小时后的时间点
|
||||||
|
Date deadline = new Date(now.getTime() + 24L * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// 查询所有满足条件的设备:
|
||||||
|
// 1. deadTime 不为空
|
||||||
|
// 2. deadTime > 当前时间(尚未到期)
|
||||||
|
// 3. deadTime <= 当前时间 + 24小时(即将到期)
|
||||||
|
// 4. userId 不为空(已绑定用户)
|
||||||
|
List<Device> expiringDevices = deviceMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<Device>()
|
||||||
|
.isNotNull(Device::getDeadTime)
|
||||||
|
.gt(Device::getDeadTime, now)
|
||||||
|
.le(Device::getDeadTime, deadline)
|
||||||
|
.isNotNull(Device::getUserId)
|
||||||
|
.select(Device::getId, Device::getUserId, Device::getSerialNum,
|
||||||
|
Device::getDeviceName, Device::getDeviceType, Device::getPondId,
|
||||||
|
Device::getDeadTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (expiringDevices == null || expiringDevices.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[到期告警] 扫描到 {} 台即将到期的设备,开始逐一通知", expiringDevices.size());
|
||||||
|
|
||||||
|
for (Device device : expiringDevices) {
|
||||||
|
try {
|
||||||
|
deviceDataHandler.triggerDeviceExpirationAlarm(device);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[到期告警] 处理单台设备异常,deviceId={}: {}", device.getId(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[到期告警] 扫描设备到期告警异常: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user