// shared/lib/alert/alert-manager.ts — 告警管理引擎 import type { AlertLevel, AlertChannelConfig, AlertDecision } from './types' export interface CooldownProvider { getLastAlertTime(channelId: number, serviceId: number, level: string): Promise setLastAlertTime(channelId: number, serviceId: number, level: string, timestamp: number): Promise } export interface AlertPushChannel { push(title: string, content: string, level: string): Promise<{ success: boolean }> } export class AlertManager { private cooldownProvider: CooldownProvider constructor(cooldownProvider: CooldownProvider) { this.cooldownProvider = cooldownProvider } // 评估是否向某 channel 推送告警 async evaluate( channel: AlertChannelConfig, serviceId: number, level: AlertLevel, now: Date = new Date() ): Promise { // 1. 级别匹配 if (!this.levelMatches(channel, level)) { return { shouldPush: false, reason: `level ${level} not enabled for channel ${channel.name}` } } // 2. 免打扰时段 if (this.isQuietPeriod(channel, now) && !(level === 'critical' && channel.quietBypassCritical)) { return { shouldPush: false, reason: `quiet period for channel ${channel.name}` } } // 3. 冷却检查 const cooling = await this.isCoolingDown(channel.id, serviceId, level, channel.cooldownMinutes, now) if (cooling) { return { shouldPush: false, reason: `cooldown for channel ${channel.name}` } } return { shouldPush: true } } // 级别匹配检查 private levelMatches(channel: AlertChannelConfig, level: AlertLevel): boolean { switch (level) { case 'critical': return channel.levelCritical case 'warning': return channel.levelWarning case 'info': return channel.levelInfo } } // 免打扰时段判定(支持跨日时段) isQuietPeriod(channel: AlertChannelConfig, now: Date): boolean { if (!channel.quietEnabled) return false const [sh, sm] = channel.quietStart.split(':').map(Number) const [eh, em] = channel.quietEnd.split(':').map(Number) const current = now.getHours() * 60 + now.getMinutes() const start = sh * 60 + sm const end = eh * 60 + em if (start <= end) { // 同日时段,如 02:00 - 06:00 return current >= start && current < end } else { // 跨日时段,如 23:00 - 07:00 return current >= start || current < end } } // 冷却检查 async isCoolingDown( channelId: number, serviceId: number, level: AlertLevel, cooldownMinutes: number, now: Date = new Date() ): Promise { const lastTime = await this.cooldownProvider.getLastAlertTime(channelId, serviceId, level) if (!lastTime) return false const elapsed = now.getTime() - lastTime return elapsed < cooldownMinutes * 60 * 1000 } // 记录告警时间 async recordAlert( channelId: number, serviceId: number, level: AlertLevel, timestamp: number = Date.now() ): Promise { await this.cooldownProvider.setLastAlertTime(channelId, serviceId, level, timestamp) } }