feat(mqtt): 添加报警检查服务实现多阶段报警处理

- 实现 AlarmCheckService 提供延迟队列消费者功能
- 添加 AlarmDelayProvider 接口定义延迟检查任务
- 集成 AlarmStageConfig 支持租户配置报警阶段延迟时间
- 重构 AliyunVoiceUtil 返回完整响应对象而非字符串
- 在 AppDeviceController 中新增 AlarmList 接口查询设备告警列表
- 扩展设备相关控制器支持数据来源枚举参数传递
- 新增 Xinghan 指令控制器提供 HBY018A 设备专用接口
- 定义 DataSourceEnum 枚举区分 APP 和 Web 数据来源
- 扩展 Device 实体类增加紧急联系人和通知配置字段
- 添加 DeviceAlarm 实体类告警状态和等级属性
- 新增 DeviceContactPhoneBo 处理设备联系人信息
- 优化设备操作记录日志支持数据来源标识
- 实现设备自定义语音短信消息编辑功能
- 添加设备通知开关和紧急联系人设置接口
This commit is contained in:
2026-05-26 15:38:18 +08:00
parent 7fcbb81317
commit c291e47ae8
33 changed files with 2116 additions and 72 deletions

View File

@ -0,0 +1,145 @@
package com.fuyuanshen.job.integration;
import com.aizuda.snailjob.client.job.core.openapi.SnailJobOpenApi;
import com.aizuda.snailjob.client.job.core.enums.AllocationAlgorithmEnum;
import com.aizuda.snailjob.common.core.enums.JobBlockStrategyEnum;
import com.aizuda.snailjob.client.job.core.enums.TriggerTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* SnailJob 1.8.1 业务集成客户端
* 优化点:增强健壮性、参数校验、日志标准化
*/
@Slf4j
@Component
public class SnailJobClient {
//执行超时时间
private static final int DEFAULT_TIMEOUT = 60;
//如果重试开启,两次重试之间间隔几秒
private static final int RETRY_INTERVAL = 30;
/**
* 创建离线自动关单任务
* 优化:引入 Assert 校验,防止非法参数进入 OpenAPI 导致报错
*/
public void addRetryTask(Long alarmId, String businessNo, String executorName, int delayMinutes) {
Assert.notNull(alarmId, "alarmId cannot be null");
Assert.hasText(businessNo, "businessNo cannot be empty");
try {
// 计算新的延迟秒数
String triggerInterval = String.valueOf(delayMinutes * 60L);
// 1. 计算 5 分钟后时间戳
LocalDateTime execTime = LocalDateTime.now().plusMinutes(delayMinutes);
long triggerTime = execTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
SnailJobOpenApi.addClusterJob()
// 设置任务路由策略:随机选择执行器节点
.setRouteKey(AllocationAlgorithmEnum.RANDOM)
// 设置任务名称(使用业务编号唯一标识)
.setJobName(businessNo)
// 设置执行器名称(指定任务由哪个执行器执行)
.setExecutorInfo(executorName)
// 设置执行器超时时间(使用系统默认超时配置)
.setExecutorTimeout(DEFAULT_TIMEOUT)
// 设置任务描述:设备报警离线自动关单任务
.setDescription("设备报警离线自动关单:" + businessNo)
// 设置任务阻塞策略:丢弃后续并发请求
.setBlockStrategy(JobBlockStrategyEnum.OVERLAY)
// 设置最大重试次数0 代表不重试
.setMaxRetryTimes(0)
// 设置任务触发类型:定时触发
// 改为 CRON 触发
.setTriggerType(TriggerTypeEnum.SCHEDULED_TIME)
// 毫秒级时间戳
.setTriggerInterval(triggerInterval)
// 添加任务入参传入报警ID字符串
.addArgsStr("alarmId", alarmId.toString())
// 设置重试间隔时间(重试时的等待时间)
.setRetryInterval(RETRY_INTERVAL)
// 执行任务创建/提交操作
.execute();
log.info("[SnailJob] 创建关单任务成功 | businessNo: {} | delay: {}m", businessNo, delayMinutes);
} catch (Exception e) {
log.error("[SnailJob] 创建关单任务异常 | businessNo: {}", businessNo, e);
}
}
/**
* 更新任务触发时间(续期)
* 优化:增加原子性思考。虽然 1.8.1 必须删后再加,但通过 try-catch 确保删除失败不中断逻辑(可能任务本就不存在)
*/
public void updateRetryTaskNextTriggerTime(Long alarmId, String businessNo, String executorName, int delayMinutes) {
try {
// 计算新的延迟秒数
String triggerInterval = String.valueOf(delayMinutes * 60L);
// 只要 JobName (businessNo) 一致SnailJob 服务端会识别为同一个任务进行更新
SnailJobOpenApi.addClusterJob()
.setJobName(businessNo) // 关键:保持 JobName 不变
.setExecutorInfo(executorName)
.setTriggerType(TriggerTypeEnum.SCHEDULED_TIME)
.setTriggerInterval(triggerInterval)
.addArgsStr("alarmId", alarmId.toString())
.setRouteKey(AllocationAlgorithmEnum.RANDOM)
.setMaxRetryTimes(0)
// 强制开启覆盖更新(如果 SDK 支持部分版本需显式指定1.8.1 默认通常为 saveOrUpdate 逻辑)
.execute();
log.info("[SnailJob] 任务续期成功(覆盖方式) → businessNo:{}", businessNo);
} catch (Exception e) {
log.error("[SnailJob] 任务续期失败", e);
}
}
/**
* 删除任务
* 优化:针对 hashCode() 可能产生的负值进行处理,并使用更稳健的 Set 构造
*/
public void deleteRetryTask(String businessNo) {
if (businessNo == null) return;
try {
// 注意hashCode 存在冲突可能。在 SnailJob 中若需绝对精确删除,建议存储 addClusterJob 返回的 ID
// 这里保留你的逻辑,但对负数哈希取绝对值以符合一般 ID 预期(取决于服务端接收逻辑)
long jobId = Math.abs((long) businessNo.hashCode());
SnailJobOpenApi.deleteJob(Set.of(jobId)).execute();
log.info("[SnailJob] 删除任务成功 | businessNo: {} | jobId: {}", businessNo, jobId);
} catch (Exception e) {
// 删除通常作为补偿操作,记录 warn 即可,无需抛出异常中断业务
log.warn("[SnailJob] 删除任务异常 | businessNo: {} | msg: {}", businessNo, e.getMessage());
}
}
/**
* 触发工作流
* 优化:增加对 args 内容的空值保护
*/
public void startWorkflow(String workflowId, Map<String, Object> args) {
Assert.hasText(workflowId, "workflowId cannot be empty");
try {
var request = SnailJobOpenApi.triggerWorkFlow(Long.parseLong(workflowId));
if (args != null) {
args.forEach((k, v) -> request.addArgsStr(k, Objects.toString(v, "")));
}
request.execute();
log.info("[SnailJob] 触发工作流成功 | workflowId: {}", workflowId);
} catch (Exception e) {
log.error("[SnailJob] 触发工作流失败 | workflowId: {}", workflowId, e);
}
}
}