"use strict"; /** * 聚合规则存储 + 聚合升级引擎 * * 规则结构: * { * id: "agg_core_switch_offline", * name: "核心交换机批量离线升级", * group_by: ["catalog_type", "tier"], // 维度(D6:catalog_type + tier) * window_minutes: 5, * threshold_count: 3, * alert_type_filter: ["connectivity"], * level_filter: ["critical"], * target_level: "critical", * enabled: true, * } * * 触发方式:实时(D2)—— 每来一条新告警就检查窗口内计数 */ const { STORAGE_KEY_AGGREGATION_RULES } = require("./storageKeys.cjs"); let _rules = null; function _ensureLoaded(readFn) { if (_rules === null) { const raw = readFn(STORAGE_KEY_AGGREGATION_RULES); _rules = raw ? _safeParse(raw, []) : _getDefaultRules(); } } function _safeParse(text, def) { if (text === null || text === undefined) return def; if (typeof text === "object") return text; try { return JSON.parse(text); } catch { return def; } } function _save(writeFn) { writeFn(STORAGE_KEY_AGGREGATION_RULES, JSON.stringify(_rules)); } function _getDefaultRules() { const now = Date.now(); return [ { id: "agg_core_switch_offline", name: "核心设备批量离线升级", group_by: ["catalog_type", "tier"], window_minutes: 5, threshold_count: 3, alert_type_filter: ["connectivity"], level_filter: ["critical"], target_level: "critical", enabled: true, builtin: true, created_at: now, updated_at: now, }, { id: "agg_core_performance_spike", name: "核心设备性能批量异常升级", group_by: ["catalog_type", "tier"], window_minutes: 10, threshold_count: 5, alert_type_filter: ["performance_high"], level_filter: ["warning", "critical"], target_level: "critical", enabled: true, builtin: true, created_at: now, updated_at: now, }, ]; } // ---------- 规则 CRUD ---------- function listRules(readFn) { _ensureLoaded(readFn); return [..._rules]; } function getRule(readFn, ruleId) { _ensureLoaded(readFn); return _rules.find((r) => r.id === ruleId) || null; } function createRule(readFn, writeFn, data) { _ensureLoaded(readFn); const rule = { id: data.id || `agg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, name: data.name, group_by: data.group_by || ["catalog_type", "tier"], window_minutes: data.window_minutes || 5, threshold_count: data.threshold_count || 3, alert_type_filter: data.alert_type_filter || [], level_filter: data.level_filter || [], target_level: data.target_level || "critical", enabled: data.enabled !== false, builtin: false, created_at: Date.now(), updated_at: Date.now(), }; _rules.push(rule); _save(writeFn); return { ...rule }; } function updateRule(readFn, writeFn, ruleId, updates) { _ensureLoaded(readFn); const idx = _rules.findIndex((r) => r.id === ruleId); if (idx === -1) return null; if (_rules[idx].builtin) return null; _rules[idx] = { ..._rules[idx], ...updates, updated_at: Date.now() }; _save(writeFn); return { ..._rules[idx] }; } function deleteRule(readFn, writeFn, ruleId) { _ensureLoaded(readFn); const idx = _rules.findIndex((r) => r.id === ruleId); if (idx === -1) return false; if (_rules[idx].builtin) return false; _rules.splice(idx, 1); _save(writeFn); return true; } // ---------- 实时聚合检查(D2:每来一条新告警就调用)---------- /** * 检查窗口内活跃告警是否命中聚合规则 * @param {object} readFn - 存储读取函数 * @param {Array} activeAlerts - alertRecordStore 返回的活跃告警列表 * @param {object} deviceProfiles - { [device_id]: { catalog_type, tier, ... } } * @returns {Array} 命中的聚合规则 [{ rule, groupKey, matchingAlerts }] */ function checkAggregation(readFn, activeAlerts, deviceProfiles) { _ensureLoaded(readFn); const hits = []; const now = Date.now(); for (const rule of _rules) { if (!rule.enabled) continue; const windowMs = rule.window_minutes * 60 * 1000; const windowStart = now - windowMs; // 过滤时间窗口内的活跃告警 const inWindow = activeAlerts.filter((a) => a.triggered_at >= windowStart); // 按 group_by 维度分组 const groups = new Map(); for (const alert of inWindow) { const profile = deviceProfiles?.[alert.device_id] || {}; let keyParts = []; for (const dim of rule.group_by) { if (dim === "catalog_type") keyParts.push(profile.catalog_type || alert.alert_catalog || "unknown"); else if (dim === "tier") keyParts.push(profile.tier || "unknown"); else keyParts.push("*"); } const groupKey = keyParts.join("::"); // alert_type / level 过滤 if (rule.alert_type_filter?.length > 0) { const at = alert.alert_type || ""; if (!rule.alert_type_filter.includes(at)) continue; } if (rule.level_filter?.length > 0 && !rule.level_filter.includes(alert.level)) { continue; } if (!groups.has(groupKey)) groups.set(groupKey, []); groups.get(groupKey).push(alert); } // 检查阈值 for (const [groupKey, matchingAlerts] of groups) { if (matchingAlerts.length >= rule.threshold_count) { hits.push({ rule, groupKey, matchingAlerts }); } } } return hits; } /** * 升级告警(产出新 alertRecord) * @param {object} readFn - 存储读取函数 * @param {object} writeFn - 存储写入函数 * @param {string} reason - 升级原因 * @param {object} data - { source_alert_ids, target_level, message } * @returns {object} 新告警记录 */ function escalateAlert(readFn, writeFn, reason, data) { const { alertRecordStore } = require("./alertRecordStore.cjs"); const alert = alertRecordStore._createAlert ? _createEscalationAlert(reason, data) : _createEscalationAlert(reason, data); // 直接写到 alertRecordStore(复用内部函数) const alerts = _loadAlerts(readFn); alerts.push(alert); _saveAlerts(writeFn, alerts); return alert; } // alertRecordStore 内部结构复用(直接操作存储避免循环依赖) function _loadAlerts(readFn) { const raw = readFn("netcatty_inspection_alert_records_v1"); return raw ? (typeof raw === "string" ? JSON.parse(raw) : raw) : []; } function _saveAlerts(writeFn, alerts) { writeFn("netcatty_inspection_alert_records_v1", JSON.stringify(alerts)); } function _createEscalationAlert(reason, data) { const now = Date.now(); const deviceNames = (data.source_alert_ids || []).join(", "); return { id: `alert_esc_${now}_${Math.random().toString(36).slice(2, 8)}`, rule_id: `rule_escalation_${reason}`, rule_name: reason === "aggregation" ? "聚合升级" : "超时升级", device_id: "group_escalation", device_name: `[升级告警] ${deviceNames}`, metric: "escalation", metric_value: data.source_alert_ids?.length || 0, threshold: data.target_level || "critical", level: data.target_level || "critical", message: data.message || `${reason}: ${data.source_alert_ids?.length || 0} 台设备告警触发升级`, triggered_at: now, status: "active", acknowledged_by: null, acknowledged_at: null, resolved_at: null, resolved_reason: null, note: null, // v1.3 扩展字段 escalation: { trigger: reason, // aggregation | escalation_fallback_timeout source_alert_ids: data.source_alert_ids || [], target_level: data.target_level || "critical", escalated_at: now, }, }; } function _resetForTest() { _rules = null; } module.exports = { listRules, getRule, createRule, updateRule, deleteRule, checkAggregation, escalateAlert, _resetForTest, };