dd/app/Services/Cron/ReportUmami.php
2026-08-13 16:30:49 +08:00

162 lines
5.0 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace App\Services\Cron;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
/**
* 定时获取 Umami 统计信息并发送至 Telegram 机器人
*/
final class ReportUmami
{
private string $baseUrl;
private string $username;
private string $password;
public function __construct()
{
$this->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;
}
}