98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
// shared/lib/alert/alert-manager.ts — 告警管理引擎
|
|
import type { AlertLevel, AlertChannelConfig, AlertDecision } from './types'
|
|
|
|
export interface CooldownProvider {
|
|
getLastAlertTime(channelId: number, serviceId: number, level: string): Promise<number | null>
|
|
setLastAlertTime(channelId: number, serviceId: number, level: string, timestamp: number): Promise<void>
|
|
}
|
|
|
|
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<AlertDecision> {
|
|
// 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<boolean> {
|
|
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<void> {
|
|
await this.cooldownProvider.setLastAlertTime(channelId, serviceId, level, timestamp)
|
|
}
|
|
}
|