import { getDb } from './db' // 상수 정의 const INTERVAL_SUCCESS = 5 * 60 * 1000 // 5분 const INTERVAL_FAILURE = 1 * 60 * 1000 // 1분 const REQUEST_TIMEOUT = 10 * 1000 // 10초 // 타입 정의 interface PrivnetTarget { id: number name: string url: string is_active: number } interface PrivnetStatus { id: number current_index: number check_interval: number is_healthy: number last_target_id: number | null last_checked_at: string | null scheduler_running: number } interface CheckResult { targetId: number targetName: string url: string isSuccess: boolean } class PrivnetScheduler { private timer: ReturnType | null = null private isRunning: boolean = false /** * 스케줄러 시작 */ start(): void { if (this.isRunning) { console.log('[PrivnetScheduler] Already running') return } this.isRunning = true this.updateSchedulerRunning(1) console.log('[PrivnetScheduler] Started') // 즉시 첫 체크 실행 this.runCheck() } /** * 스케줄러 중지 */ stop(): void { if (this.timer) { clearTimeout(this.timer) this.timer = null } this.isRunning = false this.updateSchedulerRunning(0) console.log('[PrivnetScheduler] Stopped') } /** * 실행 상태 확인 */ getIsRunning(): boolean { return this.isRunning } /** * 체크 실행 */ private async runCheck(): Promise { if (!this.isRunning) return try { const result = await this.checkCurrentTarget() // 결과에 따라 다음 간격 결정 const nextInterval = result.isSuccess ? INTERVAL_SUCCESS : INTERVAL_FAILURE // 로그 저장 this.saveLog(result) // 상태 업데이트 (인덱스 증가) this.updateStatus(result, nextInterval) console.log( `[PrivnetScheduler] ${result.targetName} (${result.url}) - ` + `${result.isSuccess ? 'SUCCESS' : 'FAILED'} - ` + `Next check in ${nextInterval / 1000}s` ) // 다음 체크 예약 this.timer = setTimeout(() => this.runCheck(), nextInterval) } catch (error) { console.error('[PrivnetScheduler] Error:', error) // 에러 발생 시 1분 후 재시도 this.timer = setTimeout(() => this.runCheck(), INTERVAL_FAILURE) } } /** * 현재 타겟 URL 체크 */ private async checkCurrentTarget(): Promise { const db = getDb() // 활성화된 타겟 목록 조회 const targets = db.prepare(` SELECT * FROM privnet_targets WHERE is_active = 1 ORDER BY id ASC `).all() as PrivnetTarget[] if (targets.length === 0) { throw new Error('No active targets found') } // 현재 인덱스 조회 const status = db.prepare('SELECT * FROM privnet_status WHERE id = 1').get() as PrivnetStatus const currentIndex = status.current_index % targets.length const target = targets[currentIndex] // HTTP 요청 실행 let isSuccess = false try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT) const response = await fetch(target.url, { method: 'HEAD', signal: controller.signal, headers: { 'User-Agent': 'OSOLIT-Monitor/1.0' } }) clearTimeout(timeoutId) isSuccess = response.status === 200 } catch (err: any) { isSuccess = false } return { targetId: target.id, targetName: target.name, url: target.url, isSuccess } } /** * 체크 결과 로그 저장 */ private saveLog(result: CheckResult): void { const db = getDb() db.prepare(` INSERT INTO privnet_logs (target_id, is_success) VALUES (@targetId, @isSuccess) `).run({ targetId: result.targetId, isSuccess: result.isSuccess ? 1 : 0 }) } /** * 상태 업데이트 (인덱스 순환) */ private updateStatus(result: CheckResult, nextInterval: number): void { const db = getDb() // 활성 타겟 수 조회 const countResult = db.prepare(` SELECT COUNT(*) as cnt FROM privnet_targets WHERE is_active = 1 `).get() as { cnt: number } const status = db.prepare('SELECT current_index FROM privnet_status WHERE id = 1').get() as { current_index: number } const nextIndex = (status.current_index + 1) % countResult.cnt db.prepare(` UPDATE privnet_status SET current_index = @nextIndex, check_interval = @checkInterval, is_healthy = @isHealthy, last_target_id = @lastTargetId, last_checked_at = datetime('now', 'localtime'), updated_at = datetime('now', 'localtime') WHERE id = 1 `).run({ nextIndex, checkInterval: nextInterval, isHealthy: result.isSuccess ? 1 : 0, lastTargetId: result.targetId }) } /** * 스케줄러 실행 상태 업데이트 */ private updateSchedulerRunning(running: number): void { const db = getDb() db.prepare(` UPDATE privnet_status SET scheduler_running = @running, updated_at = datetime('now', 'localtime') WHERE id = 1 `).run({ running }) } } // 싱글톤 인스턴스 export const privnetScheduler = new PrivnetScheduler()