baseUrl = (string) "https://".config('path.url_umami_base'); $this->username = (string) config('key.umami_user'); $this->password = (string) config('key.umami_pass'); } /** * 定时任务入口方法 */ public function handle(): void { try { // 1. 获取 API 认证 Token $token = $this->getAuthToken(); if (!$token) { TeleSlave::log()->enableDetail(false)->send("⚠️ [Umami 报表失败]: 无法获取 Auth Token,请检查账号密码。"); return; } // 2. 获取网站列表 $websites = $this->getWebsites($token); if (empty($websites)) { TeleSlave::log()->enableDetail(false)->send("ℹ️ [Umami 报表]: 数据库中未找到任何网站。"); return; } // 3. 统计过去 24 小时的数据 $endAt = (int) (microtime(true) * 1000); // 当前时间戳 (毫秒) $startAt = $endAt - (24 * 3600 * 1000); // 24小时前的时间戳 (毫秒) foreach ($websites as $site) { $siteId = $site['id'] ?? ''; $siteName = $site['name'] ?? '未命名站点'; $domain = $site['domain'] ?? ''; if (!$siteId) { continue; } // 获取单个网站的统计指标 $stats = $this->getWebsiteStats($token, $siteId, $startAt, $endAt); if ($stats) { // 对齐结构:直接获取 int 值 $pageviews = $stats['pageviews'] ?? 0; $pageviewsPrev = $stats['comparison']['pageviews'] ?? 0; $visitors = $stats['visitors'] ?? 0; $visits = $stats['visits'] ?? 0; $bounces = $stats['bounces'] ?? 0; // 计算跳出率 $bounceRate = $visits > 0 ? round(($bounces / $visits) * 100, 1) : 0; $msg = "\n\n======= 📊 访客统计:{$domain} ==================="; $msg .= "\n- 今日:{$pageviews}"; $msg .= "\n- 昨日:{$pageviewsPrev}"; } } // 4. 发送日志至 Telegram TeleSlave::log()->enableDetail(false)->send($msg); } catch (\Throwable $e) { TeleSlave::log()->enableDetail(false)->send("❌ [Umami 报表异常]: " . $e->getMessage()); } } /** * 1. 登录 Umami 获取 JWT Token */ private function getAuthToken(): ?string { $response = $this->curlRequest('POST', '/api/auth/login', [ 'username' => $this->username, 'password' => $this->password, ]); return $response['token'] ?? null; } /** * 2. 获取网站列表 */ private function getWebsites(string $token): array { $response = $this->curlRequest('GET', '/api/websites', [], $token); // Umami 2.x API 网站列表格式通常包含在 data 字段中 if (isset($response['data']) && is_array($response['data'])) { return $response['data']; } return is_array($response) ? $response : []; } /** * 3. 获取指定网站的统计数据 */ private function getWebsiteStats(string $token, string $siteId, int $startAt, int $endAt): ?array { $endpoint = sprintf('/api/websites/%s/stats?startAt=%d&endAt=%d', $siteId, $startAt, $endAt); return $this->curlRequest('GET', $endpoint, [], $token); } /** * 通用 cURL 请求工具方法 */ private function curlRequest(string $method, string $path, array $data = [], ?string $token = null): ?array { $ch = curl_init(rtrim($this->baseUrl, '/') . $path); $headers = [ 'Content-Type: application/json', 'Accept: application/json', ]; if ($token) { $headers[] = 'Authorization: Bearer ' . $token; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 200 && $httpCode < 300 && $result) { return json_decode($result, true); } return null; } }