feat: add monitor history 24h table
This commit is contained in:
1 parent
61a59f8a4d
commit
ca4ddeac65
8 files changed
+181
-84
No files matched your search
@@ -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,
|
||||
|
||||
@@ -24,6 +24,11 @@ export async function createTables(env: Bindings): Promise<void> {
|
||||
"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))"
|
||||
|
||||
@@ -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",
|
||||
];
|
||||
|
||||
// 检查每个表是否存在
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<MonitorStatusHistory>();
|
||||
}
|
||||
// 记录监控状态历史
|
||||
// 记录监控状态历史到热表
|
||||
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)
|
||||
|
||||
@@ -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 || [],
|
||||
|
||||
@@ -165,7 +165,7 @@ export async function deleteNotificationTemplate(
|
||||
|
||||
// 通知设置相关服务,获取所有的通知设置
|
||||
export async function getNotificationConfig(
|
||||
db: D1Database,
|
||||
db: D1Database
|
||||
): Promise<models.NotificationConfig> {
|
||||
return await repositories.getNotificationConfig(db);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user