diff --git a/backend/schema.sql b/backend/schema.sql index 27b1672..806df23 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -6,6 +6,8 @@ DROP TABLE IF EXISTS status_page_agents; DROP TABLE IF EXISTS status_page_monitors; DROP TABLE IF EXISTS status_page_config; DROP TABLE IF EXISTS monitor_status_history; +DROP TABLE IF EXISTS monitor_status_history_24h; +DROP TABLE IF EXISTS monitor_daily_stats; DROP TABLE IF EXISTS agents; DROP TABLE IF EXISTS monitors; DROP TABLE IF EXISTS users; @@ -55,6 +57,18 @@ CREATE TABLE IF NOT EXISTS monitor_status_history ( FOREIGN KEY (monitor_id) REFERENCES monitors(id) ); +-- 24小时监控状态历史表(热表) +CREATE TABLE IF NOT EXISTS monitor_status_history_24h ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + status TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + response_time INTEGER, + status_code INTEGER, + error TEXT, + FOREIGN KEY (monitor_id) REFERENCES monitors(id) +); + -- 监控每日统计表 CREATE TABLE IF NOT EXISTS monitor_daily_stats ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/backend/src/initialization/database.ts b/backend/src/initialization/database.ts index 82bd540..d8a41e9 100644 --- a/backend/src/initialization/database.ts +++ b/backend/src/initialization/database.ts @@ -24,6 +24,11 @@ export async function createTables(env: Bindings): Promise { "CREATE TABLE IF NOT EXISTS monitor_status_history (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, response_time INTEGER, status_code INTEGER, error TEXT, FOREIGN KEY (monitor_id) REFERENCES monitors(id))" ); + console.log("创建24小时监控状态历史表(热表)..."); + await env.DB.exec( + "CREATE TABLE IF NOT EXISTS monitor_status_history_24h (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, response_time INTEGER, status_code INTEGER, error TEXT, FOREIGN KEY (monitor_id) REFERENCES monitors(id))" + ); + console.log("创建监控每日统计表..."); await env.DB.exec( "CREATE TABLE IF NOT EXISTS monitor_daily_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, date TEXT NOT NULL, total_checks INTEGER NOT NULL DEFAULT 0, up_checks INTEGER NOT NULL DEFAULT 0, down_checks INTEGER NOT NULL DEFAULT 0, avg_response_time INTEGER DEFAULT 0, min_response_time INTEGER DEFAULT 0, max_response_time INTEGER DEFAULT 0, availability REAL DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (monitor_id) REFERENCES monitors(id))" diff --git a/backend/src/initialization/initCheck.ts b/backend/src/initialization/initCheck.ts index f2124b4..5adcaa2 100644 --- a/backend/src/initialization/initCheck.ts +++ b/backend/src/initialization/initCheck.ts @@ -3,13 +3,7 @@ * 用于应用启动时检测数据库是否为空,如果为空则初始化 */ import { Bindings } from "../models/db"; -import { - createTables, - createAdminUser, - createDefaultStatusPage, - createNotificationTemplates, - createNotificationChannelsAndSettings, -} from "./database"; +import { createTables } from "./database"; import { runMigrations } from "../migrations/migration"; import { cleanupOldRecords } from "../repositories/monitor"; @@ -41,6 +35,8 @@ export async function checkAndInitializeDatabase( "users", "monitors", "monitor_status_history", + "monitor_status_history_24h", + "monitor_daily_stats", "agents", "status_page_config", "status_page_monitors", @@ -49,7 +45,6 @@ export async function checkAndInitializeDatabase( "notification_templates", "notification_settings", "notification_history", - "monitor_daily_stats", ]; // 检查每个表是否存在 diff --git a/backend/src/jobs/monitor-task.ts b/backend/src/jobs/monitor-task.ts index c33a5d2..3e38110 100644 --- a/backend/src/jobs/monitor-task.ts +++ b/backend/src/jobs/monitor-task.ts @@ -163,38 +163,39 @@ async function handleMonitorNotification( async function generateDailyStats(c: any) { try { console.log("开始生成每日监控统计数据..."); - + // 获取前一天的日期 (YYYY-MM-DD 格式) const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); - const dateStr = yesterday.toISOString().split('T')[0]; - + const dateStr = yesterday.toISOString().split("T")[0]; + console.log(`正在处理日期 ${dateStr} 的数据`); - + // 时间范围 const startTime = `${dateStr}T00:00:00.000Z`; const endTime = `${dateStr}T23:59:59.999Z`; - + // 一次性获取所有监控 const monitorsResult = await c.env.DB.prepare( "SELECT id, name FROM monitors" ).all(); - + if (!monitorsResult.results || monitorsResult.results.length === 0) { console.log("没有找到监控"); return { success: true, message: "没有监控", processed: 0 }; } - + const monitors = monitorsResult.results; console.log(`找到 ${monitors.length} 个监控`); - + // 创建监控ID列表 const monitorIds = monitors.map((m: any) => m.id); - + // 一次性获取所有监控历史记录(指定日期范围内) console.log(`一次性查询所有监控在 ${startTime} 至 ${endTime} 的历史记录`); - - const historyResult = await c.env.DB.prepare(` + + const historyResult = await c.env.DB.prepare( + ` SELECT monitor_id, status, @@ -204,20 +205,21 @@ async function generateDailyStats(c: any) { WHERE timestamp >= ? AND timestamp <= ? - `) - .bind(startTime, endTime) - .all(); - + ` + ) + .bind(startTime, endTime) + .all(); + if (!historyResult.results || historyResult.results.length === 0) { console.log(`在 ${dateStr} 没有找到任何监控历史记录`); return { success: true, message: "没有历史记录", processed: 0 }; } - + console.log(`找到 ${historyResult.results.length} 条历史记录`); - + // 按监控ID分组处理数据 const statsMap = new Map(); - + // 初始化每个监控的统计数据结构 for (const monitorId of monitorIds) { statsMap.set(monitorId, { @@ -229,69 +231,81 @@ async function generateDailyStats(c: any) { avgResponseTime: 0, minResponseTime: 0, maxResponseTime: 0, - availability: 0 + availability: 0, }); } - + // 处理所有历史记录 for (const record of historyResult.results) { const monitorId = record.monitor_id; - + // 如果这个监控ID不在我们的列表中,跳过 if (!statsMap.has(monitorId)) continue; - + const stats = statsMap.get(monitorId); stats.totalChecks++; - + // 统计状态 - if (record.status === 'up') { + if (record.status === "up") { stats.upChecks++; - } else if (record.status === 'down') { + } else if (record.status === "down") { stats.downChecks++; } - + // 收集响应时间数据 if (record.response_time != null && record.response_time > 0) { stats.responseTimes.push(record.response_time); } } - + // 处理每个监控的响应时间统计和可用率计算 for (const [monitorId, stats] of statsMap.entries()) { // 只处理有记录的监控 if (stats.totalChecks === 0) continue; - + // 计算响应时间统计数据 if (stats.responseTimes.length > 0) { - stats.avgResponseTime = stats.responseTimes.reduce((sum: number, time: number) => sum + time, 0) / stats.responseTimes.length; + stats.avgResponseTime = + stats.responseTimes.reduce( + (sum: number, time: number) => sum + time, + 0 + ) / stats.responseTimes.length; stats.minResponseTime = Math.min(...stats.responseTimes); stats.maxResponseTime = Math.max(...stats.responseTimes); } - + // 计算可用率 - stats.availability = stats.totalChecks > 0 ? (stats.upChecks / stats.totalChecks) * 100 : 0; - + stats.availability = + stats.totalChecks > 0 ? (stats.upChecks / stats.totalChecks) * 100 : 0; + // 删除临时数据 delete stats.responseTimes; } - + // 将数据写入数据库 const now = new Date().toISOString(); let processed = 0; - + // 批量构建 INSERT 语句 for (const [monitorId, stats] of statsMap.entries()) { // 跳过没有记录的监控 if (stats.totalChecks === 0) continue; - + const monitor = monitors.find((m: any) => m.id === monitorId); const monitorName = monitor ? monitor.name : `ID: ${monitorId}`; - + try { - console.log(`监控 ${monitorName} (ID: ${monitorId}) 数据: 总检查=${stats.totalChecks}, 正常=${stats.upChecks}, 故障=${stats.downChecks}, 可用率=${stats.availability.toFixed(2)}%`); - + console.log( + `监控 ${monitorName} (ID: ${monitorId}) 数据: 总检查=${ + stats.totalChecks + }, 正常=${stats.upChecks}, 故障=${ + stats.downChecks + }, 可用率=${stats.availability.toFixed(2)}%` + ); + // 使用 UPSERT 操作 - await c.env.DB.prepare(` + await c.env.DB.prepare( + ` INSERT INTO monitor_daily_stats ( monitor_id, date, @@ -312,61 +326,129 @@ async function generateDailyStats(c: any) { min_response_time = excluded.min_response_time, max_response_time = excluded.max_response_time, availability = excluded.availability - `).bind( - monitorId, - dateStr, - stats.totalChecks, - stats.upChecks, - stats.downChecks, - stats.avgResponseTime, - stats.minResponseTime, - stats.maxResponseTime, - stats.availability, - now - ).run(); - + ` + ) + .bind( + monitorId, + dateStr, + stats.totalChecks, + stats.upChecks, + stats.downChecks, + stats.avgResponseTime, + stats.minResponseTime, + stats.maxResponseTime, + stats.availability, + now + ) + .run(); + processed++; console.log(`成功更新监控 ID ${monitorId} 的每日统计数据`); } catch (error) { console.error(`更新监控 ID ${monitorId} 的每日统计数据时出错:`, error); } } - + console.log(`每日统计数据生成完成,成功处理了 ${processed} 个监控`); - + return { success: true, message: "每日统计数据生成完成", processed: processed, - date: dateStr + date: dateStr, }; } catch (error) { console.error("生成每日统计数据时出错:", error); - return { - success: false, - message: "生成每日统计数据时出错", - error: String(error) + return { + success: false, + message: "生成每日统计数据时出错", + error: String(error), }; } } +// 迁移24小时以前的监控历史数据到冷表 +async function migrateMonitorHistoryData(c: any) { + try { + console.log("开始迁移24小时以前的监控历史数据..."); + const oneDayAgo = new Date(); + oneDayAgo.setHours(oneDayAgo.getHours() - 24); + const timestamp = oneDayAgo.toISOString(); + + // 获取24小时前的监控历史数据 + const historyResult = await c.env.DB.prepare( + ` + SELECT * FROM monitor_status_history_24h + WHERE timestamp < ? + ` + ) + .bind(timestamp) + .all(); + + console.log( + `找到 ${historyResult.results.length} 条24小时前的监控历史数据` + ); + + // 将数据迁移到冷表 + await c.env.DB.prepare( + ` + INSERT INTO monitor_status_history ( + monitor_id, + status, + timestamp, + response_time, + status_code, + error + ) VALUES (?, ?, ?, ?, ?, ?) + ` + ) + .bind( + historyResult.results.map((record: any) => [ + record.monitor_id, + record.status, + record.timestamp, + record.response_time, + record.status_code, + record.error, + ]) + ) + .run(); + + // 删除热表中的数据 + await c.env.DB.prepare( + ` + DELETE FROM monitor_status_history_24h + WHERE timestamp < ? + ` + ) + .bind(timestamp) + .run(); + + console.log("迁移完成,成功迁移了 ${historyResult.results.length} 条数据"); + } catch (error) { + console.error("迁移24小时以前的监控历史数据时出错:", error); + } +} // 在 Cloudflare Workers 中设置定时触发器 export default { async scheduled(event: any, env: any, ctx: any) { const c = { env }; - + // 默认执行监控检查任务 let result: any = await checkMonitors(c); const now = new Date(); const hour = now.getUTCHours(); const minute = now.getUTCMinutes(); - + if (hour === 0 && minute === 5) { + // 迁移24小时以前的监控历史数据到冷表 + await migrateMonitorHistoryData(c); + // 生成每日监控统计数据 const statsResult = await generateDailyStats(c); result = { monitorCheck: result, dailyStats: statsResult }; } - + return result; }, fetch: monitorTask.fetch, diff --git a/backend/src/repositories/monitor.ts b/backend/src/repositories/monitor.ts index 34d4888..c86c7b1 100644 --- a/backend/src/repositories/monitor.ts +++ b/backend/src/repositories/monitor.ts @@ -138,15 +138,14 @@ export async function getMonitorsByIds( } // 获取单个监控状态历史 24小时内 -export async function getMonitorStatusHistory( +export async function getMonitorStatusHistoryIn24h( db: Bindings["DB"], monitorId: number ) { return await db .prepare( - `SELECT * FROM monitor_status_history + `SELECT * FROM monitor_status_history_24h WHERE monitor_id = ? - AND timestamp > datetime('now', '-24 hours') ORDER BY timestamp ASC` ) .bind(monitorId) @@ -154,16 +153,15 @@ export async function getMonitorStatusHistory( } // 获取所有监控状态历史 24小时内 -export async function getAllMonitorStatusHistory(db: Bindings["DB"]) { +export async function getAllMonitorStatusHistoryIn24h(db: Bindings["DB"]) { return await db .prepare( - `SELECT * FROM monitor_status_history - WHERE timestamp > datetime('now', '-24 hours') + `SELECT * FROM monitor_status_history_24h ORDER BY timestamp ASC` ) .all(); } -// 记录监控状态历史 +// 记录监控状态历史到热表 export async function insertMonitorStatusHistory( db: Bindings["DB"], monitorId: number, @@ -177,7 +175,7 @@ export async function insertMonitorStatusHistory( return await db .prepare( - `INSERT INTO monitor_status_history (monitor_id, status, timestamp, response_time, status_code, error) + `INSERT INTO monitor_status_history_24h (monitor_id, status, timestamp, response_time, status_code, error) VALUES (?, ?, ?, ?, ?, ?)` ) .bind(monitorId, status, now, response_time, status_code, error) diff --git a/backend/src/services/MonitorService.ts b/backend/src/services/MonitorService.ts index 9b8035f..a408f9e 100644 --- a/backend/src/services/MonitorService.ts +++ b/backend/src/services/MonitorService.ts @@ -148,7 +148,7 @@ export async function getMonitorDetails(db: Bindings["DB"], id: number) { } // 获取历史状态数据 - const historyResult = await repositories.getMonitorStatusHistory(db, id); + const historyResult = await repositories.getMonitorStatusHistoryIn24h(db, id); return { ...monitor, @@ -198,7 +198,7 @@ export async function getMonitorById(db: Bindings["DB"], id: number) { } // 获取历史状态数据 - const historyResult = await repositories.getMonitorStatusHistory(db, id); + const historyResult = await repositories.getMonitorStatusHistoryIn24h(db, id); return { success: true, @@ -392,7 +392,10 @@ export async function getMonitorStatusHistoryById( } // 获取历史状态 - const historyResult = await repositories.getMonitorStatusHistory(db, id); + const historyResult = await repositories.getMonitorStatusHistoryIn24h( + db, + id + ); return { success: true, @@ -417,7 +420,7 @@ export async function getMonitorStatusHistoryById( * @returns 所有监控状态历史 */ export async function getAllMonitorStatusHistory(db: Bindings["DB"]) { - const result = await repositories.getAllMonitorStatusHistory(db); + const result = await repositories.getAllMonitorStatusHistoryIn24h(db); return { success: true, history: result.results || [], diff --git a/backend/src/services/NotificationService.ts b/backend/src/services/NotificationService.ts index 1ba4d6f..3761035 100644 --- a/backend/src/services/NotificationService.ts +++ b/backend/src/services/NotificationService.ts @@ -165,7 +165,7 @@ export async function deleteNotificationTemplate( // 通知设置相关服务,获取所有的通知设置 export async function getNotificationConfig( - db: D1Database, + db: D1Database ): Promise { return await repositories.getNotificationConfig(db); } diff --git a/backend/src/services/StatusService.ts b/backend/src/services/StatusService.ts index 4f81e07..3770d91 100644 --- a/backend/src/services/StatusService.ts +++ b/backend/src/services/StatusService.ts @@ -227,7 +227,7 @@ export async function getStatusPagePublicData(env: { DB: Bindings["DB"] }) { env.DB, monitorId ); - const monitorHistory = await repositories.getMonitorStatusHistory( + const monitorHistory = await repositories.getMonitorStatusHistoryIn24h( env.DB, monitorId );