|
|
|
|
@ -2,12 +2,27 @@ package org.dromara.ems.record.service.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
|
|
import org.dromara.common.core.utils.DateUtils;
|
|
|
|
|
import org.dromara.common.json.utils.JsonUtils;
|
|
|
|
|
import org.dromara.common.sse.utils.SseMessageUtils;
|
|
|
|
|
import org.dromara.ems.base.domain.EmsAlarmPushLog;
|
|
|
|
|
import org.dromara.ems.base.domain.EmsBaseMonitorInfo;
|
|
|
|
|
import org.dromara.ems.base.domain.bo.EmsAlarmNotifyGroupUserBo;
|
|
|
|
|
import org.dromara.ems.base.domain.vo.EmsAlarmNotifyGroupUserVo;
|
|
|
|
|
import org.dromara.ems.base.mapper.EmsAlarmPushLogMapper;
|
|
|
|
|
import org.dromara.ems.base.mapper.EmsBaseEnergyTypeMapper;
|
|
|
|
|
import org.dromara.ems.base.mapper.EmsBaseMonitorInfoMapper;
|
|
|
|
|
import org.dromara.ems.base.service.IEmsAlarmNotifyGroupUserService;
|
|
|
|
|
import org.dromara.ems.record.constant.EmsAlarmPushStatusConstants;
|
|
|
|
|
import org.dromara.ems.record.constant.EmsAlarmStatusConstants;
|
|
|
|
|
import org.dromara.ems.record.domain.EmsRecordAlarmData;
|
|
|
|
|
import org.dromara.ems.record.domain.EmsRecordAlarmRule;
|
|
|
|
|
import org.dromara.ems.record.domain.RecordIotenvInstant;
|
|
|
|
|
import org.dromara.ems.record.domain.vo.AlarmHandleResultVo;
|
|
|
|
|
import org.dromara.ems.record.domain.vo.EmsRealtimeAlarmBatchResultVo;
|
|
|
|
|
import org.dromara.ems.record.domain.vo.EmsRealtimeAlarmEventVo;
|
|
|
|
|
import org.dromara.ems.record.domain.vo.EmsRecordAlarmDataSummaryVo;
|
|
|
|
|
import org.dromara.ems.record.mapper.EmsRecordAlarmDataMapper;
|
|
|
|
|
import org.dromara.ems.record.mapper.EmsRecordAlarmRuleMapper;
|
|
|
|
|
@ -15,10 +30,13 @@ import org.dromara.ems.record.mapper.RecordIotenvInstantMapper;
|
|
|
|
|
import org.dromara.ems.record.service.IEmsRecordAlarmDataService;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
import org.springframework.transaction.support.TransactionSynchronization;
|
|
|
|
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
import static org.dromara.common.satoken.utils.LoginHelper.getUserId;
|
|
|
|
|
import static org.dromara.common.satoken.utils.LoginHelper.getUsername;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -28,6 +46,7 @@ import static org.dromara.common.satoken.utils.LoginHelper.getUsername;
|
|
|
|
|
* @date 2024-05-15
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
@Slf4j
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService {
|
|
|
|
|
|
|
|
|
|
@ -45,6 +64,12 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
|
|
|
|
|
private final EmsBaseEnergyTypeMapper emsBaseEnergyTypeMapper;
|
|
|
|
|
|
|
|
|
|
private final EmsAlarmPushLogMapper emsAlarmPushLogMapper;
|
|
|
|
|
|
|
|
|
|
private final IEmsAlarmNotifyGroupUserService emsAlarmNotifyGroupUserService;
|
|
|
|
|
|
|
|
|
|
private static final long REALTIME_DUPLICATE_WINDOW_MS = 5 * 60 * 1000L;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 监测字段字典映射
|
|
|
|
|
* 将前端字典值映射到数据库字段名和中文描述
|
|
|
|
|
@ -171,16 +196,40 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public int handleExceptionsAlarmData(Long[] objIds) {
|
|
|
|
|
int result = 0;
|
|
|
|
|
public AlarmHandleResultVo handleExceptionsAlarmData(Long[] objIds) {
|
|
|
|
|
AlarmHandleResultVo result = new AlarmHandleResultVo();
|
|
|
|
|
if (objIds == null || objIds.length == 0) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
result.setRequestCount(objIds.length);
|
|
|
|
|
for (Long objId : objIds) {
|
|
|
|
|
EmsRecordAlarmData existing = emsRecordAlarmDataMapper.selectEmsRecordAlarmDataByObjId(objId);
|
|
|
|
|
if (existing == null) {
|
|
|
|
|
result.setMissingCount(result.getMissingCount() + 1);
|
|
|
|
|
result.getMissingIds().add(objId);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (Objects.equals(existing.getAlarmStatus(), EmsAlarmStatusConstants.HANDLED)) {
|
|
|
|
|
result.setAlreadyHandledCount(result.getAlreadyHandledCount() + 1);
|
|
|
|
|
result.getAlreadyHandledIds().add(objId);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
EmsRecordAlarmData alarmData = new EmsRecordAlarmData();
|
|
|
|
|
alarmData.setObjId(objId);
|
|
|
|
|
alarmData.setAlarmStatus(0L);
|
|
|
|
|
alarmData.setAlarmStatus(EmsAlarmStatusConstants.HANDLED);
|
|
|
|
|
alarmData.setOperationName(getUsername());
|
|
|
|
|
alarmData.setOperationTime(DateUtils.getNowDate());
|
|
|
|
|
int i = emsRecordAlarmDataMapper.updateEmsRecordAlarmData(alarmData);
|
|
|
|
|
result += 1;
|
|
|
|
|
alarmData.setConfirmUserId(getUserId());
|
|
|
|
|
alarmData.setUpdateTime(DateUtils.getNowDate());
|
|
|
|
|
// 只允许从“未处理”切到“已处理”,这样并发确认时第二次提交会拿到 0 行更新。
|
|
|
|
|
int updated = emsRecordAlarmDataMapper.markAlarmHandled(alarmData);
|
|
|
|
|
if (updated > 0) {
|
|
|
|
|
result.setUpdatedCount(result.getUpdatedCount() + updated);
|
|
|
|
|
result.getSuccessIds().add(objId);
|
|
|
|
|
} else {
|
|
|
|
|
result.setAlreadyHandledCount(result.getAlreadyHandledCount() + 1);
|
|
|
|
|
result.getAlreadyHandledIds().add(objId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
@ -792,73 +841,71 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量保存WebSocket告警数据
|
|
|
|
|
* 处理前端传来的告警数据列表,直接插入数据库
|
|
|
|
|
* 兼容旧入口,但补回结构化结果,便于前端实时链路和联调页直接复用。
|
|
|
|
|
*
|
|
|
|
|
* @param alarmDataList 告警数据列表
|
|
|
|
|
* @return 实际插入的记录数量
|
|
|
|
|
* @return 实时告警处理结果
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public int saveWebSocketAlarmDataBatch(List<EmsRecordAlarmData> alarmDataList) {
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public EmsRealtimeAlarmBatchResultVo saveWebSocketAlarmDataBatch(List<EmsRecordAlarmData> alarmDataList) {
|
|
|
|
|
EmsRealtimeAlarmBatchResultVo result = new EmsRealtimeAlarmBatchResultVo();
|
|
|
|
|
if (alarmDataList == null || alarmDataList.isEmpty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("==== 开始保存WebSocket告警数据 ====");
|
|
|
|
|
System.out.println("接收到告警数据数量: " + alarmDataList.size());
|
|
|
|
|
|
|
|
|
|
// 设置默认值和创建时间
|
|
|
|
|
result.setTotalCount(alarmDataList.size());
|
|
|
|
|
Date currentTime = DateUtils.getNowDate();
|
|
|
|
|
int insertedCount = 0;
|
|
|
|
|
|
|
|
|
|
for (EmsRecordAlarmData alarmData : alarmDataList) {
|
|
|
|
|
try {
|
|
|
|
|
// 设置创建时间
|
|
|
|
|
if (alarmData.getCreateTime() == null) {
|
|
|
|
|
alarmData.setCreateTime(currentTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置默认告警状态为未处理
|
|
|
|
|
if (alarmData.getAlarmStatus() == null) {
|
|
|
|
|
alarmData.setAlarmStatus(1L); // 1表示未处理
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 数据完整性检查
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMonitorId())) {
|
|
|
|
|
System.out.println("警告:跳过monitorId为空的告警数据");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alarmData.getCollectTime() == null) {
|
|
|
|
|
System.out.println("警告:collectTime为空,使用当前时间");
|
|
|
|
|
alarmData.setCollectTime(currentTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 推送状态、标题和内容属于新增字段,入库前补齐默认值,避免后续列表/详情出现空链路。
|
|
|
|
|
fillAlarmEnhanceFields(alarmData);
|
|
|
|
|
|
|
|
|
|
System.out.println("保存告警数据 - 设备:" + alarmData.getMonitorId() +
|
|
|
|
|
", 原因:" + alarmData.getCause() +
|
|
|
|
|
", 数值:" + alarmData.getAlarmData() +
|
|
|
|
|
", 告警类型:" + alarmData.getAlarmType());
|
|
|
|
|
|
|
|
|
|
// 直接插入数据库,不进行去重检查
|
|
|
|
|
int result = insertEmsRecordAlarmData(alarmData);
|
|
|
|
|
if (result > 0) {
|
|
|
|
|
insertedCount++;
|
|
|
|
|
System.out.println("成功保存告警数据 - 设备:" + alarmData.getMonitorId() +
|
|
|
|
|
", 原因:" + alarmData.getCause());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
System.err.println("保存告警数据时发生错误: " + e.getMessage());
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
Set<String> requestDeduplicationKeys = new HashSet<>();
|
|
|
|
|
for (int index = 0; index < alarmDataList.size(); index++) {
|
|
|
|
|
EmsRecordAlarmData alarmData = alarmDataList.get(index);
|
|
|
|
|
Optional<String> invalidReason = validateRealtimeAlarm(alarmData, currentTime);
|
|
|
|
|
if (invalidReason.isPresent()) {
|
|
|
|
|
// 这里允许坏消息单条失败,但数据库写入错误仍走事务整体回滚,避免产生半闭环脏数据。
|
|
|
|
|
result.setFailedCount(result.getFailedCount() + 1);
|
|
|
|
|
log.warn("实时告警第{}条校验失败,原因={}", index + 1, invalidReason.get());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String requestKey = buildUniqueKey(alarmData);
|
|
|
|
|
if (requestKey != null && !requestDeduplicationKeys.add(requestKey)) {
|
|
|
|
|
result.setDuplicateCount(result.getDuplicateCount() + 1);
|
|
|
|
|
log.info("实时告警命中批次内去重,businessKey={}", requestKey);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<EmsRecordAlarmRule> matchedRules = emsRecordAlarmRuleMapper.selectMatchedAlarmRulesByAlarmInfo(alarmData.getMonitorId(), alarmData.getCause());
|
|
|
|
|
enrichAlarmByMonitor(alarmData);
|
|
|
|
|
enrichAlarmByRule(alarmData, matchedRules);
|
|
|
|
|
fillAlarmEnhanceFields(alarmData);
|
|
|
|
|
|
|
|
|
|
if (isDuplicateRealtimeAlarm(alarmData)) {
|
|
|
|
|
result.setDuplicateCount(result.getDuplicateCount() + 1);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int inserted = insertEmsRecordAlarmData(alarmData);
|
|
|
|
|
if (inserted != 1 || alarmData.getObjId() == null) {
|
|
|
|
|
throw new ServiceException("实时告警落库失败,monitorId=" + alarmData.getMonitorId() + ", cause=" + alarmData.getCause());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int pushCount = createPendingPushLogs(alarmData, matchedRules);
|
|
|
|
|
if (pushCount > 0 || !EmsAlarmPushStatusConstants.NONE.equals(alarmData.getPushStatus())) {
|
|
|
|
|
EmsRecordAlarmData patch = new EmsRecordAlarmData();
|
|
|
|
|
patch.setObjId(alarmData.getObjId());
|
|
|
|
|
patch.setPushCount(pushCount);
|
|
|
|
|
patch.setPushStatus(alarmData.getPushStatus());
|
|
|
|
|
patch.setUpdateTime(DateUtils.getNowDate());
|
|
|
|
|
emsRecordAlarmDataMapper.updateEmsRecordAlarmData(patch);
|
|
|
|
|
alarmData.setPushCount(pushCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.getRecords().add(alarmData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("WebSocket告警数据保存完成,实际插入: " + insertedCount + " 条记录");
|
|
|
|
|
System.out.println("==== WebSocket告警数据保存结束 ====");
|
|
|
|
|
|
|
|
|
|
return insertedCount;
|
|
|
|
|
result.setInsertedCount(result.getRecords().size());
|
|
|
|
|
publishRealtimeAlarmEventAfterCommit(result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void fillAlarmEnhanceFields(EmsRecordAlarmData alarmData) {
|
|
|
|
|
@ -868,10 +915,7 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
if (alarmData.getPushCount() == null) {
|
|
|
|
|
alarmData.setPushCount(0);
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getPushStatus())) {
|
|
|
|
|
// 约定 0 为未推送,先补默认值,避免新增字段因为空值影响筛选和详情展示。
|
|
|
|
|
alarmData.setPushStatus("0");
|
|
|
|
|
}
|
|
|
|
|
alarmData.setPushStatus(normalizePushStatus(alarmData.getPushStatus()));
|
|
|
|
|
if (alarmData.getActualValue() == null && ObjectUtil.isNotEmpty(alarmData.getAlarmData())) {
|
|
|
|
|
try {
|
|
|
|
|
alarmData.setActualValue(new BigDecimal(alarmData.getAlarmData()));
|
|
|
|
|
@ -906,4 +950,214 @@ public class EmsRecordAlarmDataServiceImpl implements IEmsRecordAlarmDataService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Optional<String> validateRealtimeAlarm(EmsRecordAlarmData alarmData, Date fallbackTime) {
|
|
|
|
|
if (alarmData == null) {
|
|
|
|
|
return Optional.of("告警对象为空");
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMonitorId())) {
|
|
|
|
|
return Optional.of("monitorId不能为空");
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getCause())) {
|
|
|
|
|
return Optional.of("cause不能为空,SOP 命中链无法建立");
|
|
|
|
|
}
|
|
|
|
|
if (alarmData.getCollectTime() == null) {
|
|
|
|
|
alarmData.setCollectTime(fallbackTime);
|
|
|
|
|
}
|
|
|
|
|
if (alarmData.getCreateTime() == null) {
|
|
|
|
|
alarmData.setCreateTime(fallbackTime);
|
|
|
|
|
}
|
|
|
|
|
if (alarmData.getAlarmStatus() == null) {
|
|
|
|
|
alarmData.setAlarmStatus(EmsAlarmStatusConstants.UNHANDLED);
|
|
|
|
|
}
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isDuplicateRealtimeAlarm(EmsRecordAlarmData alarmData) {
|
|
|
|
|
Date collectTime = alarmData.getCollectTime() != null ? alarmData.getCollectTime() : DateUtils.getNowDate();
|
|
|
|
|
Date startTime = new Date(collectTime.getTime() - REALTIME_DUPLICATE_WINDOW_MS);
|
|
|
|
|
Date endTime = new Date(collectTime.getTime() + REALTIME_DUPLICATE_WINDOW_MS);
|
|
|
|
|
Integer duplicateCount = emsRecordAlarmDataMapper.checkDuplicateAlarmData(alarmData.getMonitorId(), alarmData.getCause(), startTime, endTime);
|
|
|
|
|
return duplicateCount != null && duplicateCount > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void enrichAlarmByMonitor(EmsRecordAlarmData alarmData) {
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMonitorId())) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
EmsBaseMonitorInfo query = new EmsBaseMonitorInfo();
|
|
|
|
|
query.setMonitorCode(alarmData.getMonitorId());
|
|
|
|
|
List<EmsBaseMonitorInfo> monitorInfos = emsBaseMonitorInfoMapper.selectEmsBaseMonitorInfoList(query);
|
|
|
|
|
if (monitorInfos == null || monitorInfos.isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
EmsBaseMonitorInfo monitorInfo = monitorInfos.get(0);
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMonitorName())) {
|
|
|
|
|
alarmData.setMonitorName(monitorInfo.getMonitorName());
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMetricCode())) {
|
|
|
|
|
alarmData.setMetricCode(monitorInfo.getMetricCode());
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getTenantId())) {
|
|
|
|
|
alarmData.setTenantId(monitorInfo.getTenantId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void enrichAlarmByRule(EmsRecordAlarmData alarmData, List<EmsRecordAlarmRule> matchedRules) {
|
|
|
|
|
if (matchedRules == null || matchedRules.isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
EmsRecordAlarmRule matchedRule = matchedRules.get(0);
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getTenantId())) {
|
|
|
|
|
alarmData.setTenantId(matchedRule.getTenantId());
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getMetricCode())) {
|
|
|
|
|
alarmData.setMetricCode(matchedRule.getMetricCode());
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getAlarmLevel())) {
|
|
|
|
|
alarmData.setAlarmLevel(matchedRule.getAlarmLevel());
|
|
|
|
|
}
|
|
|
|
|
if (ObjectUtil.isEmpty(alarmData.getNotifyUser())) {
|
|
|
|
|
alarmData.setNotifyUser(matchedRule.getNotifyUser());
|
|
|
|
|
}
|
|
|
|
|
if (alarmData.getThresholdValue() == null) {
|
|
|
|
|
if (matchedRule.getTriggerValue() != null) {
|
|
|
|
|
alarmData.setThresholdValue(matchedRule.getTriggerValue());
|
|
|
|
|
} else if (Objects.equals(1L, matchedRule.getTriggerRule())) {
|
|
|
|
|
alarmData.setThresholdValue(matchedRule.getAlarmLower());
|
|
|
|
|
} else {
|
|
|
|
|
alarmData.setThresholdValue(matchedRule.getAlarmUpper());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int createPendingPushLogs(EmsRecordAlarmData alarmData, List<EmsRecordAlarmRule> matchedRules) {
|
|
|
|
|
Set<String> pushTargets = new LinkedHashSet<>();
|
|
|
|
|
collectExplicitNotifyUsers(pushTargets, alarmData.getNotifyUser());
|
|
|
|
|
if (matchedRules != null) {
|
|
|
|
|
for (EmsRecordAlarmRule matchedRule : matchedRules) {
|
|
|
|
|
collectExplicitNotifyUsers(pushTargets, matchedRule.getNotifyUser());
|
|
|
|
|
if (matchedRule.getNotifyGroupId() != null) {
|
|
|
|
|
EmsAlarmNotifyGroupUserBo query = new EmsAlarmNotifyGroupUserBo();
|
|
|
|
|
query.setGroupId(matchedRule.getNotifyGroupId());
|
|
|
|
|
query.setIsEnable("0");
|
|
|
|
|
List<EmsAlarmNotifyGroupUserVo> groupUsers = emsAlarmNotifyGroupUserService.queryList(query);
|
|
|
|
|
if (groupUsers == null || groupUsers.isEmpty()) {
|
|
|
|
|
pushTargets.add("GROUP:" + matchedRule.getNotifyGroupId());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (EmsAlarmNotifyGroupUserVo groupUser : groupUsers) {
|
|
|
|
|
if (ObjectUtil.isNotEmpty(groupUser.getPhone())) {
|
|
|
|
|
pushTargets.add(groupUser.getPhone());
|
|
|
|
|
} else if (ObjectUtil.isNotEmpty(groupUser.getEmail())) {
|
|
|
|
|
pushTargets.add(groupUser.getEmail());
|
|
|
|
|
} else if (ObjectUtil.isNotEmpty(groupUser.getNickName())) {
|
|
|
|
|
pushTargets.add(groupUser.getNickName());
|
|
|
|
|
} else if (ObjectUtil.isNotEmpty(groupUser.getUserName())) {
|
|
|
|
|
pushTargets.add(groupUser.getUserName());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (String target : pushTargets) {
|
|
|
|
|
EmsAlarmPushLog pushLog = new EmsAlarmPushLog();
|
|
|
|
|
pushLog.setAlarmObjId(alarmData.getObjId());
|
|
|
|
|
pushLog.setChannelType(resolveChannelType(target));
|
|
|
|
|
pushLog.setTargetValue(target);
|
|
|
|
|
pushLog.setAlarmLevel(alarmData.getAlarmLevel());
|
|
|
|
|
pushLog.setPushContent(alarmData.getAlarmContent());
|
|
|
|
|
pushLog.setPushStatus(EmsAlarmPushStatusConstants.PENDING);
|
|
|
|
|
pushLog.setResponseMsg("已生成待推送日志,等待真实推送执行链接管");
|
|
|
|
|
pushLog.setPushTime(DateUtils.getNowDate());
|
|
|
|
|
emsAlarmPushLogMapper.insert(pushLog);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这里明确区分“已生成推送任务”和“本条告警没有通知配置”,避免统计页把未配置也算成待推送。
|
|
|
|
|
alarmData.setPushStatus(pushTargets.isEmpty() ? EmsAlarmPushStatusConstants.NONE : EmsAlarmPushStatusConstants.PENDING);
|
|
|
|
|
return pushTargets.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void collectExplicitNotifyUsers(Set<String> pushTargets, String notifyUser) {
|
|
|
|
|
if (ObjectUtil.isEmpty(notifyUser)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
String[] parts = notifyUser.split("[,;,;\\s]+");
|
|
|
|
|
for (String part : parts) {
|
|
|
|
|
if (ObjectUtil.isNotEmpty(part)) {
|
|
|
|
|
pushTargets.add(part.trim());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String resolveChannelType(String targetValue) {
|
|
|
|
|
if (ObjectUtil.isEmpty(targetValue)) {
|
|
|
|
|
return "SITE";
|
|
|
|
|
}
|
|
|
|
|
if (targetValue.contains("@")) {
|
|
|
|
|
return "EMAIL";
|
|
|
|
|
}
|
|
|
|
|
if (targetValue.matches("^1\\d{10}$")) {
|
|
|
|
|
return "SMS";
|
|
|
|
|
}
|
|
|
|
|
if (targetValue.startsWith("http://") || targetValue.startsWith("https://")) {
|
|
|
|
|
return "WEBHOOK";
|
|
|
|
|
}
|
|
|
|
|
return "SITE";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String normalizePushStatus(String pushStatus) {
|
|
|
|
|
if (ObjectUtil.isEmpty(pushStatus)) {
|
|
|
|
|
return EmsAlarmPushStatusConstants.NONE;
|
|
|
|
|
}
|
|
|
|
|
String normalized = pushStatus.trim().toUpperCase(Locale.ROOT);
|
|
|
|
|
if ("0".equals(normalized) || "WAIT".equals(normalized)) {
|
|
|
|
|
return EmsAlarmPushStatusConstants.PENDING;
|
|
|
|
|
}
|
|
|
|
|
if ("1".equals(normalized)) {
|
|
|
|
|
return EmsAlarmPushStatusConstants.PROCESSING;
|
|
|
|
|
}
|
|
|
|
|
if ("2".equals(normalized) || "FAILED".equals(normalized)) {
|
|
|
|
|
return EmsAlarmPushStatusConstants.FAIL;
|
|
|
|
|
}
|
|
|
|
|
if (EmsAlarmPushStatusConstants.NONE.equals(normalized)
|
|
|
|
|
|| EmsAlarmPushStatusConstants.PENDING.equals(normalized)
|
|
|
|
|
|| EmsAlarmPushStatusConstants.PROCESSING.equals(normalized)
|
|
|
|
|
|| EmsAlarmPushStatusConstants.SUCCESS.equals(normalized)
|
|
|
|
|
|| EmsAlarmPushStatusConstants.FAIL.equals(normalized)) {
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
return EmsAlarmPushStatusConstants.NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void publishRealtimeAlarmEventAfterCommit(EmsRealtimeAlarmBatchResultVo result) {
|
|
|
|
|
if (result == null || result.getInsertedCount() <= 0 || result.getRecords().isEmpty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Runnable publishTask = () -> {
|
|
|
|
|
try {
|
|
|
|
|
EmsRealtimeAlarmEventVo event = new EmsRealtimeAlarmEventVo();
|
|
|
|
|
// 这里使用前端约定的 eventType,避免迁移阶段再引入额外的协议转换层。
|
|
|
|
|
event.setEventType("ems_alarm_realtime");
|
|
|
|
|
event.setGeneratedAt(DateUtils.getNowDate());
|
|
|
|
|
event.setSource("saveWebSocketAlarmData");
|
|
|
|
|
event.setAlarms(result.getRecords());
|
|
|
|
|
SseMessageUtils.publishAll(JsonUtils.toJsonString(event));
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
// 这里不能反向影响主事务,避免“落库成功但广播失败”把记录整体回滚。
|
|
|
|
|
log.warn("实时告警 SSE 广播失败,insertedCount={}", result.getInsertedCount(), ex);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
|
|
|
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
|
|
@Override
|
|
|
|
|
public void afterCommit() {
|
|
|
|
|
publishTask.run();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
publishTask.run();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|