162 lines
5.0 KiB
PHP
Executable File
162 lines
5.0 KiB
PHP
Executable File
<?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;
|
||
}
|
||
}
|