From c4d673dfe4908c7ea546c9ea06cedc732be93ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E6=9F=A0?= <2338174386@qq.com> Date: Tue, 20 Jan 2026 20:46:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AliyunTrafficCheck.php | 56 ++++++++------ Database.php | 123 ++++++++---------------------- index.php | 18 ++++- template.html | 167 +++++++++++++++++++++++++---------------- 4 files changed, 180 insertions(+), 184 deletions(-) diff --git a/AliyunTrafficCheck.php b/AliyunTrafficCheck.php index 1844a31..929e9b8 100644 --- a/AliyunTrafficCheck.php +++ b/AliyunTrafficCheck.php @@ -139,17 +139,40 @@ class AliyunTrafficCheck return $config; } - public function getSystemLogs() + // --- 修改:支持按 Tab 获取日志 --- + public function getSystemLogs($tab = 'action') { if ($this->initError) return []; - $logs = $this->db->getLogs(50); + + if ($tab === 'heartbeat') { + // 心跳日志:只看 heartbeat 类型 + $types = ['heartbeat']; + } else { + // 动作日志:只看 info 和 warning,排除 error (超时/接口错误) + $types = ['info', 'warning']; + } + + // 仅返回最近 20 条 + $logs = $this->db->getLogsByTypes($types, 20); + foreach ($logs as &$log) { $log['time_str'] = date('Y-m-d H:i:s', $log['created_at']); } return $logs; } - // --- 修改:获取流量历史数据(适配新表结构) --- + // --- 新增:清空日志 --- + public function clearSystemLogs($tab = 'action') + { + if ($this->initError) return false; + + if ($tab === 'heartbeat') { + return $this->db->clearLogsByTypes(['heartbeat']); + } else { + return $this->db->clearLogsByTypes(['info', 'warning', 'error']); // 清空动作日志时连带Error一起清空,保持干净 + } + } + public function getAccountHistory($id) { if ($this->initError) return []; @@ -159,7 +182,6 @@ class AliyunTrafficCheck $ak = $account['access_key_id']; - // 1. 获取最近24小时数据 (Hourly) $rawHourly = $this->db->getHourlyStats($ak); $chartHourly = []; foreach ($rawHourly as $row) { @@ -170,7 +192,6 @@ class AliyunTrafficCheck ]; } - // 2. 获取最近30天数据 (Daily) $rawDaily = $this->db->getDailyStats($ak); $chartDaily = []; foreach ($rawDaily as $row) { @@ -193,7 +214,7 @@ class AliyunTrafficCheck if ($this->initError) return "Error: " . $this->initError; $this->db->pruneLogs(30); - $this->db->pruneStats(); // 清理旧的统计数据 + $this->db->pruneStats(); $logs = []; $currentUserTime = date('H:i'); @@ -249,14 +270,6 @@ class AliyunTrafficCheck $shouldCheckApi = $forceRefresh || (($currentTime - $lastUpdate) > $currentInterval); - // 特殊检查:是否需要记录统计数据? - // 策略:Monitor每分钟运行,尝试插入当前整点和当前0点的数据。 - // 由于数据库有 UNIQUE 索引 + INSERT OR IGNORE,只有每小时/每天第一次尝试会成功。 - // 因此,我们不需要复杂的“是否已记录”判断,直接尝试记录即可。 - // 但为了减少API调用,我们仅在“应该检查API”或者“尚未记录当前小时/天数据”时才调用API? - // 简单起见,利用现有的 $shouldCheckApi 逻辑。 - // 如果为了保证整点记录的及时性,我们应该每分钟都检查一下是否到了整点? - // 优化:如果当前分钟是 00 (整点),则强制刷新一次,确保整点数据最准确。 if (date('i') === '00') { $shouldCheckApi = true; } @@ -264,7 +277,6 @@ class AliyunTrafficCheck $newUpdateTime = $currentTime; if ($shouldCheckApi) { - // 使用 Safe 方法 $newTraffic = $this->safeGetTraffic($account); $status = $this->safeGetInstanceStatus($account); @@ -281,10 +293,7 @@ class AliyunTrafficCheck $traffic = $newTraffic; $apiStatusLog = "已更新"; - // --- 修改:记录统计数据 --- - // 尝试记录小时数据 (利用DB唯一性去重) $this->db->addHourlyStat($account['access_key_id'], $traffic); - // 尝试记录天数据 (利用DB唯一性去重) $this->db->addDailyStat($account['access_key_id'], $traffic); } @@ -365,7 +374,11 @@ class AliyunTrafficCheck } $actionLog = empty($actions) ? "无动作" : implode(", ", $actions); - $logs[] = sprintf("%s %s | %s | %s | %s", $logPrefix, $actionLog, $trafficDesc, $status, $apiStatusLog); + $logLine = sprintf("%s %s | %s | %s | %s", $logPrefix, $actionLog, $trafficDesc, $status, $apiStatusLog); + + // --- 修改:将心跳日志写入数据库 --- + $this->db->addLog('heartbeat', $logLine); + $logs[] = $logLine; } $this->configManager->updateLastRunTime(time()); @@ -393,7 +406,6 @@ class AliyunTrafficCheck $checkInterval = $isTransientState ? 60 : $userInterval; if (($currentTime - $lastUpdate) > $checkInterval) { - // 使用 Safe 方法 $newTraffic = $this->safeGetTraffic($account); $status = $this->safeGetInstanceStatus($account); @@ -407,7 +419,6 @@ class AliyunTrafficCheck $newUpdateTime = $lastUpdate; } else { $traffic = $newTraffic; - // --- 修改:同时尝试记录统计数据 --- $this->db->addHourlyStat($account['access_key_id'], $traffic); $this->db->addDailyStat($account['access_key_id'], $traffic); } @@ -460,7 +471,6 @@ class AliyunTrafficCheck if ($traffic < 0) { $traffic = $targetAccount['traffic_used']; } else { - // 手动刷新也尝试记录统计数据 $this->db->addHourlyStat($targetAccount['access_key_id'], $traffic); $this->db->addDailyStat($targetAccount['access_key_id'], $traffic); } @@ -571,4 +581,4 @@ class AliyunTrafficCheck include 'template.html'; return ob_get_clean(); } -} +} \ No newline at end of file diff --git a/Database.php b/Database.php index b6ed3b1..0b9717b 100644 --- a/Database.php +++ b/Database.php @@ -101,8 +101,6 @@ class Database attempt_time INTEGER )"); - // --- 新增:独立的小时级和天级流量表 --- - // 1. 小时级表 (24小时折线图) $this->pdo->exec("CREATE TABLE IF NOT EXISTS traffic_hourly ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -110,7 +108,6 @@ class Database traffic REAL, recorded_at INTEGER )"); - // 唯一索引:确保每个 AK 在每个小时(时间戳归一化后)只有一条记录 $this->pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_traffic_hourly_unique ON traffic_hourly (access_key_id, recorded_at)"); // 2. 天级表 (30天柱状图) @@ -120,7 +117,6 @@ class Database traffic REAL, recorded_at INTEGER )"); - // 唯一索引:确保每个 AK 在每天(时间戳归一化后)只有一条记录 $this->pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_traffic_daily_unique ON traffic_daily (access_key_id, recorded_at)"); $this->ensureColumn('accounts', 'traffic_used', 'REAL DEFAULT 0'); @@ -156,6 +152,30 @@ class Database return $stmt->fetchAll(PDO::FETCH_ASSOC); } + // --- 新增:按类型获取日志 --- + public function getLogsByTypes(array $types, $limit = 20) + { + // 动态构建 IN 查询占位符 + $placeholders = implode(',', array_fill(0, count($types), '?')); + $sql = "SELECT * FROM logs WHERE type IN ($placeholders) ORDER BY id DESC LIMIT ?"; + + // 合并参数 + $params = $types; + $params[] = $limit; + + $stmt = $this->pdo->prepare($sql); + $stmt->execute($params); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + // --- 新增:按类型删除日志 --- + public function clearLogsByTypes(array $types) + { + $placeholders = implode(',', array_fill(0, count($types), '?')); + $stmt = $this->pdo->prepare("DELETE FROM logs WHERE type IN ($placeholders)"); + return $stmt->execute($types); + } + public function pruneLogs($days = 30) { $stmt = $this->pdo->prepare("DELETE FROM logs WHERE created_at < ?"); @@ -183,54 +203,33 @@ class Database $stmt->execute([$ip]); } - // --- 新的流量记录逻辑 --- + // --- 流量记录逻辑 --- - /** - * 记录小时级数据 - * 利用 UNIQUE INDEX 和 INSERT OR IGNORE 实现“每小时只记一条” - */ public function addHourlyStat($accessKeyId, $traffic) { - // 归一化到当前小时的整点 (例如 10:23 -> 10:00) $hourTimestamp = floor(time() / 3600) * 3600; - - $stmt = $this->pdo->prepare("INSERT OR IGNORE INTO traffic_hourly (access_key_id, traffic, recorded_at) VALUES (?, ?, ?)"); + // 修改为 INSERT OR REPLACE,实现当前小时流量实时更新 + $stmt = $this->pdo->prepare("INSERT OR REPLACE INTO traffic_hourly (access_key_id, traffic, recorded_at) VALUES (?, ?, ?)"); $stmt->execute([$accessKeyId, $traffic, $hourTimestamp]); } - /** - * 记录天级数据 - * 利用 UNIQUE INDEX 和 INSERT OR IGNORE 实现“每天只记一条” - */ public function addDailyStat($accessKeyId, $traffic) { - // 归一化到当天的 00:00 + // 归一化到当天 00:00:00 $dayTimestamp = strtotime(date('Y-m-d 00:00:00')); - - $stmt = $this->pdo->prepare("INSERT OR IGNORE INTO traffic_daily (access_key_id, traffic, recorded_at) VALUES (?, ?, ?)"); + // 修改为 INSERT OR REPLACE,实现当日流量实时更新,直到第二天0点生成新条目 + $stmt = $this->pdo->prepare("INSERT OR REPLACE INTO traffic_daily (access_key_id, traffic, recorded_at) VALUES (?, ?, ?)"); $stmt->execute([$accessKeyId, $traffic, $dayTimestamp]); } - /** - * 获取最近 24 小时的数据 - */ public function getHourlyStats($accessKeyId) { - // 获取最近 25 条,保证覆盖24小时 $stmt = $this->pdo->prepare("SELECT traffic, recorded_at FROM traffic_hourly WHERE access_key_id = ? ORDER BY recorded_at DESC LIMIT 25"); $stmt->execute([$accessKeyId]); $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - // 按时间正序排列返回 return array_reverse($data); -<<<<<<< Updated upstream -<<<<<<< Updated upstream -======= -======= } - /** - * 获取最近 30 天的数据 - */ public function getDailyStats($accessKeyId) { $stmt = $this->pdo->prepare("SELECT traffic, recorded_at FROM traffic_daily WHERE access_key_id = ? ORDER BY recorded_at DESC LIMIT 31"); @@ -239,72 +238,12 @@ class Database return array_reverse($data); } - /** - * 清理过期统计数据 - */ public function pruneStats() { - // 1. 清理小时表:保留最近 24+2 小时以外的数据 - // 既然我们只取 Limit 24,其实可以删掉 48 小时前的 $hourLimit = time() - (48 * 3600); $this->pdo->exec("DELETE FROM traffic_hourly WHERE recorded_at < $hourLimit"); - // 2. 清理天表:保留最近 60 天以外的 (留点余量) - $dayLimit = time() - (60 * 86400); - $this->pdo->exec("DELETE FROM traffic_daily WHERE recorded_at < $dayLimit"); ->>>>>>> Stashed changes - } - - /** - * 获取最近 30 天的数据 - */ - public function getDailyStats($accessKeyId) - { - $stmt = $this->pdo->prepare("SELECT traffic, recorded_at FROM traffic_daily WHERE access_key_id = ? ORDER BY recorded_at DESC LIMIT 31"); - $stmt->execute([$accessKeyId]); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - return array_reverse($data); - } - - /** - * 清理过期统计数据 - */ - public function pruneStats() - { - // 1. 清理小时表:保留最近 24+2 小时以外的数据 - // 既然我们只取 Limit 24,其实可以删掉 48 小时前的 - $hourLimit = time() - (48 * 3600); - $this->pdo->exec("DELETE FROM traffic_hourly WHERE recorded_at < $hourLimit"); - - // 2. 清理天表:保留最近 60 天以外的 (留点余量) - $dayLimit = time() - (60 * 86400); - $this->pdo->exec("DELETE FROM traffic_daily WHERE recorded_at < $dayLimit"); ->>>>>>> Stashed changes - } - - /** - * 获取最近 30 天的数据 - */ - public function getDailyStats($accessKeyId) - { - $stmt = $this->pdo->prepare("SELECT traffic, recorded_at FROM traffic_daily WHERE access_key_id = ? ORDER BY recorded_at DESC LIMIT 31"); - $stmt->execute([$accessKeyId]); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - return array_reverse($data); - } - - /** - * 清理过期统计数据 - */ - public function pruneStats() - { - // 1. 清理小时表:保留最近 24+2 小时以外的数据 - // 既然我们只取 Limit 24,其实可以删掉 48 小时前的 - $hourLimit = time() - (48 * 3600); - $this->pdo->exec("DELETE FROM traffic_hourly WHERE recorded_at < $hourLimit"); - - // 2. 清理天表:保留最近 60 天以外的 (留点余量) $dayLimit = time() - (60 * 86400); $this->pdo->exec("DELETE FROM traffic_daily WHERE recorded_at < $dayLimit"); } -} +} \ No newline at end of file diff --git a/index.php b/index.php index 8a479f1..159c6f6 100644 --- a/index.php +++ b/index.php @@ -118,14 +118,26 @@ if ($action === 'refresh_account') { exit; } -// 获取系统日志 +// 修改:获取系统日志,支持 Tab if ($action === 'get_logs') { header('Content-Type: application/json; charset=utf-8'); - echo json_encode(['data' => $app->getSystemLogs()]); + $tab = $_GET['tab'] ?? 'action'; // 默认是动作日志 + echo json_encode(['data' => $app->getSystemLogs($tab)]); + exit; +} + +// 新增:清空日志 +if ($action === 'clear_logs') { + $data = json_decode(file_get_contents('php://input'), true); + $tab = $data['tab'] ?? 'action'; + if ($app->clearSystemLogs($tab)) { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'message' => 'Clear failed']); + } exit; } -// 新增:获取流量历史 if ($action === 'get_history') { header('Content-Type: application/json; charset=utf-8'); $id = $_GET['id'] ?? 0; diff --git a/template.html b/template.html index f781aaf..9690203 100644 --- a/template.html +++ b/template.html @@ -93,7 +93,6 @@
-
@@ -127,7 +126,6 @@
- -
-
-

流量统计

账号: {{ currentChartAccount }}

-
-
-

暂无统计数据 (等待整点更新)

@@ -237,7 +229,6 @@
-
@@ -258,7 +249,6 @@
-
@@ -269,9 +259,7 @@
-
-

欢迎使用 CDT Monitor

@@ -301,7 +289,6 @@
-

系统配置

@@ -309,9 +296,7 @@
-
-

全局设置

@@ -324,7 +309,6 @@
-
-
-
@@ -408,7 +389,6 @@
-

邮件服务器配置

@@ -452,7 +432,6 @@
-
@@ -536,26 +515,49 @@
-
-

- 系统执行日志 - -

-
+
+
+

系统日志

+
+ + +
+
+
+
+ + + + + LIVE +
+ +
+
+ +
- + - - - + + + - - - - + + + - +
时间类型内容时间类型内容
{{ log.time_str }} - +
{{ log.time_str }} + {{ log.message }} + {{ log.message }} +
暂无日志记录 + 暂无{{ currentLogTab === 'action' ? '动作' : '心跳' }}记录 +
@@ -580,7 +587,7 @@ - + \ No newline at end of file