fix: 电话通知逻辑,功能修改,完善机制。
This commit is contained in:
@@ -834,10 +834,12 @@ public class DeviceDataHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response != null && response.isSuccess()) {
|
if (response != null && response.isSuccess()) {
|
||||||
// 更新通知记录状态
|
// 更新通知记录状态,同时将 callTime 更新为实际发出时间
|
||||||
|
// 超时判断基于 callTime,必须使用实际发出时间而非预计呼叫时间
|
||||||
callNotice.setCallStatus(1); // 呼叫成功,等待结果反馈
|
callNotice.setCallStatus(1); // 呼叫成功,等待结果反馈
|
||||||
callNotice.setCallId(response.getCallId());
|
callNotice.setCallId(response.getCallId());
|
||||||
callNotice.setOutId(outId);
|
callNotice.setOutId(outId);
|
||||||
|
callNotice.setCallTime(LocalDateTime.now()); // 记录实际发出时间,用于超时判断
|
||||||
warnCallNoticeMapper.updateById(callNotice);
|
warnCallNoticeMapper.updateById(callNotice);
|
||||||
|
|
||||||
log.info("[告警] 呼叫成功: {}", callNotice.getMobilePhone());
|
log.info("[告警] 呼叫成功: {}", callNotice.getMobilePhone());
|
||||||
@@ -2238,7 +2240,7 @@ public class DeviceDataHandler {
|
|||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
.eq(Device::getSerialNum, deviceName)
|
.eq(Device::getSerialNum, deviceName)
|
||||||
.select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName,
|
.select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName,
|
||||||
Device::getPondId, Device::getValueDissolvedOxygen)
|
Device::getPondId, Device::getValueDissolvedOxygen, Device::getUserId)
|
||||||
.last("LIMIT 1")
|
.last("LIMIT 1")
|
||||||
);
|
);
|
||||||
if (currentDevice == null || currentDevice.getId() == null) {
|
if (currentDevice == null || currentDevice.getId() == null) {
|
||||||
@@ -2334,7 +2336,8 @@ public class DeviceDataHandler {
|
|||||||
deviceMapper.selectOne(
|
deviceMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
.eq(Device::getId, id)
|
.eq(Device::getId, id)
|
||||||
.select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName)
|
.select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName,
|
||||||
|
Device::getUserId)
|
||||||
.last("LIMIT 1")
|
.last("LIMIT 1")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -2356,7 +2359,9 @@ public class DeviceDataHandler {
|
|||||||
&& effectiveDissolvedOxygen <= linkedCtrl.getOxyLowerValue()) {
|
&& effectiveDissolvedOxygen <= linkedCtrl.getOxyLowerValue()) {
|
||||||
log.info("[联动控制] 触发设备={} 塘口最低溶解氧={} ≤ 下限={},开启开关(本机上报={})",
|
log.info("[联动控制] 触发设备={} 塘口最低溶解氧={} ≤ 下限={},开启开关(本机上报={})",
|
||||||
deviceName, effectiveDissolvedOxygen, linkedCtrl.getOxyLowerValue(), dissolvedOxygen);
|
deviceName, effectiveDissolvedOxygen, linkedCtrl.getOxyLowerValue(), dissolvedOxygen);
|
||||||
setDeviceSwitchLinkedOpen(device, switches, true);
|
setDeviceSwitchLinkedOpen(device, switches, true,
|
||||||
|
String.format("溶解氧%.2f ≤ 下限%.2f",
|
||||||
|
effectiveDissolvedOxygen, linkedCtrl.getOxyLowerValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 溶解氧上限联动(带滞回防止重复触发)
|
// 溶解氧上限联动(带滞回防止重复触发)
|
||||||
@@ -2385,7 +2390,9 @@ public class DeviceDataHandler {
|
|||||||
linkedCtrlMapper.updateById(updateCtrl);
|
linkedCtrlMapper.updateById(updateCtrl);
|
||||||
log.info("[联动控制] 触发设备={} 塘口最低溶解氧={} ≥ 上限={},关闭开关",
|
log.info("[联动控制] 触发设备={} 塘口最低溶解氧={} ≥ 上限={},关闭开关",
|
||||||
deviceName, effectiveDissolvedOxygen, linkedCtrl.getOxyUpperValue());
|
deviceName, effectiveDissolvedOxygen, linkedCtrl.getOxyUpperValue());
|
||||||
setDeviceSwitchLinkedOpen(device, switches, false);
|
setDeviceSwitchLinkedOpen(device, switches, false,
|
||||||
|
String.format("溶解氧%.2f ≥ 上限%.2f",
|
||||||
|
effectiveDissolvedOxygen, linkedCtrl.getOxyUpperValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2396,13 +2403,14 @@ public class DeviceDataHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 按联动控制开启/关闭一组开关(参考 C# SetDeviceSwitchLinkedOpen)
|
* 按联动控制开启/关闭一组开关(参考 C# SetDeviceSwitchLinkedOpen)
|
||||||
* 下发成功后更新 DeviceSwitch.isOpen 和 lastTurnTime。
|
* 下发成功后更新 DeviceSwitch.isOpen 和 lastTurnTime,并写入操作记录。
|
||||||
*
|
*
|
||||||
* @param device 设备信息(含 iotId)
|
* @param device 设备信息(含 iotId、userId)
|
||||||
* @param switches 待操作的开关列表
|
* @param switches 待操作的开关列表
|
||||||
* @param open true=开启, false=关闭
|
* @param open true=开启, false=关闭
|
||||||
|
* @param opReasonDesc 操作原因描述(用于操作记录 message 前缀)
|
||||||
*/
|
*/
|
||||||
private void setDeviceSwitchLinkedOpen(Device device, List<com.intc.fishery.domain.DeviceSwitch> switches, boolean open) {
|
private void setDeviceSwitchLinkedOpen(Device device, List<com.intc.fishery.domain.DeviceSwitch> switches, boolean open, String opReasonDesc) {
|
||||||
if (device == null || device.getIotId() == null || switches == null || switches.isEmpty()) {
|
if (device == null || device.getIotId() == null || switches == null || switches.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2432,16 +2440,32 @@ public class DeviceDataHandler {
|
|||||||
boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 1);
|
boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 1);
|
||||||
if (success) {
|
if (success) {
|
||||||
java.util.Date now = new java.util.Date();
|
java.util.Date now = new java.util.Date();
|
||||||
|
String deviceLabel = device.getSerialNum() != null ? device.getSerialNum() : device.getIotId();
|
||||||
for (com.intc.fishery.domain.DeviceSwitch sw : toChange) {
|
for (com.intc.fishery.domain.DeviceSwitch sw : toChange) {
|
||||||
com.intc.fishery.domain.DeviceSwitch updateSw = new com.intc.fishery.domain.DeviceSwitch();
|
com.intc.fishery.domain.DeviceSwitch updateSw = new com.intc.fishery.domain.DeviceSwitch();
|
||||||
updateSw.setId(sw.getId());
|
updateSw.setId(sw.getId());
|
||||||
updateSw.setIsOpen(targetState);
|
updateSw.setIsOpen(targetState);
|
||||||
updateSw.setLastTurnTime(now);
|
updateSw.setLastTurnTime(now);
|
||||||
deviceSwitchMapper.updateById(updateSw);
|
deviceSwitchMapper.updateById(updateSw);
|
||||||
|
// 写入操作记录
|
||||||
|
if (device.getUserId() != null) {
|
||||||
|
String dName = device.getDeviceName() != null ? device.getDeviceName() : deviceLabel;
|
||||||
|
String switchName = sw.getSwitchName() != null ? sw.getSwitchName() : ("Switch" + sw.getIndex());
|
||||||
|
String reasonPart = opReasonDesc != null ? "(" + opReasonDesc + ")" : "";
|
||||||
|
com.intc.fishery.utils.MessageOpRecordUtil.addMessageOpRecordLinked(
|
||||||
|
device.getUserId(),
|
||||||
|
"开关操作",
|
||||||
|
String.format("%s(%s)的开关%s%s:%s%s。",
|
||||||
|
dName, deviceLabel,
|
||||||
|
sw.getIndex() != null ? sw.getIndex() + " " : "",
|
||||||
|
switchName,
|
||||||
|
open ? "开启" : "关闭",
|
||||||
|
reasonPart)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.info("[联动控制] 设备={} 开关下发成功:{} -> {}",
|
log.info("[联动控制] 设备={} 开关下发成功:{} -> {}",
|
||||||
device.getSerialNum() != null ? device.getSerialNum() : device.getIotId(),
|
deviceLabel, properties.keySet(), open ? "开启" : "关闭");
|
||||||
properties.keySet(), open ? "开启" : "关闭");
|
|
||||||
} else {
|
} else {
|
||||||
log.warn("[联动控制] 设备={} 开关下发失败:{}",
|
log.warn("[联动控制] 设备={} 开关下发失败:{}",
|
||||||
device.getSerialNum() != null ? device.getSerialNum() : device.getIotId(),
|
device.getSerialNum() != null ? device.getSerialNum() : device.getIotId(),
|
||||||
|
|||||||
@@ -104,6 +104,38 @@ public interface AquWarnCallNoticeMapper extends BaseMapper<AquWarnCallNotice> {
|
|||||||
@Select("SELECT * FROM aqu_call_notice WHERE call_status = 0 AND call_time <= #{now} ORDER BY call_time ASC")
|
@Select("SELECT * FROM aqu_call_notice WHERE call_status = 0 AND call_time <= #{now} ORDER BY call_time ASC")
|
||||||
List<AquWarnCallNotice> selectDuePendingNotices(@Param("now") LocalDateTime now);
|
List<AquWarnCallNotice> selectDuePendingNotices(@Param("now") LocalDateTime now);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定设备在指定时间之后是否已有「等待回执中(callStatus=1)」或「回执成功(callStatus=3)」的通知记录
|
||||||
|
* 用于定时任务发出待发通知前的拦截判断:
|
||||||
|
* - callStatus=1:前一条已发出但回执未到,不应重复拨打,等其超时后再发
|
||||||
|
* - callStatus=3:已成功,直接取消后续记录
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param afterTime 批次窗口起始时间
|
||||||
|
* @return 满足条件的通知数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(1) FROM aqu_call_notice " +
|
||||||
|
"WHERE device_id = #{deviceId} " +
|
||||||
|
"AND call_status IN (1, 3) " +
|
||||||
|
"AND call_time >= #{afterTime}")
|
||||||
|
long countWaitingOrSuccessByDeviceAfter(@Param("deviceId") Long deviceId,
|
||||||
|
@Param("afterTime") LocalDateTime afterTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定设备在指定时间之后是否已有「回执成功」的通知记录(不区分 title)
|
||||||
|
* 用于定时任务发出待发通知前,判断同批次是否已有成功回执,避免重复拨打
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param afterTime 起始时间(通常取批次第一条记录的 callTime)
|
||||||
|
* @return 满足条件的通知数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(1) FROM aqu_call_notice " +
|
||||||
|
"WHERE device_id = #{deviceId} " +
|
||||||
|
"AND call_status = 3 " +
|
||||||
|
"AND call_time >= #{afterTime}")
|
||||||
|
long countSuccessByDeviceAfter(@Param("deviceId") Long deviceId,
|
||||||
|
@Param("afterTime") LocalDateTime afterTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过 callNoticeId 查询关联的第一条告警消息内容
|
* 通过 callNoticeId 查询关联的第一条告警消息内容
|
||||||
* 用于定时任务重发时构建 TTS 参数
|
* 用于定时任务重发时构建 TTS 参数
|
||||||
|
|||||||
@@ -56,10 +56,11 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 回执超时时间(分钟)
|
* 回执超时时间(分钟)
|
||||||
* VMS 回执延迟较长,设为 15 分钟,给 VMS 平台充足的回调缓冲时间
|
* 从呼叫实际发出(callTime 已修正为真实发出时间)到收到 VMS 回执的最长等待时间。
|
||||||
* 注意:超时判断基于 callTime(预计呼叫时间),实际呼叫发起后回调可能延迟数分钟到达
|
* 正常链路:通话约 30s + VMS 生成回执约 1~2min,3 分钟内完成。
|
||||||
|
* 设为 5 分钟,兼顾回执延迟容忍与批次重试及时性(批次间隔 3 分钟)。
|
||||||
*/
|
*/
|
||||||
private static final int CALLBACK_TIMEOUT_MINUTES = 15;
|
private static final int CALLBACK_TIMEOUT_MINUTES = 5;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -258,6 +259,20 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
int sentCount = 0;
|
int sentCount = 0;
|
||||||
for (AquWarnCallNotice notice : pendingList) {
|
for (AquWarnCallNotice notice : pendingList) {
|
||||||
try {
|
try {
|
||||||
|
// 发出前先检查同设备同批次窗口内的状态
|
||||||
|
// 批次窗口 = 当前记录的 callTime 往前 CALLBACK_TIMEOUT_MINUTES 分钟(足够覆盖整个批次)
|
||||||
|
// - status=1(等待回执中):前一条已发出但回执未到,不重复拨打,等其超时后再发
|
||||||
|
// - status=3(已成功):已有成功回执,跳过该条记录
|
||||||
|
if (notice.getDeviceId() != null) {
|
||||||
|
LocalDateTime batchWindowStart = notice.getCallTime().minusMinutes(CALLBACK_TIMEOUT_MINUTES);
|
||||||
|
long blockingCount = warnCallNoticeMapper.countWaitingOrSuccessByDeviceAfter(
|
||||||
|
notice.getDeviceId(), batchWindowStart);
|
||||||
|
if (blockingCount > 0) {
|
||||||
|
log.info("[待发通知] 设备 {} 同批次内已有等待回执或成功的记录,跳过记录ID: {}", notice.getDeviceId(), notice.getId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 查询关联的告警标题(title),作为 TTS warnMessage 参数
|
// 查询关联的告警标题(title),作为 TTS warnMessage 参数
|
||||||
// 使用 title(如"设备离线"、"溶解氧")而非完整 message,避免长字符串被 TTS 逐字播报
|
// 使用 title(如"设备离线"、"溶解氧")而非完整 message,避免长字符串被 TTS 逐字播报
|
||||||
String warnTitle = warnCallNoticeMapper.selectWarnTitleByCallNoticeId(notice.getId());
|
String warnTitle = warnCallNoticeMapper.selectWarnTitleByCallNoticeId(notice.getId());
|
||||||
@@ -291,6 +306,7 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
notice.setCallStatus(CALL_STATUS_CALL_SUCCESS);
|
notice.setCallStatus(CALL_STATUS_CALL_SUCCESS);
|
||||||
notice.setCallId(response.getCallId());
|
notice.setCallId(response.getCallId());
|
||||||
notice.setOutId(outId);
|
notice.setOutId(outId);
|
||||||
|
notice.setCallTime(LocalDateTime.now()); // 记录实际发出时间,用于超时判断
|
||||||
warnCallNoticeMapper.updateById(notice);
|
warnCallNoticeMapper.updateById(notice);
|
||||||
sentCount++;
|
sentCount++;
|
||||||
log.info("[待发通知] 呼叫成功: {} - 记录ID: {}", notice.getMobilePhone(), notice.getId());
|
log.info("[待发通知] 呼叫成功: {} - 记录ID: {}", notice.getMobilePhone(), notice.getId());
|
||||||
|
|||||||
Reference in New Issue
Block a user