将 Hexo Fluid 的 UV/PV 统计数据迁移到 Supabase
LeanCloud 将于 2027 年 1 月 12 日起正式停止服务,Hexo Fluid 支持的平台还有不蒜子和 Umami, 但是前者不支持初始化数据,只能从 0 开始,后者的话我没有自己的服务器😭,所以最后准备迁移到 LeanCloud 相接近的 Supabase.
1. 注册一个 Supabase 账号
记录下:
- Project 的 URL (Settings → Data API → URL)
anon_key(Settings → API Keys → Legacy anon, … API keys)
2. 建立数据库
在 SQL Editor 里运行:
1 | |
Supabase 默认时区是 UTC+0, 你可以修改为东八区:
1alter database postgres set timezone to 'Asia/Shanghai';
3. 复制一份主题文件
把 node_modules/hexo-theme-fluid 复制到 themes/hexo-theme-fluid 下,_config.fluid.yml 也复制一份,命名为 _config.hexo-theme-fluid.yml;
把 _config.yml 中的 theme 改为 hexo-theme-fluid;
把 _config.hexo-theme-fluid.yml 中 web_analytics.leancloud 的 server_url 和 app_key 分别修改为第 1 步记录的 URL 和 anon_key;
找到 hexo-theme-fluid/source/js/leancloud.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
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
112
113
114/* global CONFIG */
// eslint-disable-next-line no-console
(function(window, document) {
var supabaseUrl = CONFIG.web_analytics.leancloud.server_url;
var supabaseKey = CONFIG.web_analytics.leancloud.app_key;
if (!supabaseUrl) {
throw new Error('Supabase serverUrl is empty');
}
if (!supabaseKey) {
throw new Error('Supabase anonKey is empty');
}
// 参数: target (slug), amount (增加的数量,1 或 0)
// 这个 RPC 调用同时包含了更新和查询逻辑
function updateCounter(target, amount) {
return fetch(`${supabaseUrl}/rest/v1/rpc/update_counter`, {
method: 'POST',
headers: {
'apikey': supabaseKey,
'Authorization': `Bearer ${supabaseKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
slug_input: target,
increment_amount: amount
})
})
.then(res => res.json())
.then(data => data)
.catch(err => {
console.error('Supabase Error:', err);
return 0;
});
}
// 校验是否为有效的 Host
function validHost() {
if (window.location.protocol === 'file:') {
return false;
}
if (CONFIG.web_analytics.leancloud.ignore_local) {
var hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return false;
}
}
return true;
}
// 校验是否为有效的 UV
function validUV() {
var key = 'leancloud_UV_Flag';
var flag = localStorage.getItem(key);
if (flag) {
// 距离标记小于 24 小时则不计为 UV
if (new Date().getTime() - parseInt(flag, 10) <= 86400000) {
return false;
}
}
localStorage.setItem(key, new Date().getTime().toString());
return true;
}
function addCount() {
var enableIncr = CONFIG.web_analytics.enable && !Fluid.ctx.dnt && validHost();
// 请求 PV 并自增
var pvCtn = document.querySelector('#leancloud-site-pv-container');
if (pvCtn) {
// 如果允许统计则 +1,否则 +0 (只读)
var amount = enableIncr ? 1 : 0;
updateCounter('site-pv', amount).then(count => {
var ele = document.querySelector('#leancloud-site-pv');
if (ele) {
ele.innerText = count;
pvCtn.style.display = 'inline';
}
});
}
// 请求 UV 并自增
var uvCtn = document.querySelector('#leancloud-site-uv-container');
if (uvCtn) {
var amount = (enableIncr && validUV()) ? 1 : 0;
updateCounter('site-uv', amount).then(count => {
var ele = document.querySelector('#leancloud-site-uv');
if (ele) {
ele.innerText = count;
uvCtn.style.display = 'inline';
}
});
}
// 如果有页面浏览数节点,则请求浏览数并自增
var viewCtn = document.querySelector('#leancloud-page-views-container');
if (viewCtn) {
var path = eval(CONFIG.web_analytics.leancloud.path || 'window.location.pathname');
var target = decodeURI(path.replace(/\/*(index.html)?$/, '/'));
var amount = enableIncr ? 1 : 0;
updateCounter(target, amount).then(count => {
var ele = document.querySelector('#leancloud-page-views');
if (ele) {
ele.innerText = count;
viewCtn.style.display = 'inline';
}
});
}
}
addCount();
})(window, document);
4. 将 Leancloud 的数据导入 Supabase
参考如下 Python 脚本:
1 | |
最后将生成的 supabase_counters.csv 和 supabase_visit_logs.csv 分别导入两张表即可。