Files
system-monitor/backend/utils/privnet-scheduler.ts
2025-12-28 12:03:48 +09:00

222 lines
5.3 KiB
TypeScript

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<typeof setTimeout> | 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<void> {
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<CheckResult> {
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()