文章摘要
GPT 4
此内容根据文章生成,并经过人工审核,仅用于文章内容的解释与总结
投诉

搭建umami

参考:使用Docker搭建Umami统计,显示近一年的pv、uv数据的API搭建

搭建api

获取Token

点击hoppscotch获取token,如图:

post

1处请求地址填写https://[你的 Umami 部署地址]/api/auth/login

2处请求参数:(Json 格式)

1
2
3
4
{
"username":"[你的 Umami 账户名]",
"password":"[你的 Umami 账户密码]"
}

获取成功会在 Response 中返回一个 Token,请妥善保管。

创建PHP 脚本

根目录创建/hot/index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");

// 配置 Umami API 的凭据
$apiConfig = [
'baseUrl' => '[你的 Umami 部署地址,如 https://umami.haiskyblog.top]',
'token' => '[你的 Umami Token]',
'websiteId' => '[你的 Umami 网站 ID]'
];
$cacheConfig = [
'file' => 'umami_cache.json',
'time' => 600 // 缓存时间为10分钟(600秒)
];
$path = '/p/'; // 获取文章
$site = '[你的网站地址,如 https://blog.everfu.cn]';

// 获取当前时间戳(毫秒级)
$currentTimestamp = time() * 1000;

// Umami API 的起始时间戳(毫秒级)
$startTimestamps = [
'today' => strtotime("today") * 1000,
'yesterday' => strtotime("yesterday") * 1000,
'lastMonth' => strtotime("-1 month") * 1000,
'lastYear' => strtotime("-1 year") * 1000
];

// 定义 Umami API 请求函数
function fetchUmamiData($config, $startAt, $endAt, $type = 'url', $limit = 500) {
$url = "{$config['baseUrl']}/api/websites/{$config['websiteId']}/metrics?" . http_build_query([
'startAt' => $startAt,
'endAt' => $endAt,
'type' => $type,
'limit' => $limit
]);
$options = [
'http' => [
'method' => 'GET',
'header' => [
"Authorization: Bearer {$config['token']}",
"Content-Type: application/json"
]
]
];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);

if ($response === FALSE) {
$error = error_get_last();
echo json_encode(["error" => "Error fetching data: " . $error['message'], "url" => $url]);
return null;
}

global $path;
return array_values(array_filter(json_decode($response, true), fn($item) => strpos($item['x'], $path) === 0));
}

// 检查缓存文件是否存在且未过期
function isCacheValid($cacheConfig) {
return file_exists($cacheConfig['file']) && (time() - filemtime($cacheConfig['file']) < $cacheConfig['time']);
}

// 从缓存中读取数据
function readCache($cacheConfig) {
return file_get_contents($cacheConfig['file']);
}

// 将数据写入缓存
function writeCache($cacheConfig, $data) {
file_put_contents($cacheConfig['file'], json_encode($data));
}

// 获取并处理数据
function getProcessedData($config, $startTimestamps, $currentTimestamp) {
$data = fetchUmamiData($config, $startTimestamps['today'], $currentTimestamp);
if (!$data) return [];

$responseData = array_map(fn($item) => ["url" => $item['x'], "pv" => $item['y']], $data);
usort($responseData, fn($a, $b) => $b['pv'] <=> $a['pv']);
global $site;

$processedData = [];
foreach (array_slice($responseData, 0, 5) as $item) {
$url = $item['url'];
$title = fetchPageTitle($site . $url);
$processedData[] = ["url" => $url, "title" => $title];
}

return $processedData;
}

function fetchPageTitle($url) {
$html = @file_get_contents($url);
if ($html === FALSE) {
return "Unknown Title";
}

preg_match("/<h1 class=\"post-title\">(.*?)<\/h1>/is", $html, $matches);
return $matches[1] ?? "Unknown Title";
}

// 主逻辑
if (isCacheValid($cacheConfig)) {
echo readCache($cacheConfig);
} else {
$responseData = getProcessedData($apiConfig, $startTimestamps, $currentTimestamp);
writeCache($cacheConfig, $responseData);
echo json_encode($responseData);
}
?>

会自行创建一个 umami_cache.json 文件(权限 755,与 PHP 脚本文件同级),用于缓存数据。

插入hot卡片

在aside.yaml中添加:

1
2
3
4
5
- name: hot
title: 今日热门
class: card-hotpost
icon: fas fa-fire
content_id: card-hotpost

创建hot.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
document.addEventListener("DOMContentLoaded", () => {
initializeHot();
});

document.addEventListener("pjax:complete", () => {
initializeHot();
});

function initializeHot() {
const url = "[你的 PHP 脚本地址,如 https://api.haiskyblog.top/hot/]";
const e = document.getElementById("card-hotpost");
if (e) {
fetch(url)
.then(response => response.json())
.then(data => {
let rank = 0;
data.forEach(item => {
rank++;
const hotItem = document.createElement("a");
hotItem.classList.add("hot-post-link");
hotItem.setAttribute("href", item.url);
hotItem.innerHTML = `<span class="post-rank rank-${rank == 1 ? "1" : "2"}">${rank}</span><div class="post-title-container"><span class="post-title">${item.title}</span></div>`;
e.appendChild(hotItem);
});
});
}
}

创建hot.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#card-hotpost {
display: flex;
flex-direction: column;
gap: 4px
}

#card-hotpost .post-rank {
background: var(--efu-secondbg);
border: var(--style-border-always);
color: var(--efu-fontcolor);
border-radius: 8px;
margin-right: 4px;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
margin-top: 2px
}

#card-hotpost .post-rank.rank-1 {
background: var(--efu-lighttext);
color: var(--efu-card-bg);
border-color: var(--efu-lighttext)
}

.hot-post-link {
display: flex;
font-size: 15px;
overflow: hidden;
padding: .3rem;
gap: 4px;
border-radius: 12px
}

.hot-post-link .post-title-container {
display: flex;
align-items: center;
flex: 1
}

.hot-post-link .post-title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-height: 1.38;
flex: 1;
overflow: hidden;
padding-top: 2px
}

.hot-post-link:hover {
background: var(--efu-lighttext);
border-radius: 8px;
color: var(--efu-card-bg)
}

#card-hotpost .hot-post-link:hover .post-rank {
background: var(--efu-card-bg);
color: var(--efu-fontcolor);
border-color: var(--efu-card-bg)
}

#card-hotpost .hot-post-link:hover .post-rank.rank-1 {
background: var(--efu-maskbg);
color: var(--efu-white);
border-color: var(--efu-maskbg)
}

在主题配置文件中,找到 aside 字段,在需要显示的页面增加hot

1
2
3
4
aside:
home: # on the homepage
noSticky: "about,flip,calendar,schedule"
Sticky: "hot, newestPost,allInfo"