import { BadRequestException } from '@nestjs/common'; import { createHash } from 'node:crypto'; export function alertHistoryRange(from?: string, to?: string, now = new Date()) { const dateKey = (date: Date) => new Date(date.getTime() + 8 * 3600_000).toISOString().slice(0, 10); const endDate = to || dateKey(now); const startDate = from || dateKey(new Date(now.getTime() - 6 * 86400_000)); const parse = (value: string) => { const result = new Date(`${value}T00:00:00+08:00`); if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(result.getTime()) || dateKey(result) !== value) { throw new BadRequestException('告警日期无效'); } return result.getTime() / 1000; }; const start = parse(startDate); const end = parse(endDate) + 86400; if (end <= start || end - start > 31 * 86400) throw new BadRequestException('告警日期范围须为1至31天'); return { startDate, endDate, start, end: Math.min(end, now.getTime() / 1000) }; } export type AlertHistoryItem = { id: string; name: string; severity: string; service: string; instance: string; startedAt: string; firstObservedAt: string; lastObservedAt: string; }; // ALERTS_FOR_STATE stores activeAt as the sample value, separating repeated trigger cycles. // Observation boundaries are not claimed as exact recovery times. export function mergeAlertHistory( target: Map, series: Array<{ metric: Record; values?: [number, string][] }>, start: number, end: number, ) { for (const { metric, values } of series) { const labels = Object.entries(metric) .filter(([key]) => key !== '__name__') .sort(([a], [b]) => a.localeCompare(b)); const fingerprint = createHash('sha256').update(JSON.stringify(labels)).digest('hex'); for (const [time, rawActiveAt] of values ?? []) { const activeAt = Number(rawActiveAt); if (time < start || time >= end || !Number.isFinite(activeAt) || activeAt <= 0 || activeAt > time) continue; const id = `${fingerprint}:${activeAt}`; const observed = new Date(time * 1000).toISOString(); const item = target.get(id); if (item) { if (observed < item.firstObservedAt) item.firstObservedAt = observed; if (observed > item.lastObservedAt) item.lastObservedAt = observed; } else { target.set(id, { id, name: metric.alertname || '未命名告警', severity: metric.severity || 'info', service: metric.service || '', instance: metric.instance || '', startedAt: new Date(activeAt * 1000).toISOString(), firstObservedAt: observed, lastObservedAt: observed, }); } } } }