diff --git a/ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/equManagement/EquBaseController.java b/ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/equManagement/EquBaseController.java index 5184b890..9cc60e67 100644 --- a/ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/equManagement/EquBaseController.java +++ b/ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/equManagement/EquBaseController.java @@ -61,7 +61,7 @@ public class EquBaseController extends BaseController /** * 查询多台设备的最新监测信息 **/ - @PreAuthorize("@ss.hasPermi('device:files:query')") + @PreAuthorize("@ss.hasPermi('device:files:querymonitorpage')") @GetMapping("/monitorpage") @ApiOperation("分页查询多台设备的最新监测信息") public TableDataInfo getMonitorList() { @@ -88,7 +88,7 @@ public class EquBaseController extends BaseController * 获取设备最新时间的历史监控信息 * @param id 查询对象,包含设备ID、开始时间和结束时间 */ - @PreAuthorize("@ss.hasPermi('device:files:query')") + @PreAuthorize("@ss.hasPermi('device:files:querylatest')") @GetMapping(value = "/latest/{id}") // URL清晰地表达了意图:获取历史监控信息 @ApiOperation("获取设备最新时间的历史监控信息") @Anonymous @@ -102,7 +102,7 @@ public class EquBaseController extends BaseController * 获取设备指定时间段内的历史监控信息 * @param equBaseQueryDto 查询对象,包含设备ID、开始时间和结束时间 */ - @PreAuthorize("@ss.hasPermi('device:files:query')") + @PreAuthorize("@ss.hasPermi('device:files:queryhistory')") @GetMapping(value = "/history") // URL清晰地表达了意图:获取历史监控信息 @ApiOperation("获取设备指定时间段内的历史监控信息") @Anonymous @@ -131,7 +131,7 @@ public class EquBaseController extends BaseController /** * 统计楼层设备分布 */ - @PreAuthorize("@ss.hasPermi('device:files:statistic')") + @PreAuthorize("@ss.hasPermi('device:files:statisticfloor')") @GetMapping("/floorStatistic") @ApiOperation("统计楼层设备分布信息") public AjaxResult getFloorDeviceStatistic() { diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/dto/EquBaseQueryDto.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/dto/EquBaseQueryDto.java index 76c7ce07..e670868e 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/dto/EquBaseQueryDto.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/dto/EquBaseQueryDto.java @@ -26,6 +26,7 @@ public class EquBaseQueryDto implements Serializable { /** 安装位置 ID (location, varchar) */ private String location; + private String floor; /** 起始时间 (min_time) */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date minTime; diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/GatherEntities.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/GatherEntities.java index 32f4928e..43e615fc 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/GatherEntities.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/GatherEntities.java @@ -15,6 +15,7 @@ import java.util.Map; public class GatherEntities { private EquGather equGather; // 主表 - private Map> subEntities = new HashMap<>(); + private Map> subEntities = new HashMap<>();//插入表 + private Map updateEntities = new HashMap<>();//更新表 } diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/job/GatherJob.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/job/GatherJob.java index dc3dffa5..19a9de66 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/job/GatherJob.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/job/GatherJob.java @@ -46,11 +46,11 @@ public class GatherJob implements Job { // scheduler.deleteJob(jobKey); boolean deleted = scheduler.deleteJob(jobKey); if (deleted) { - iAlertSenderService.sendInternalMessage("采集任务取消成功","任务执行完此次,已取消后续任务","2"); + iAlertSenderService.sendInternalMessage("采集整体任务取消成功","任务执行完此次,已取消后续任务","2"); log.info("规则[{}] 已标记为取消状态(4),对应定时任务已删除: {}", rule.getId(), jobKey); } else { log.warn("规则[{}] 尝试取消定时任务失败: {}", rule.getId(), jobKey); - iAlertSenderService.sendInternalMessage("采集任务取消失败","尝试取消定时任务失败,请检查调度器状态","2"); + iAlertSenderService.sendInternalMessage("采集整体任务取消失败","尝试取消定时任务失败,请检查调度器状态","2"); } return; } @@ -67,7 +67,7 @@ public class GatherJob implements Job { if ( now.after(end)) { rule.setStatus(2); // 超出范围标记完成 scheduleRulesMapper.updateScheduleRules(rule); - iAlertSenderService.sendInternalMessage("采集任务成功","按照频率进行采集的任务执行完成","2"); + iAlertSenderService.sendInternalMessage("采集整体任务成功","按照频率进行采集的任务执行完成","2"); return; } @@ -81,10 +81,10 @@ public class GatherJob implements Job { if (rule.getRuleType() == 1 ) { rule.setStatus(2); - String Content=String.format("定时采集任务执行完成", + String Content=String.format("定时采集任务整体执行完成", rule.getId(), rule.getRuleName()); - iAlertSenderService.sendInternalMessage("采集任务成功" ,Content,"2" ); + iAlertSenderService.sendInternalMessage("采集任务整体成功" ,Content,"2" ); scheduleRulesMapper.updateScheduleRules(rule); } @@ -100,10 +100,10 @@ public class GatherJob implements Job { rule.setStatus(3); scheduleRulesMapper.updateScheduleRules(rule); try { - String Content=String.format("采集任务执行失败,规则ID=%s,规则名称=%s,原因:%s", + String Content=String.format("采集任务整体执行失败,规则ID=%s,规则名称=%s,原因:%s", rule.getId(), rule.getRuleName(), e.getMessage()); - iAlertSenderService.sendInternalMessage("采集任务失败" ,Content,"2" ); + iAlertSenderService.sendInternalMessage("采集任务整体执行失败" ,Content,"2" ); log.info("已发送失败通知给运维人员"); diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquBaseMapper.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquBaseMapper.java index 6ca9c2d3..535e466e 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquBaseMapper.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquBaseMapper.java @@ -22,6 +22,14 @@ import org.springframework.data.repository.query.Param; public interface EquBaseMapper { + String selectEquNameById(@Param("equId") String equId); + + /** + * 批量更新 equ_base 表的 version 字段 + * @param list 待更新的实体列表,每个实体必须包含 id 和 version + * @return 更新的行数 + */ + int updateEquBaseVersionBatch(@Param("list") List list); /** * 查询状态为1的设备及其最新采集信息和类型名称 diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquGatherMapper.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquGatherMapper.java index 461618b5..e5ef2407 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquGatherMapper.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquGatherMapper.java @@ -16,6 +16,11 @@ import java.util.List; public interface EquGatherMapper { + + /** + * 根据设备ID列表查询每台设备的最新采集ID + */ + List selectLatestGatherIdsByEquIds(@Param("equIds") List equIds); // 更新 health 字段 int updateHealth(String collectId, Double totalHealth); diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquOidMapper.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquOidMapper.java index 3f7c62fe..065bb2ca 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquOidMapper.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/mapper/EquOidMapper.java @@ -22,6 +22,7 @@ public interface EquOidMapper * @return OID列表 */ List findByManuIdAndStatus(@Param("manufacturerId") String manufacturerId); + List findByManuIdAndStatus2(@Param("manufacturerId") String manufacturerId); /** * 查询OID配置信息 * diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/IScheduleRulesService.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/IScheduleRulesService.java index 8843a655..ca199021 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/IScheduleRulesService.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/IScheduleRulesService.java @@ -19,8 +19,6 @@ import java.util.concurrent.ExecutionException; public interface IScheduleRulesService { - - public Boolean checkRunningOrWaitingRules() throws SchedulerException; public Boolean executeRule(ScheduleRules newRule) throws SchedulerException, ExecutionException, InterruptedException; diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/MemoryToolService.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/MemoryToolService.java index 4b0e34d6..71ab58e3 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/MemoryToolService.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/MemoryToolService.java @@ -16,4 +16,8 @@ public interface MemoryToolService { EquBase device, Map snmpResults); public int persistAll(List allEntities); + + + + } diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertLaunchServiceImpl.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertLaunchServiceImpl.java index 5fbc1b34..ade8c281 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertLaunchServiceImpl.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertLaunchServiceImpl.java @@ -9,9 +9,12 @@ import com.alops.system.mapper.AlertRuleMapper; import com.alops.system.mapper.EquBaseMapper; import com.alops.system.service.IAlertLaunchService; import com.alops.system.service.IAlertSenderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.*; @@ -38,42 +41,76 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { private EquBaseMapper equBaseMapper; - + private static final Logger log = LoggerFactory.getLogger(AlertLaunchServiceImpl.class); private final ExecutorService executor = Executors.newFixedThreadPool(5); /** - * 执行立即预警 + * 执行立即预警,这里捕捉异常不影响采集操作 */ - @Transactional(rollbackFor = Exception.class) +// @Override +// public void executeImmediateAlerts() { +// Integer maxBatch = alertMessageMapper.selectMaxBatch(); +// Integer currentBatch = (maxBatch == null ? 0 : maxBatch) + 1; +// +// List rules = alertRuleMapper.selectAlertRule(); +// +// Map totalFailureCount = new HashMap<>(); +// for (AlertRule rule : rules) { +// Map map = processRule(rule, currentBatch); // 直接同步调用 +// map.forEach((equId, count) -> totalFailureCount.merge(equId, count, Integer::sum)); +// } +// +// List> updateList = totalFailureCount.entrySet().stream() +// .map(entry -> { +// Map map = new HashMap<>(); +// map.put("equId", entry.getKey()); +// map.put("count", entry.getValue()); +// return map; +// }) +// .collect(Collectors.toList()); +// if (!updateList.isEmpty()) { +// equBaseMapper.batchUpdateFailureFrequency(updateList); // 出异常会触发回滚 +// } +// } @Override - - public void executeImmediateAlerts() { + public void executeImmediateAlerts() { Integer maxBatch = alertMessageMapper.selectMaxBatch(); Integer currentBatch = (maxBatch == null ? 0 : maxBatch) + 1; List rules = alertRuleMapper.selectAlertRule(); Map totalFailureCount = new HashMap<>(); + for (AlertRule rule : rules) { - Map map = processRule(rule, currentBatch); // 直接同步调用 - map.forEach((equId, count) -> totalFailureCount.merge(equId, count, Integer::sum)); + try { + Map map = processRule(rule, currentBatch); // 规则级容错 + map.forEach((equId, count) -> totalFailureCount.merge(equId, count, Integer::sum)); + } catch (Exception e) { + // 防御性捕获,保证循环继续 + log.error("【规则执行异常】规则ID={} 名称='{}'", rule.getId(), rule.getRuleName(), e); + } } - List> updateList = totalFailureCount.entrySet().stream() - .map(entry -> { - Map map = new HashMap<>(); - map.put("equId", entry.getKey()); - map.put("count", entry.getValue()); - return map; - }) - .collect(Collectors.toList()); - if (!updateList.isEmpty()) { - equBaseMapper.batchUpdateFailureFrequency(updateList); // 出异常会触发回滚 + // 更新设备故障频率(核心统计数据异常才回滚) + if (!totalFailureCount.isEmpty()) { + try { + List> updateList = totalFailureCount.entrySet().stream() + .map(entry -> { + Map map = new HashMap<>(); + map.put("equId", entry.getKey()); + map.put("count", entry.getValue()); + return map; + }).collect(Collectors.toList()); + + equBaseMapper.batchUpdateFailureFrequency(updateList); + } catch (Exception e) { + log.error("更新设备故障频率异常", e); + throw new RuntimeException("更新设备故障频率失败", e); // 核心异常才抛 + } } } - // public void executeImmediateAlerts() throws ExecutionException, InterruptedException { // // 1. 查询当前最大批次,没有则返回 0 // Integer maxBatch = alertMessageMapper.selectMaxBatch(); @@ -112,14 +149,29 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { /** * 同步插入消息 */ - @Transactional +// @Transactional +// public void insertAlertMessages(List messages) { +// if (messages == null || messages.isEmpty()) { +// return; +// } +// int rows = alertMessageMapper.batchInsert(messages); +// if (rows != messages.size()) { +// throw new RuntimeException("部分消息未插入成功"); +// } +// } public void insertAlertMessages(List messages) { if (messages == null || messages.isEmpty()) { return; } - int rows = alertMessageMapper.batchInsert(messages); - if (rows != messages.size()) { - throw new RuntimeException("部分消息未插入成功"); + + try { + int rows = alertMessageMapper.batchInsert(messages); + if (rows != messages.size()) { + log.warn("部分预警消息未成功插入,预期={}条,实际={}条", messages.size(), rows); + } + } catch (Exception e) { + log.error("插入预警消息失败: {}", e.getMessage(), e); + // 不抛出异常,继续处理 } } @@ -128,12 +180,62 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { * 处理单条预警规则 */ - private Map processRule(AlertRule rule, Integer currentBatch) { +// private Map processRule(AlertRule rule, Integer currentBatch) { +// Map failureCountMap = new HashMap<>(); +// try { +// List messages = new ArrayList<>(); +// List msgs = new ArrayList<>(); +// +// boolean isChangedRule = "CHANGED".equalsIgnoreCase(rule.getAlertSql()); +// List> queryResults = isChangedRule +// ? handleChangedRule(rule) +// : jdbcTemplate.queryForList(rule.getAlertSql()); +// +// for (Map row : queryResults) { +// AlertMessage msg = new AlertMessage(); +// msg.setLevel(rule.getSeverity()); +// msg.setType(rule.getType()); +// msg.setOccurTime(new Date()); +// msg.setStatus("UNHANDLED"); +// +// String equId = row.get("equ_id") != null ? row.get("equ_id").toString() : null; +// msg.setEquId(equId); +// msg.setEquType(row.get("equ_type") != null ? row.get("equ_type").toString() : null); +//// msg.setEquType(row.get("equ_name") != null ? row.get("equ_name").toString() : null); +// String msgdes = fillTemplate(rule.getAlertTemplate(), row, rule); +// +// msg.setBatch(currentBatch); +// msg.setMessage(msgdes); +// msg.setAlertRuleId(rule.getId()); +// messages.add(msg); +// msgs.add(msgdes); +// +// // 统计每个设备出现的次数 +// if (equId != null) { +// failureCountMap.put(equId, failureCountMap.getOrDefault(equId, 0) + 1); +// } +// } +// +// if (!messages.isEmpty()) { +// alertSender.sendAlert(msgs, rule.getAlertWay(), rule.getAlertStation()); +// insertAlertMessages(messages); +// } +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return failureCountMap; +// } + /** + * 单条规则执行逻辑(独立事务执行,避免互相影响) + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Map processRule(AlertRule rule, Integer currentBatch) { Map failureCountMap = new HashMap<>(); - try { - List messages = new ArrayList<>(); - List msgs = new ArrayList<>(); + List messages = new ArrayList<>(); + List msgs = new ArrayList<>(); + try { boolean isChangedRule = "CHANGED".equalsIgnoreCase(rule.getAlertSql()); List> queryResults = isChangedRule ? handleChangedRule(rule) @@ -149,7 +251,6 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { String equId = row.get("equ_id") != null ? row.get("equ_id").toString() : null; msg.setEquId(equId); msg.setEquType(row.get("equ_type") != null ? row.get("equ_type").toString() : null); -// msg.setEquType(row.get("equ_name") != null ? row.get("equ_name").toString() : null); String msgdes = fillTemplate(rule.getAlertTemplate(), row, rule); msg.setBatch(currentBatch); @@ -158,34 +259,40 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { messages.add(msg); msgs.add(msgdes); - // 统计每个设备出现的次数 if (equId != null) { failureCountMap.put(equId, failureCountMap.getOrDefault(equId, 0) + 1); } } + // ⚙️ 发送预警 & 入库 if (!messages.isEmpty()) { alertSender.sendAlert(msgs, rule.getAlertWay(), rule.getAlertStation()); insertAlertMessages(messages); } + log.info("规则执行成功 → 规则ID={} 名称='{}',生成预警 {} 条", + rule.getId(), rule.getRuleName(), messages.size()); + } catch (Exception e) { - e.printStackTrace(); + // ⚠️ 独立事务自动回滚,不影响其他规则 + log.error("【规则执行异常】规则ID={} 名称='{}',SQL={},原因:{}", + rule.getId(), rule.getRuleName(), rule.getAlertSql(), e.getMessage(), e); } return failureCountMap; } + private String fillTemplate(String template, Map row, AlertRule rule) { String msg = template; Map ruleMap = convertRuleToMap(rule); //从预警规则中获取 预警规则名称,级别,方式,到站方 预警规则内容描述,预警规则创新时间,表格名称 for (Map.Entry entry : ruleMap.entrySet()) { - msg = msg.replace("{" + entry.getKey() + "}", entry.getValue() != null ? entry.getValue().toString() : ""); + msg = msg.replace("{" + entry.getKey() + "}", entry.getValue() != null ? entry.getValue().toString() : "为空"); } //设备id,设备类型,你要查参数 ,针对表格内容具体id, for (Map.Entry entry : row.entrySet()) { - msg = msg.replace("{" + entry.getKey() + "}", entry.getValue() != null ? entry.getValue().toString() : ""); + msg = msg.replace("{" + entry.getKey() + "}", entry.getValue() != null ? entry.getValue().toString() : "为空"); } return msg; } @@ -234,8 +341,10 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService { " WHERE t3.equ_id = g1.equ_id " + " ) " + ") " + - "AND g1.`" + col + "` <> g2.`" + col + "`"; + "AND (g1." + col + " <> g2." + col + " OR g1." + col + " IS NULL OR g2." + col + " IS NULL)"; + System.out.println(sql); + System.out.println(jdbcTemplate.queryForList(sql)); // 4. 返回查询结果 return jdbcTemplate.queryForList(sql); } diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertRuleServiceImpl.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertRuleServiceImpl.java index 5ad4545b..81fb8ca6 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertRuleServiceImpl.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertRuleServiceImpl.java @@ -215,7 +215,7 @@ public class AlertRuleServiceImpl implements IAlertRuleService switch (op) { case "=": case "!=": case "<": case "<=": case ">": case ">=": - return column + " " + op + " " + formatValue(value, valueType); + return "(" + column + " " + op + " " + formatValue(value, valueType) + " OR " + column + " IS NULL)"; case "contains": return column + " LIKE '%" + escapeLike(value) + "%'"; @@ -246,7 +246,7 @@ public class AlertRuleServiceImpl implements IAlertRuleService throw new IllegalArgumentException(op + " 运算符需要两个值: " + value); } String range = formatValue(vals[0].trim(), valueType) + " AND " + formatValue(vals[1].trim(), valueType); - return column + (op.equals("between") ? " BETWEEN " : " NOT BETWEEN ") + range; + return "(" + column + (op.equals("between") ? " BETWEEN " : " NOT BETWEEN ") + range + " OR " + column + " IS NULL)"; case "in": case "not in": diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java index e0662387..f527d368 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java @@ -35,7 +35,7 @@ public class AlertSenderServiceImpl implements IAlertSenderService { @Autowired private InternalMessageMapper messageMapper; // 站内信 Mapper @Autowired - private WebSocketService webSocketService; + private WebSocketService webSocketService; @Value("${spring.mail.username}") private String from; @@ -47,21 +47,45 @@ public class AlertSenderServiceImpl implements IAlertSenderService { * @param alertWay "email" 或 "message" * @param target 预警目标(邮箱或角色ID) */ - @Override +// @Override +// public void sendAlert(List alertMessages, String alertWay, String target) { +// if (alertMessages == null || alertMessages.isEmpty()) return; +// +// // 将多条消息合并成一条 +// String combinedMessage = String.join("\n", alertMessages); +// //运维人员2 +// sendInternalMessage("预警信息", combinedMessage, "2"); +// +// if ("email".equalsIgnoreCase(alertWay)) { +// sendEmail(combinedMessage, target); +// } else { +// throw new IllegalArgumentException("未知预警方式:" + alertWay); +// } +// +// } public void sendAlert(List alertMessages, String alertWay, String target) { if (alertMessages == null || alertMessages.isEmpty()) return; // 将多条消息合并成一条 String combinedMessage = String.join("\n", alertMessages); - //运维人员2 - sendInternalMessage("预警信息",combinedMessage, "2"); + // 发送站内消息(运维人员2) + try { + sendInternalMessage("预警信息", combinedMessage, "2"); + } catch (Exception e) { + log.error("发送站内消息失败: {}", e.getMessage(), e); + } + + // 发送邮件或其他方式 if ("email".equalsIgnoreCase(alertWay)) { - sendEmail(combinedMessage, target); + try { + sendEmail(combinedMessage, target); + } catch (Exception e) { + log.info("发送邮件失败: {}", e.getMessage(), e); + } } else { - throw new IllegalArgumentException("未知预警方式:" + alertWay); + log.info("未知预警方式:{}", alertWay); } - } @@ -109,58 +133,134 @@ public class AlertSenderServiceImpl implements IAlertSenderService { // // } - public void sendInternalMessage(String title, String content, String roleIdStr) { - long roleId = Long.parseLong(roleIdStr); +// public void sendInternalMessage(String title, String content, String roleIdStr) { +// long roleId = Long.parseLong(roleIdStr); +// List users = userMapper.selectByRoleId(roleId); +// if (users == null || users.isEmpty()) return; +// //1.插入站内信表 +// List messages = new ArrayList<>(); +// for (SysUser user : users) { +// InternalMessage msg = new InternalMessage(); +// msg.setUserId(user.getUserId()); +// msg.setContent(content); // 一次插入整条合并后的消息 +// msg.setCreateTime(new Date()); +// msg.setStatus("unread"); +// msg.setTitle(title); +// messages.add(msg); +// } +// List> result = insertMessages(messages); +// +// //2.发送实时消息 +// for (Map item : result) { +// Long userId = (Long) item.get("userId"); +// Long messageId = (Long) item.get("id"); +// +// // 调用 WebSocket 工具发送 +// webSocketService.sendAlertMessage(userId, messageId, title, content); +// } +// +// log.info("共发送站内信 {} 条(角色ID={})", result.size(), roleId); +// } + + //实现容错性,就是插入站内信,发送通知失败,不会滚事务,只记录 + public void sendInternalMessage(String title, String content, String roleIdStr) { + long roleId; + try { + roleId = Long.parseLong(roleIdStr); + } catch (NumberFormatException e) { + log.error("角色ID格式错误: {}", roleIdStr, e); + return; + } + List users = userMapper.selectByRoleId(roleId); - if (users == null || users.isEmpty()) return; - //1.插入站内信表 + if (users == null || users.isEmpty()) { + log.warn("角色ID={} 没有用户可发送站内信", roleId); + return; + } + List messages = new ArrayList<>(); for (SysUser user : users) { InternalMessage msg = new InternalMessage(); msg.setUserId(user.getUserId()); - msg.setContent(content); // 一次插入整条合并后的消息 + msg.setContent(content); msg.setCreateTime(new Date()); msg.setStatus("unread"); msg.setTitle(title); messages.add(msg); } - List> result = insertMessages(messages); - //2.发送实时消息 - for (Map item : result) { - Long userId = (Long) item.get("userId"); - Long messageId = (Long) item.get("id"); + List> result = new ArrayList<>(); + try { + result = insertMessages(messages); // 插入站内信 + } catch (Exception e) { + log.error("插入站内信失败: {}", e.getMessage(), e); + } - // 调用 WebSocket 工具发送 - webSocketService.sendAlertMessage(userId, messageId, title, content); + // 发送实时消息(WebSocket),单条失败不影响其他 + for (Map item : result) { + try { + Long userId = (Long) item.get("userId"); + Long messageId = (Long) item.get("id"); + webSocketService.sendAlertMessage(userId, messageId, title, content); + } catch (Exception e) { + log.error("发送实时站内信失败 userId={} messageId={}: {}", + item.get("userId"), item.get("id"), e.getMessage(), e); + } } - log.info("共发送站内信 {} 条(角色ID={})", result.size(), roleId); + log.info("尝试发送站内信 {} 条(角色ID={})", result.size(), roleId); } - @Transactional(rollbackFor = Exception.class) +// @Transactional(rollbackFor = Exception.class) +// public List> insertMessages(List messages) { +// if (messages == null || messages.isEmpty()) { +// return Collections.emptyList(); +// } +// +// int rows = messageMapper.batchInsert(messages); +// if (rows != messages.size()) { +// throw new RuntimeException("部分消息未插入成功,事务回滚"); +// } +// +// // 组装返回结果 +// List> resultList = new ArrayList<>(); +// for (InternalMessage msg : messages) { +// Map map = new HashMap<>(); +// map.put("id", msg.getId()); // 主键已被MyBatis自动回填 +// map.put("userId", msg.getUserId()); +// resultList.add(map); +// } +// +// return resultList; +// } +//} + + @Transactional public List> insertMessages(List messages) { if (messages == null || messages.isEmpty()) { return Collections.emptyList(); } - int rows = messageMapper.batchInsert(messages); - if (rows != messages.size()) { - throw new RuntimeException("部分消息未插入成功,事务回滚"); - } - - // 组装返回结果 List> resultList = new ArrayList<>(); - for (InternalMessage msg : messages) { - Map map = new HashMap<>(); - map.put("id", msg.getId()); // 主键已被MyBatis自动回填 - map.put("userId", msg.getUserId()); - resultList.add(map); + try { + int rows = messageMapper.batchInsert(messages); + if (rows != messages.size()) { + log.warn("部分站内信未成功插入,预期={}条,实际={}条", messages.size(), rows); + } + + for (InternalMessage msg : messages) { + Map map = new HashMap<>(); + map.put("id", msg.getId()); + map.put("userId", msg.getUserId()); + resultList.add(map); + } + + } catch (Exception e) { + log.error("插入站内信异常", e); + // 不抛异常 → 外层事务不会回滚 } return resultList; } } - - diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/GatherValidator.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/GatherValidator.java new file mode 100644 index 00000000..9ee6b4cb --- /dev/null +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/GatherValidator.java @@ -0,0 +1,105 @@ +package com.alops.system.service.impl; + +import com.alops.system.domain.EquOid; +import com.alops.system.domain.vo.GatherEntities; +import com.alops.system.mapper.EquBaseMapper; +import com.alops.system.service.IAlertSenderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +@Service +public class GatherValidator { + + public static class ValidationResult { + private List successList; + private List> failed; // 每个元素包含 equId 和 name + + public ValidationResult(List successList, List> failed) { + this.successList = successList; + this.failed = failed; + } + + public List getSuccessList() { + return successList; + } + + public List> getFailed() { + return failed; + } + } + private static final Logger log = LoggerFactory.getLogger(ScheduleRulesServiceImpl.class); + @Autowired + public EquBaseMapper equBaseMapper; + @Autowired + public IAlertSenderService iAlertSenderService; + + /** + * 检查 successList 中每台设备的完整性 + * 如果所有设备都失败(successList 为空),抛异常 + */ + + public ValidationResult validateBeforeDbOperation( + List successList, + List> initialFailed, // 可以传入之前收集的失败设备 + Map> oidMap) { + + List validSuccessList = new ArrayList<>(); + List> failed = new ArrayList<>(initialFailed); + + for (GatherEntities g : successList) { + String equId = g.getEquGather() != null ? g.getEquGather().getEquId() : null; + String equName = equId != null ? equBaseMapper.selectEquNameById(equId) : "未知设备"; + + boolean hasEmptySubTable = g.getEquGather() == null || + g.getSubEntities().values().stream() + .anyMatch(list -> list == null || list.isEmpty()); + + // 更新型 equ_base 检查 + List oids = oidMap.getOrDefault(equId, Collections.emptyList()); + boolean hasEnable1 = oids.stream().anyMatch(oid -> oid.getEnabled() == 1); + Object updateEntity = g.getUpdateEntities().get("equ_base"); + boolean updateEquBaseMissing = hasEnable1 && updateEntity == null; + + if (hasEmptySubTable || updateEquBaseMissing) { + Map failInfo = new HashMap<>(); + failInfo.put("equId", equId); + failInfo.put("equName", equName); + failed.add(failInfo); + + // 记录日志 + List missingTables = new ArrayList<>(); + if (g.getEquGather() == null) { + missingTables.add("equ_gather 主表为空"); + } + g.getSubEntities().forEach((tableName, list) -> { + if (list == null || list.isEmpty()) missingTables.add(tableName); + }); + if (updateEquBaseMissing) missingTables.add("更新型 equ_base 为空"); + + log.error("【采集失败设备】设备 [{}] 缺少数据:{}", equName, String.join(", ", missingTables)); + String content = String.format("设备 [%s] 采集失败,缺少数据:%s", + equName, String.join(", ", missingTables)); + iAlertSenderService.sendInternalMessage("采集失败设备", content, "2"); + } else { + validSuccessList.add(g); + log.info("设备 [{}] 采集成功,数据完整", equName); + } + } + + // 如果所有设备都失败 + if (validSuccessList.isEmpty()) { + log.error("本次采集失败,所有设备采集数据不完整或采集失败,保持旧数据完好。失败设备列表:{}", + failed.stream() + .map(f -> f.get("equName")) + .collect(Collectors.joining(", "))); + throw new RuntimeException("本次采集失败,所有设备采集数据不完整或采集失败!"); + } + + return new ValidationResult(validSuccessList, failed); + } +} + diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/MemoryToolServiceImpl.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/MemoryToolServiceImpl.java index 25ac12df..56fe2f80 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/MemoryToolServiceImpl.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/MemoryToolServiceImpl.java @@ -29,13 +29,13 @@ public class MemoryToolServiceImpl implements MemoryToolService { @Autowired private EquGatherMapper equGatherMapper; @Autowired - private EquInterfaceMapper equInterfaceMapper; + private EquInterfaceMapper equInterfaceMapper; @Autowired - private EquRoutingMapper equRoutingMapper; + private EquRoutingMapper equRoutingMapper; @Autowired - private EquVeneerMapper equVeneerMapper; + private EquVeneerMapper equVeneerMapper; @Autowired - private EquVlanMapper equVlanMapper; + private EquVlanMapper equVlanMapper; private static final Logger log = LoggerFactory.getLogger(MemoryToolServiceImpl.class); //生成实体类和存入数据库 @@ -52,6 +52,7 @@ public class MemoryToolServiceImpl implements MemoryToolService { // 2. 临时存放单值字段 Map equGatherFields = new HashMap<>(); + Map updateEntities = new HashMap<>(); // 3. 遍历 snmpResults Map> listEntitiesMap = new HashMap<>(); // key: 表名, value: 实体列表 @@ -59,6 +60,22 @@ public class MemoryToolServiceImpl implements MemoryToolService { String param = entry.getKey(); Object value = entry.getValue(); + + // 🟩 如果是 equ_base,则单独处理为更新型实体 + if ("equ_base".equalsIgnoreCase(param)) { + if (value instanceof Map) { + Map row = (Map) value; + Object entity = createEntityForTable("equ_base", row); + if (entity != null) { + // 填入设备ID(更新时使用) + fillField(entity, "equ_base", "id", device.getId()); + updateEntities.put("equ_base", entity); + } + } + continue; // 跳过普通插入逻辑 + } + + //插入行实体 if (value instanceof List) { // 处理 List 类型,转换成实体对象,表名直接用 param @@ -102,7 +119,7 @@ public class MemoryToolServiceImpl implements MemoryToolService { GatherEntities result = new GatherEntities(); result.setEquGather(equGather); result.getSubEntities().putAll(listEntitiesMap); - + result.getUpdateEntities().putAll(updateEntities); return result; } @@ -156,7 +173,34 @@ public class MemoryToolServiceImpl implements MemoryToolService { } } + public void insertEntityList(String tableName, List entities){ + if (entities == null || entities.isEmpty()) { + throw new IllegalArgumentException("批量插入实体列表为空,表:" + tableName); + } + try { + switch (tableName) { + case "equ_interface": + equInterfaceMapper.insertBatch((List)(List) entities); + break; + case "equ_routing": + equRoutingMapper.insertBatchRouting((List)(List) entities); + break; + case "equ_veneer": + equVeneerMapper.insertBatchVeneer((List)(List) entities); + break; + case "equ_vlan": + equVlanMapper.insertBatchVlan((List)(List) entities); + break; + default: + throw new UnsupportedOperationException("未实现的表批量插入:" + tableName); + } + } catch (Exception e) { + // 打印日志并抛异常,保证事务回滚 + log.error("批量插入表 {} 失败, 数据量: {}", tableName, entities.size(), e); + throw new RuntimeException("批量插入表 " + tableName + " 失败", e); + } + } /** * 根据表名和字段生成对应实体对象 @@ -182,6 +226,10 @@ public class MemoryToolServiceImpl implements MemoryToolService { EquVlan equVlan=new EquVlan(); rowData.forEach((k, v) -> fillField(equVlan, tableName, k, v != null ? v.toString() : null)); return equVlan; + case "equ_base": + EquBase equBase =new EquBase(); + rowData.forEach((k, v) -> fillField(equBase, tableName, k, v != null ? v.toString() : null)); + return equBase; // 可以继续加其他表 default: return null; @@ -191,27 +239,30 @@ public class MemoryToolServiceImpl implements MemoryToolService { /** * 批量插入数据库,按表名分发到对应 Mapper */ - private void insertEntityList(String tableName, List entities) { - if (entities.isEmpty()) return; - switch (tableName) { - case "equ_interface": - equInterfaceMapper.insertBatch((List)(List) entities); - break; - case "equ_routing": - equRoutingMapper.insertBatchRouting((List)(List) entities); - break; - case "equ_veneer": - equVeneerMapper.insertBatchVeneer((List)(List) entities); - break; - case "equ_vlan": - equVlanMapper.insertBatchVlan((List)(List) entities); - break; - // 其他表继续添加 - } - } +// private void insertEntityList(String tableName, List entities) { +// if (entities.isEmpty()) return; +// +// switch (tableName) { +// case "equ_interface": +// equInterfaceMapper.insertBatch((List)(List) entities); +// break; +// case "equ_routing": +// +// equRoutingMapper.insertBatchRouting((List)(List) entities); +// break; +// case "equ_veneer": +// +// equVeneerMapper.insertBatchVeneer((List)(List) entities); +// break; +// case "equ_vlan": +// equVlanMapper.insertBatchVlan((List)(List) entities); +// break; +// // 其他表继续添加 +// } +// } /** * 根据表名和字段名给实体赋值 @@ -222,9 +273,9 @@ public class MemoryToolServiceImpl implements MemoryToolService { * @param value 要赋的值(String 类型,可根据属性类型转换) */ public void fillField(Object entity, String tableName, String fieldName, String value) { - if (entity == null || fieldName == null || value == null) { - return; - } +// if (entity == null || fieldName == null || value == null) { +// throw new IllegalArgumentException("实体对象、字段名或值不能为空"); +// } try { Field field = null; @@ -241,7 +292,8 @@ public class MemoryToolServiceImpl implements MemoryToolService { } catch (NoSuchFieldException ignored) { log.warn("实体 {} 没有字段 {}(驼峰:{}),表名 {}", clazz.getSimpleName(), fieldName, camelName, tableName); - return; // 找不到就直接返回 + throw new RuntimeException(String.format("实体 %s 没有字段 %s(驼峰:%s),表名 %s", + clazz.getSimpleName(), fieldName, camelName, tableName)); } } @@ -270,6 +322,8 @@ public class MemoryToolServiceImpl implements MemoryToolService { } catch (Exception e) { log.error("实体 {} 字段 {} 赋值失败: {}", entity.getClass().getSimpleName(), fieldName, e.getMessage(), e); +// throw new RuntimeException(String.format("实体 %s 字段 %s 赋值失败: %s", +// entity.getClass().getSimpleName(), fieldName, e.getMessage()), e); } } diff --git a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/ScheduleRulesServiceImpl.java b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/ScheduleRulesServiceImpl.java index 60c4a0c2..20dc9338 100644 --- a/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/ScheduleRulesServiceImpl.java +++ b/ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/ScheduleRulesServiceImpl.java @@ -21,6 +21,7 @@ import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -50,16 +51,32 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { @Autowired private IAlertLaunchService iAlertLaunchService; + @Autowired + private EquInterfaceMapper equInterfaceMapper; + @Autowired + private EquRoutingMapper equRoutingMapper; + @Autowired + private EquVeneerMapper equVeneerMapper; + @Autowired + private EquVlanMapper equVlanMapper; + @Autowired private IQuartzService quartzService; - @Autowired - private MemoryToolService memoryToolService; + @Autowired + private MemoryToolService memoryToolService; @Autowired private IAlertSenderService iAlertSenderService; + @Autowired + public GatherValidator gatherValidator; - private static final Logger log = LoggerFactory.getLogger(ScheduleRulesServiceImpl.class); + private final JdbcTemplate jdbcTemplate; + // 构造器注入 + public ScheduleRulesServiceImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + private static final Logger log = LoggerFactory.getLogger(ScheduleRulesServiceImpl.class); @Override @@ -99,34 +116,7 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { public Boolean executeRule(ScheduleRules newRule) throws SchedulerException, ExecutionException, InterruptedException { -// // 1. 获取所有正在执行(1)和待执行(0)的规则 -// List runningOrWaiting = scheduleRulesMapper.selectList(); -// if (runningOrWaiting!=null) { -// for (ScheduleRules rule : runningOrWaiting) { -// switch (rule.getStatus()) { -// case 1: // 正在执行 -// log.info("规则[{}]:正在执行上一个任务,等待上一个任务完成……", rule.getId()); -// waitTaskFinish(rule.getId()); -// log.info("规则[{}]:完成上一个任务任务,开始新的任务…", rule.getId()); -// if (rule.getRuleType() == 2 || rule.getRuleType() == 1) { -// rule.setStatus(2); -// } else if (rule.getRuleType() == 0) { -// rule.setStatus(4); -// } -// scheduleRulesMapper.updateScheduleRules(rule); -// break; -// case 0: // 待执行 -// if (rule.getRuleType() == 0 || rule.getRuleType() == 1) { -// quartzService.deleteJob(rule.getId()); -// } -// rule.setStatus(4); -// scheduleRulesMapper.updateScheduleRules(rule); -// break; -// default: -// break; -// } -// } -// } + // 2. 执行新规则 try { Date now = new Date(); @@ -151,7 +141,7 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { scheduleRulesMapper.updateScheduleRules(newRule); String content = String.format("立即任务执行完成", newRule.getRuleName()); - iAlertSenderService.sendInternalMessage("采集任务成功",content, "2"); + iAlertSenderService.sendInternalMessage("采集任务整体执行成功",content, "2"); } catch (Exception e) { newRule.setStatus(3); // 执行失败 @@ -159,7 +149,7 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { String content = String.format("立即任务执行失败:%s", e.getMessage()); try { - iAlertSenderService.sendInternalMessage("采集任务失败",content, "2"); + iAlertSenderService.sendInternalMessage("采集任务整体执行失败",content, "2"); } catch (Exception ne) { log.error("发送站内信失败: {}", ne.getMessage(), ne); } @@ -192,96 +182,8 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { } } - /** - * 删除所有设备相关表数据,保证事务一致性 - */ - @Transactional(rollbackFor = Exception.class) - public void deleteAllEquipmentData() { - equBaseMapper.deleteEquInterface(); - equBaseMapper.deleteEquRouting(); - equBaseMapper.deleteEquVeneer(); - equBaseMapper.deleteEquVlan(); - } - //往上抛异常 -// @Override -// public int executeImmediately() throws ExecutionException, InterruptedException { -// // 删除之前的信息 -// deleteAllEquipmentData(); -// -// List devices = equBaseMapper.findEquBaseList(); -// if (devices.isEmpty()) return Code.FAIL; -// -// Map> oidMap = new HashMap<>(); -// for (EquBase dev : devices) { -// oidMap.put(dev.getEquManuId(), equOidMapper.findByManuIdAndStatus(dev.getEquManuId())); -// } -// -// ExecutorService executor = Executors.newFixedThreadPool(10); -// List> futures = new ArrayList<>(); -// -// for (EquBase device : devices) { -// futures.add(executor.submit(() -> { -// long start = System.currentTimeMillis(); -// -// //获取对应oid -// List oids = oidMap.getOrDefault(device.getEquManuId(), Collections.emptyList()); -// if (oids.isEmpty()) { -// System.err.println("⚠️ 设备[" + device.getName() + "]未配置OID,跳过"); -// return null; -// } -// //采集对应结果 -// Map snmpResults = snmpService.query(device.getIp(), oids); -// if (snmpResults == null || snmpResults.isEmpty()) { -// System.err.println("⚠️ 设备[" + device.getName() + "] SNMP采集为空,跳过"); -// return null; -// } -// -// long afterSnmp = System.currentTimeMillis(); -// System.out.println("SNMP查询耗时: " + (afterSnmp - start) + " ms"); -// //生成对应实体 -// GatherEntities entities = memoryToolService.buildAndInsertEntity(device, snmpResults); -// -// if (entities == null || entities.getEquGather() == null) { -// System.err.println("⚠️ 设备[" + device.getName() + "] 构建实体失败,跳过"); -// return null; -// } -// -// long afterBuild = System.currentTimeMillis(); -// System.out.println("buildAndInsertEntity耗时: " + (afterBuild - afterSnmp) + " ms"); -// -// //构建健康度 -// if (entities != null && entities.getEquGather() != null) { -// Double temperature = parseDouble(snmpResults.get("temperature")); -// Double cpuUsage = parseDouble(snmpResults.get("cpu_utilization")); -// Double memUsage = parseDouble(snmpResults.get("memory_utilization")); -// double tempHealth = calculateTemperatureHealth(temperature); -// double cpuHealth = calculateCpuHealth(cpuUsage); -// double memHealth = calculateMemoryHealth(memUsage); -// double totalHealth = Math.max(Math.max(tempHealth, 1), Math.max(cpuHealth, memHealth)); -// entities.getEquGather().setHealth(BigDecimal.valueOf(totalHealth)); -// return entities; -// } -// return null; -// })); -// } -// //收集采集结果,批量存入数据库 -// List allEntities = new ArrayList<>(); -// try { -// for (Future f : futures) { -// GatherEntities g = f.get(); // 阻塞等待线程完成,如果线程异常会抛ExecutionException -// if (g == null) throw new RuntimeException("设备采集返回 null,可能采集失败"); -// allEntities.add(g); -// } -// } catch (ExecutionException | InterruptedException e) { -// e.printStackTrace(); -// executor.shutdownNow(); // 出异常直接中断剩余线程 -// throw e; // 抛给调用方 Job 捕获 -// } finally { -// executor.shutdown(); -// } -// return persistAndUpdate(allEntities); -// -// } + + @Transactional(rollbackFor = Exception.class) @Override @@ -293,12 +195,11 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { return Code.FAIL; } - // 构建设备 OID 映射 + // 构建设备 OID 映射(插入) Map> oidMap = new HashMap<>(); for (EquBase dev : devices) { oidMap.put(dev.getEquManuId(), equOidMapper.findByManuIdAndStatus(dev.getEquManuId())); } - // 并发采集 ExecutorService executor = Executors.newFixedThreadPool(10); List> futures = new ArrayList<>(); @@ -345,130 +246,189 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { // 收集采集结果 List successList = new ArrayList<>(); - List failed = new ArrayList<>(); + List> failed = new ArrayList<>(); // 包含设备ID和设备名称 + for (int i = 0; i < futures.size(); i++) { EquBase dev = devices.get(i); try { GatherEntities g = futures.get(i).get(); if (g != null) successList.add(g); - else failed.add(dev.getName()); + else failed.add(Map.of("equId", dev.getId(), "equName", dev.getName())); } catch (Exception e) { - failed.add(dev.getName()); + failed.add(Map.of("equId", dev.getId(), "equName", dev.getName())); log.error("收集设备 [{}] 结果失败", dev.getName(), e); } } + //关闭线程池 executor.shutdown(); + // 过滤 successList 中不完整的设备,记录失败设备,equ_gather和equ_base只要有数据就行 + GatherValidator.ValidationResult validationResult = + gatherValidator.validateBeforeDbOperation(successList, failed, oidMap); -// // ✅ 完整性校验:找出所有子表为空的设备 -// List invalidDevices = successList.stream() -// .filter(g -> g.getSubEntities() == null -// || g.getSubEntities().isEmpty() -// || g.getSubEntities().values().stream().anyMatch(list -> list == null || list.isEmpty())) -// .map(g -> g.getEquGather().getEquId()) -// .collect(Collectors.toList()); -// -// // 如果全部失败或者发现不完整设备,抛异常 -// if (successList.isEmpty() || !invalidDevices.isEmpty()) { -// String msg = "本次采集失败,保持旧数据完好。失败设备:" + -// String.join(", ", failed) + -// (invalidDevices.isEmpty() ? "" : ",采集数据不完整设备:" + String.join(", ", invalidDevices)); -// log.error(msg); -// throw new RuntimeException(msg); // 异常抛出去,外层可捕获发送邮件 -// } - // ✅ 完整性校验:找出所有子表为空的设备及对应子表 - Map> invalidDeviceMap = successList.stream() - .map(g -> { - String equId = g.getEquGather().getEquId(); - - // 找出这个设备下不完整的子表 - List emptyTables = new ArrayList<>(); - - // subEntities 为空或没有数据 - if (g.getSubEntities() == null || g.getSubEntities().isEmpty()) { - emptyTables.add("所有子表为空"); - } else { - // 检查每个子表的值 - g.getSubEntities().forEach((tableName, list) -> { - if (list == null || list.isEmpty()) { - emptyTables.add(tableName); - } - }); - } - // 如果有任何空子表,返回 (设备ID, 空表列表) - return emptyTables.isEmpty() ? null : Map.entry(equId, emptyTables); - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + List failedEquIds = validationResult.getFailed().stream() + .map(f -> f.get("equId")) + .collect(Collectors.toList()); -// ✅ 提取不完整设备ID列表 - List invalidDevices = new ArrayList<>(invalidDeviceMap.keySet()); + // 事务内:删除成功设备依附表 + 插入成功设备数据 + 执行预警 + deleteAllEquipmentData(failedEquIds); // 删除依附表,不含 equ_gather -// ✅ 如果全部失败或有不完整设备,抛异常 - if (successList.isEmpty() || !invalidDevices.isEmpty()) { - StringBuilder msg = new StringBuilder("本次采集失败,保持旧数据完好。"); + // 插入成功设备数据(内部失败会抛异常,触发事务回滚) + persistAndUpdate(successList); - if (!failed.isEmpty()) { - msg.append("失败设备:").append(String.join(", ", failed)); - } + // 发送成功日志 + log.info("本次采集成功 {} 台,删除旧表并写入最新数据,失败设备:{}", successList.size(), failed); - if (!invalidDeviceMap.isEmpty()) { - msg.append(";采集数据不完整设备如下:"); - invalidDeviceMap.forEach((equId, tables) -> - msg.append("\n设备[").append(equId).append("] 缺少表:") - .append(String.join(", ", tables))); - } + return Code.SUCCESS; + } - log.error(msg.toString()); - throw new RuntimeException(msg.toString()); + @Transactional(rollbackFor = Exception.class) + public void deleteAllEquipmentData(List failedEquIds) { + // 1️⃣ 查询失败设备最新采集ID + List excludeGatherIds = failedEquIds == null || failedEquIds.isEmpty() ? + Collections.emptyList() : equGatherMapper.selectLatestGatherIdsByEquIds(failedEquIds); + + // 2️⃣ 依附表列表 + String[] dependentTables = {"equ_interface", "equ_routing", "equ_veneer", "equ_vlan"}; + + for (String table : dependentTables) { + int deleted; + if (excludeGatherIds.isEmpty()) { + // 没有失败设备,直接清空表 + deleted = jdbcTemplate.update("DELETE FROM " + table); + } else { + // 使用占位符方式安全删除 + String inSql = excludeGatherIds.stream().map(id -> "?").collect(Collectors.joining(",")); + String sql = String.format("DELETE FROM %s WHERE equ_gather_id NOT IN (%s)", table, inSql); + deleted = jdbcTemplate.update(sql, excludeGatherIds.toArray()); + } + log.info("表 {} 删除 {} 条数据,保留失败设备采集ID: {}", table, deleted, excludeGatherIds); } - // 事务内:删除旧表 + 插入成功设备数据+执行预警 - deleteAllEquipmentData(); // 删除依附表,不含 equ_gather - - int inserted = persistAndUpdate(successList); - - if (inserted <= 0) { - throw new RuntimeException("成功采集设备写入失败,回滚事务"); - } - //发送一个采集成功的信 - log.info("本次采集成功 {} 台,删除旧表并写入最新数据,失败设备:{}", successList.size(), failed); - return Code.SUCCESS; + log.info("依附表数据删除完成,共保留 {} 个失败设备最新采集ID", excludeGatherIds.size()); } + /** * 批量落库、更新采集次数、执行最新预警 * 事务保证,如果任何步骤失败,整个操作回滚 */ @Transactional(rollbackFor = Exception.class) - public int persistAndUpdate(List allEntities) throws ExecutionException, InterruptedException { + public int persistAndUpdate(List allEntities) { if (allEntities == null || allEntities.isEmpty()) { - return 0; + throw new RuntimeException("待插入实体为空,无法落库"); } - // 批量落库 - long persistStart = System.currentTimeMillis(); - int result = memoryToolService.persistAll(allEntities); - long persistEnd = System.currentTimeMillis(); - System.out.println("批量落库耗时: " + (persistEnd - persistStart) + " ms"); - - // 更新采集次数 - Set idSet = allEntities.stream() - .filter(g -> g.getEquGather() != null) - .map(g -> g.getEquGather().getEquId()) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - if (!idSet.isEmpty()) { - equBaseMapper.incrementCollectCount(new ArrayList<>(idSet)); - } + try { + // ===== 1. 主表批量插入 ===== + List gatherList = allEntities.stream() + .map(GatherEntities::getEquGather) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (!gatherList.isEmpty()) { + equGatherMapper.batchInsertEquGather(gatherList); + log.info("主表批量插入完成,数量:{}", gatherList.size()); + } - // 执行最新预警 - iAlertLaunchService.executeImmediateAlerts(); + // ===== 2. 子表按表名分组批量插入 ===== + Map> groupedSubEntities = new HashMap<>(); + for (GatherEntities g : allEntities) { + g.getSubEntities().forEach((table, list) -> { + if (list != null && !list.isEmpty()) { + groupedSubEntities.computeIfAbsent(table, k -> new ArrayList<>()).addAll(list); + } + }); + } + + for (Map.Entry> entry : groupedSubEntities.entrySet()) { + String tableName = entry.getKey(); + List list = entry.getValue(); + insertEntityList(tableName, list); // 内部异常直接抛出 + log.info("子表 {} 批量插入完成,数量:{}", tableName, list.size()); + } + + // ===== 3. 更新 equ_base ===== + List updateList = allEntities.stream() + .map(GatherEntities::getUpdateEntities) + .filter(map -> map != null && map.get("equ_base") != null) + .map(map -> (EquBase) map.get("equ_base")) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (!updateList.isEmpty()) { + equBaseMapper.updateEquBaseVersionBatch(updateList); + log.info("equ_base 批量更新完成,数量:{}", updateList.size()); + } + + // ===== 4. 更新采集次数 ===== + Set equIds = allEntities.stream() + .map(GatherEntities::getEquGather) + .filter(Objects::nonNull) + .map(EquGather::getEquId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (!equIds.isEmpty()) { + equBaseMapper.incrementCollectCount(new ArrayList<>(equIds)); + log.info("采集次数更新完成,设备数量:{}", equIds.size()); + } - log.info("采集完一次!!!"); - return result; + // ===== 5. 执行预警(容错,不影响主事务) ===== + try { + iAlertLaunchService.executeImmediateAlerts(); + } catch (Exception e) { + log.error("预警执行异常,不影响采集事务", e); + } + + log.info("批量落库、更新完成,共处理设备:{}", allEntities.size()); + return allEntities.size(); + } catch (Exception e) { + log.error("批量落库失败,事务回滚", e); + throw new RuntimeException("批量落库失败,事务回滚", e); + } } +// @Transactional(rollbackFor = Exception.class) +// public int persistAndUpdate(List allEntities) throws ExecutionException, InterruptedException { +// if (allEntities == null || allEntities.isEmpty()) { +// return 0; +// } +// +// // 批量落库 +// +// int result = memoryToolService.persistAll(allEntities); +// +// //批量跟新 +// List updateList = allEntities.stream() +// .map(GatherEntities::getUpdateEntities) +// .filter(map -> map != null && map.get("equ_base") != null) +// .map(map -> (EquBase) map.get("equ_base")) +// .filter(Objects::nonNull) +// .collect(Collectors.toList()); +// +// if (!updateList.isEmpty()) { +// equBaseMapper.updateEquBaseVersionBatch(updateList); +// } +// +// // 更新采集次数 +// Set idSet = allEntities.stream() +// .filter(g -> g.getEquGather() != null) +// .map(g -> g.getEquGather().getEquId()) +// .filter(Objects::nonNull) +// .collect(Collectors.toSet()); +// if (!idSet.isEmpty()) { +// equBaseMapper.incrementCollectCount(new ArrayList<>(idSet)); +// } +// +// try { +// iAlertLaunchService.executeImmediateAlerts(); // 内部规则级容错 +// } catch (Exception e) { +// log.error("预警执行异常,统计预警次数出现错误,不影响采集", e); +// } +// log.info("采集完一次!!!"); +// return result; +// } // @Override // public int executeImmediately() throws ExecutionException, InterruptedException { @@ -564,6 +524,37 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService { // // } + + public void insertEntityList(String tableName, List entities){ + if (entities == null || entities.isEmpty()) { + throw new IllegalArgumentException("批量插入实体列表为空,表:" + tableName); + } + + try { + switch (tableName) { + case "equ_interface": + equInterfaceMapper.insertBatch((List)(List) entities); + break; + case "equ_routing": + equRoutingMapper.insertBatchRouting((List)(List) entities); + break; + case "equ_veneer": + equVeneerMapper.insertBatchVeneer((List)(List) entities); + break; + case "equ_vlan": + equVlanMapper.insertBatchVlan((List)(List) entities); + break; + default: + throw new UnsupportedOperationException("未实现的表批量插入:" + tableName); + } + } catch (Exception e) { + // 打印日志并抛异常,保证事务回滚 + log.error("批量插入表 {} 失败, 数据量: {}", tableName, entities.size(), e); + throw new RuntimeException("批量插入表 " + tableName + " 失败", e); + } + } + + // 健康度计算函数示例 private int calculateTemperatureHealth(Double temp) { if (temp == null) return 4; // 无数据视为需维护 diff --git a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml index f446891b..bf9a5fd2 100644 --- a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml +++ b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml @@ -77,6 +77,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + UPDATE equ_base + + + version = #{item.version} + + + WHERE id = #{item.id} + + + select id,equ_number, name, version, ip, manufacture, location, equ_type, in_charge, install_date, life_cycle, warranty, create_by, create_date, status, device_level, supplier_id from equ_base @@ -216,10 +235,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - AND b.create_date >= #{minTime} + AND b.install_date >= #{minTime} - AND b.create_date <= #{maxTime} + AND b.install_date <= #{maxTime} @@ -231,6 +250,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND b.location LIKE CONCAT('%', #{location}, '%') + + AND b.floor = #{floor} + diff --git a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquGatherMapper.xml b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquGatherMapper.xml index 170bfcd5..5c3e35fa 100644 --- a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquGatherMapper.xml +++ b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquGatherMapper.xml @@ -26,6 +26,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + UPDATE equ_gather diff --git a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquOidMapper.xml b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquOidMapper.xml index 9edb8ac1..4abdda1c 100644 --- a/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquOidMapper.xml +++ b/ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquOidMapper.xml @@ -65,9 +65,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update_time AS updateTime FROM equ_oid WHERE manufacturer_id = #{manufacturerId} - AND enabled = 2 + AND enabled IN (1, 2) ORDER BY id + + +