// shared/lib/alert/health-checker.ts — 健康检查引擎 import type { CheckType, HealthCheckConfig, CheckResult } from './types' export interface Checker { check(config: HealthCheckConfig): Promise } export class HealthChecker { private checkers = new Map() registerChecker(type: CheckType, handler: Checker): void { this.checkers.set(type, handler) } // 并发检查所有维度 async check(checks: HealthCheckConfig[], timeoutMs: number): Promise { const results = await Promise.allSettled( checks.map(config => this.checkOne(config, timeoutMs)) ) return results.map(r => (r.status === 'fulfilled' ? r.value : { success: false, status: 'unknown' as const, error: 'Checker execution failed', latency: timeoutMs, checkType: 'http' as const, })) } private async checkOne(config: HealthCheckConfig, timeoutMs: number): Promise { const checker = this.checkers.get(config.type) if (!checker) { return { success: false, status: 'unknown', error: `Unknown check type: ${config.type}`, latency: 0, checkType: config.type } } return checker.check(config) } } // 内置 HTTP 检查器 export class HttpChecker implements Checker { async check(config: HealthCheckConfig): Promise { const start = Date.now() try { const res = await fetch(config.url!, { signal: AbortSignal.timeout(5000), headers: { Accept: 'text/html,application/json' }, }) const latency = Date.now() - start if (config.expectedStatus && res.status !== config.expectedStatus) { return { success: false, status: 'abnormal', error: `Expected ${config.expectedStatus}, got ${res.status}`, latency, checkType: 'http' } } if (config.expectedBody) { const body = await res.text() if (!body.includes(config.expectedBody)) { return { success: false, status: 'abnormal', error: `Body does not contain "${config.expectedBody}"`, latency, checkType: 'http' } } } return { success: true, status: 'normal', latency, checkType: 'http' } } catch (err) { return { success: false, status: 'abnormal', error: String(err), latency: Date.now() - start, checkType: 'http' } } } } // 内置 Docker 检查器(同机部署,需要 docker.sock 只读挂载) export class DockerChecker implements Checker { async check(config: HealthCheckConfig): Promise { const start = Date.now() try { const { execFileSync } = await import('child_process') const containerName = config.containerName! const stdout = execFileSync('docker', ['ps', '--filter', `name=${containerName}`, '--format', '{{.Status}}'], { encoding: 'utf-8', timeout: 5000, }).trim() const latency = Date.now() - start if (!stdout) { return { success: false, status: 'abnormal', error: `Container ${containerName} not found or stopped`, latency, checkType: 'docker' } } if (stdout.startsWith('Up')) { return { success: true, status: 'normal', latency, checkType: 'docker' } } return { success: false, status: 'abnormal', error: `Container ${containerName} status: ${stdout}`, latency, checkType: 'docker' } } catch (err) { const latency = Date.now() - start // 区分 daemon 不可达 vs 其他错误 const errStr = String(err) if (errStr.includes('Cannot connect') || errStr.includes('Is the docker daemon')) { return { success: false, status: 'unknown', error: 'Docker daemon unreachable', latency, checkType: 'docker' } } return { success: false, status: 'abnormal', error: errStr, latency, checkType: 'docker' } } } }