2026 年 1 月 12 日,LeanCloud 官宣停服。对于数以万计的静态博客主而言,这无异于一场"地震":原本依赖它实现的文章阅读量(PV)与站点访客(UV)统计将在一年后彻底停摆。
面对终局,博主们面临抉择:是继续寻找下一个易损的 BaaS 平台,还是斥资租赁云服务器、投入繁琐的运维成本去"大炮打蚊子"?
其实,对于追求轻量的静态博客,我们需要的仅仅是一个纯粹、快速且自主的计数器。相比复杂的服务器自建方案,基于边缘计算的 SaaS 化改造显然更契合 Hexo 的极简灵魂。

与其被动等待下一个"关停通知",不如利用 Serverless + KV 存储 技术,打造一个轻量、完全可控的计数器(甚至还是免费的)。
这就是 OpenKounter 诞生的故事。
危机时刻
让我们回到 2026 年 1 月发布停服公告的那一刻。
当你像往常一样打开 LeanCloud 控制台查看数据时,映入眼帘的是一行醒目且冰冷的公告:
LeanCloud 官方公告(2026-01-12):平台将于 2027-01-12 正式停止对外服务。

虽然此刻你的博客阅读量还在正常显示,但倒计时的钟声已经敲响。你知道,如果不采取行动,一年后的今天,所有文章辛辛苦苦积累的阅读数据,都将随着服务器的关闭而彻底归零。

你的第一反应是:"我的数据怎么办?"
幸运的是,LeanCloud 提供了数据导出功能。但导出之后呢?这些 JSON 文件该往哪儿放?重新找一个第三方服务?还是自己搭建一个后端?
如果选择自建,传统方案需要:
- 一台云服务器(价格不高,主要是需要维护)
- 一个数据库(MySQL/MongoDB)
- 一套后端框架(Express/Flask/Django)
- 定期维护、备份、监控......
对于一个月度 PV 大概 1W 次的网站来说博客来说,单独维护这一套内容,有一些太"重"了。尤其是 Hexo 这样的静态博客网站:

有没有一种方案,既免费、又轻量、还能完全掌控数据?
答案是:Serverless + 边缘计算 。尤其是 Hexo 博客也是部署在静态托管服务上的,完全可以利用同一平台(EdgeOne Pages)的 Serverless 功能来实现计数器的功能。
EdgeOne Pages
在介绍 OpenKounter 之前,我们先聊聊它的"地基"------EdgeOne Pages。
EdgeOne Pages 是腾讯云推出的 Serverless 静态网站托管服务,类似于 Cloudflare Pages、Vercel 或 Netlify。它集成了静态托管、Edge Functions 和 Node.js Functions。它的核心能力包括:
- 静态资源托管:自动部署你的前端项目(Vue、React、Hexo 等)。
- Edge Functions:在边缘节点运行的 Serverless 函数,响应速度极快,支持操作 KV 存储。
- Node.js Functions:支持完整的 Node.js 运行时,适合复杂的业务逻辑。
- KV 存储:全球分布式的 Key-Value 数据库,读写延迟低至毫秒级(目前仅支持 Edge Functions 调用)。

什么是 KV
如果你用过 Redis,你可以把 KV 当作 Lite 版本的 Redis:
| 能力 | Redis | EdgeOne KV |
|---|---|---|
| 典型用途 | 缓存热点数据、Session 存储、限流计数器 | 页面访问计数、配置存储、临时状态 |
| 使用方式 | 需要自建/租用 Redis 实例,维护连接池 | 直接通过 Edge Functions 调用,零运维 |
| 数据结构 | 支持 String、Hash、List、Set 等丰富类型 | 纯 Key-Value,Value 存储字符串或 JSON |
| 持久化 | 支持 RDB/AOF 持久化 | 自动持久化,无需备份 |
| TTL 过期 | 主动删除,到期自动清理 | 被动过期,可逻辑自动清理,但无法主动过期清理 |
| 部署位置 | 通常是中心化的单个或集群节点 | 全球边缘节点就近访问 |
举个例子:用 Redis 做页面计数,你需要:
javascript
// 需要额外购买 Redis 服务,配置连接
const redis = new Redis('redis://your-redis-host:6379');
await redis.incr('counter:/posts/hello-world/');
用 EdgeOne KV,直接在 Edge Function 里写:
javascript
// KV_DEMO 是内置绑定,无需配置连接
const data = await KV_DEMO.get('counter:/posts/hello-world/');
const count = data ? JSON.parse(data).time + 1 : 1;
await KV_DEMO.put('counter:/posts/hello-world/', JSON.stringify({ time: count }));
KV 存储不是万能的 ,它不像 Redis 能执行 INCR、EXPIRE、LPUSH 等原子操作,也不适合存储大量热数据。但对于博客计数、配置开关、简单状态这类需求,它足够简单、足够快。
EdgeOne Pages 的免费额度非常慷慨:
- 安全加速流量 / 请求:不限量
- Edge Functions 请求:300 万次/月
- Cloud Functions 请求:100 万次/月
- KV 存储空间:1 GB
对于个人开发者来说,这些额度足够用到天荒地老。当然,如果你对 SLA 有较高的要求,那么更推荐使用自有服务。
OpenKounter
有了 EdgeOne Pages 这个"地基",我们就可以开始搭建 OpenKounter 了。首先是项目地址:
- Github 地址: github.com/Mintimate/o...
- CNB 地址: cnb.cool/Mintimate/t...

OpenKounter 的设计哲学是:简单至上,性能优先。
我没有使用关系型数据库(MySQL、PostgreSQL等),完全基于 KV 存储 来实现计数逻辑:
简单说(技术视角):
- 零运维 / 低成本 --- 完全 Serverless,EdgeOne 的额度通常足够个人开发者使用。
- 毫秒级响应(O(1)) --- 边缘执行 + KV 的 O(1) 读取,单次计数延迟通常 < 50ms。
- 无缝替换 LeanCloud --- 提供兼容的
OpenKounterClient.js用于作为客户端接入 Demo。 - 数据可控 & 易备份 --- 数据存在你的 EdgeOne 账号;同时 OpenKounter 提供管理员界面,支持导入/导出 JSON 数据,也支持 LeanCloud 数据导入。

实际效果
纸上得来终觉浅,让我们看看 OpenKounter 在实际博客中的表现。在我的 Hexo 博客中,OpenKounter 的 API 响应时间稳定在 50ms ~ 100ms 之间:

这个响应时间包含了:
- Edge Functions 的冷启动(如有)和执行时间
- KV 存储的读写延迟
- 网络传输时间
与传统方案相比:
| 方案 | 典型响应时间 | 说明 |
|---|---|---|
| 自建服务器 + 数据库 | 200-500ms | 需要建立连接、执行 SQL |
| LeanCloud | 100-300ms | 云端服务,受网络波动影响 |
| OpenKounter (EdgeOne) | 50-100ms | 边缘节点就近响应,KV 存储极速读写 |
得益于 EdgeOne 的全球边缘节点部署,用户访问时会自动路由到最近的节点,无需像传统服务那样跨区域请求。同时,KV 存储的 O(1) 读写特性,确保了无论数据量多大,单次计数操作都能保持稳定的低延迟。
架构设计
OpenKounter 代码量不大,核心也就几百行。但考虑到 KV 存储没有 Redis 那样的查询语句、批量查询和自带排序算法,还得兼容 LeanCloud 老数据,设计上确实得小动脑子一下。
咱们平时用惯了 MySQL 或 MongoDB,换到 KV 存储可能会有点不适应。
这东西就像 Redis,快是真快,但"毛坯"也是真"毛坯"------没复杂查询,没事务,连排序都得自己想办法。

为了解决这些痛点,我用了几种设计模式来搭"骨架".
KV 存储模式
在 OpenKounter 中,每一个页面的阅读量都是 KV 存储中的一个键值对。
- Key :
counter:{页面路径}(例如counter:/posts/hello-world/) - Value: JSON 字符串,包含计数值和时间戳。
json
{
"time": 1024,
"created_at": 1700000000000,
"updated_at": 1700000000000
}
这种设计使得读取特定页面的计数极其高效(O(1) 复杂度)。
当用户访问 /posts/hello-world/ 时,系统只需执行一次 KV 查询:
javascript
const data = await OPEN_KOUNTER.get('counter:/posts/hello-world/');
const count = data ? JSON.parse(data).time : 0;
相比传统数据库的 SELECT * FROM counters WHERE path = '/posts/hello-world/',KV 存储的查询速度快了数十倍。
索引旁路模式
KV 存储的一个缺点是难以进行"范围查询"或"获取所有 Key"。比如,你想在管理后台展示"最近更新的 20 个计数器",传统数据库可以轻松实现:
sql
SELECT * FROM counters ORDER BY updated_at DESC LIMIT 20;
但 KV 存储没有 ORDER BY,也没有 LIMIT。怎么办?
OpenKounter 的解决方案是:维护一个索引列表。
每当创建或更新一个计数器时,系统会更新一个特殊的 Key:system:counter_index。这个 Key 存储了一个数组,包含了所有已存在的计数器 Key,按更新时间排序。
javascript
// edge-functions/api/counter.js 中的核心逻辑
async function updateIndex(target) {
const indexKey = 'system:counter_index';
// 1. 获取现有索引
const indexData = await OPEN_KOUNTER.get(indexKey);
let index = indexData ? JSON.parse(indexData) : [];
// 2. 移除旧位置(如果存在)
const idx = index.indexOf(target);
if (idx > -1) {
index.splice(idx, 1);
}
// 3. 将当前 Key 移到末尾(表示最近更新)
index.push(target);
// 4. 写回索引
await OPEN_KOUNTER.put(indexKey, JSON.stringify(index));
}
通过这种方式,我们在管理后台就可以轻松实现"按更新时间排序"的计数器列表,而无需遍历整个 KV 数据库。
javascript
// 获取最近更新的 20 个计数器
const indexData = await OPEN_KOUNTER.get('system:counter_index');
const index = JSON.parse(indexData);
const recentKeys = index.slice(-20).reverse(); // 取最后 20 个,倒序排列
这种模式被称为 Index-Aside(索引旁路),是 KV 存储中常用的优化技巧。
Passkey 认证
OpenKounter 内置了基于 WebAuthn 的 Passkey 认证,支持指纹、Face ID 等生物识别方式登录管理后台,告别繁琐的 Token 密码输入。
核心流程分为注册和认证两个阶段:
KV 存储结构设计如下:
| Key | 说明 | TTL | 说明 |
|---|---|---|---|
passkey:user:{userId} |
用户信息(token、凭证列表) | 永久 | - |
passkey:credential:{credId} |
凭证信息(公钥、计数器) | 永久 | - |
passkey:challenge:{challengeId} |
临时 Challenge | 5 分钟 | 被动过期 |
passkey:mgmt_token:{tokenId} |
管理令牌 | 5 分钟 | 被动过期 |
后端在处理注册和认证时,会先清理用户的旧 Challenge,防止脏数据堆积:
javascript
// edge-functions/api/passkey.js
async function handleGenerateRegistrationOptions(data, rpConfig, env) {
// ...
// 清理旧 Challenge,防止脏数据堆积
if (user && user.currentChallengeId) {
await kvDelete(`passkey:challenge:${user.currentChallengeId}`);
}
// ...
}
async function getAndDeleteChallenge(challengeId) {
const data = await kvGet(`passkey:challenge:${challengeId}`);
if (data) {
await kvDelete(`passkey:challenge:${challengeId}`);
// 清理用户对象上的 Challenge 指针
if (data.userId) {
const user = await getUser(data.userId);
if (user && user.currentChallengeId === challengeId) {
delete user.currentChallengeId;
await saveUser(user);
}
}
}
return data;
}
{% note warning %} EdgeOne KV 的 expirationTtl 参数设置的是被动过期,而非主动删除:
- 过期数据不会在到期时自动从存储中删除(毕竟不是 Redis 那样的内存数据库)。
- 只有在下次访问该 Key 时,才会检查是否过期并返回
null - 过期但未被访问的数据仍然占用 KV 存储空间
因此,代码中采用双重保障策略:
- 主动清理(主要策略):在生成新 Challenge 前删除旧 Challenge,防止脏数据堆积。
- TTL 兜底(安全策略):确保即使主动清理失败,过期的 Challenge 也不会被误用。
{% endnote %}
这种设计是 KV 存储的常见实践------TTL 是"安全网",而非"清理工具"。
Hexo 接入示例
为了让 OpenKounter 能够无缝替代 LeanCloud,我们提供了一个 Hexo 客户端接入示例 adapter.js。
这个脚本是专门为 Hexo 博客(特别是 Fluid 主题)设计的客户端工具包,它演示了如何在静态博客中集成 OpenKounter 的计数功能。主要特性包括:
- 读取配置 :从 Hexo 配置文件中读取
server_url等参数 - 智能收集:在页面加载时,自动检测页面中的计数器元素(PV、UV、页面浏览数)
- 批量请求:将多个计数器的更新请求打包成一个批量请求
- 实时显示:更新页面上的计数显示,提供即时反馈
- 本地环境过滤 :支持
ignore_local配置,避免本地开发时污染数据 - UV 去重:使用 localStorage 实现 24 小时内的 UV 去重逻辑
javascript
// client/adapter.js - Hexo 接入示例的核心逻辑
(function(window, document) {
'use strict';
// 从 Hexo 配置中获取服务器地址
const API_SERVER = (CONFIG.web_analytics.openkounter && CONFIG.web_analytics.openkounter.server_url) || '';
if (!API_SERVER) {
console.warn('OpenKounter: server_url is not configured');
return;
}
function addCount() {
const enableIncr = CONFIG.web_analytics.enable && validHost();
const getterArr = [];
const incrArr = [];
// 检测并处理站点 PV 容器
const pvCtn = document.querySelector('#openkounter-site-pv-container');
if (pvCtn) {
const pvGetter = getRecord('site-pv').then((record) => {
if (enableIncr) {
incrArr.push(buildIncrement(record.objectId));
}
const ele = document.querySelector('#openkounter-site-pv');
if (ele) {
ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
pvCtn.style.display = 'inline';
}
});
getterArr.push(pvGetter);
}
// 检测并处理站点 UV 容器
const uvCtn = document.querySelector('#openkounter-site-uv-container');
if (uvCtn) {
const uvGetter = getRecord('site-uv').then((record) => {
const incrUV = validUV() && enableIncr;
if (incrUV) {
incrArr.push(buildIncrement(record.objectId));
}
const ele = document.querySelector('#openkounter-site-uv');
if (ele) {
ele.innerText = (record.time || 0) + (incrUV ? 1 : 0);
uvCtn.style.display = 'inline';
}
});
getterArr.push(uvGetter);
}
// 检测并处理页面浏览数容器
const viewCtn = document.querySelector('#openkounter-page-views-container');
if (viewCtn) {
const pathConfig = CONFIG.web_analytics.openkounter.path || 'window.location.pathname';
const path = eval(pathConfig);
const target = decodeURI(path.replace(/\/*(index.html)?$/, '/'));
const viewGetter = getRecord(target).then((record) => {
if (enableIncr) {
incrArr.push(buildIncrement(record.objectId));
}
const ele = document.querySelector('#openkounter-page-views');
if (ele) {
ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0);
viewCtn.style.display = 'inline';
}
});
getterArr.push(viewGetter);
}
// 批量发起统计请求
Promise.all(getterArr).then(() => {
if (enableIncr && incrArr.length > 0) {
increment(incrArr);
}
}).catch(error => {
console.error('OpenKounter error:', error);
});
}
addCount();
})(window, document);
这个 Demo 展示了如何将 OpenKounter 集成到 Hexo 博客中。对于其他静态网站生成器(如 Hugo、Jekyll 等),你可以参考这个实现,根据自己的需求进行调整。
这样,Hexo 主题(如 Fluid)无需修改核心代码,只需引入 adapter.js 并配置 server_url 即可完成从 LeanCloud 到 OpenKounter 的迁移。
{% supportExt %}
部署指南
部署 OpenKounter 非常简单,你只需要一个腾讯云账号(用于 EdgeOne Pages 部署),并且开通 Pages 的功能:

部署项目
EdgeOne Pages 支持直接从 GitHub 仓库部署,并且支持浏览器端的无代码部署流程。点击下方的按钮,直接将项目部署到你的 EdgeOne Pages:


部署完成后,需要绑定 KV 存储。
配置 KV
部署完成后,你需要创建一个 KV 命名空间并绑定到项目:
- 进入 EdgeOne Pages 控制台,找到你的项目。
- 进入 KV 存储 。
- 创建一个新的 KV 命名空间且变量名称 命名为
OPEN_KOUNTER。 - 重新部署项目以使配置生效。

测试使用
假设,你的 EdgeOne Pages 域名是 https://your-domain.edgeone.pages.dev,你可以通过以下 API 来测试计数功能:
bash
# 查询计数
curl 'https://<your-domain>/api/counter?target=/posts/hello-world/'
# 批量自增(site-pv 和 当前页面)
curl -X POST 'https://<your-domain>/api/counter' \
-H 'Content-Type: application/json' \
-d '{"action":"batch_inc","requests":[{"target":"site-pv"},{"target":"/posts/hello-world/"}]}'
成功响应将返回 code: 0 和计数数据;管理后台首次访问会引导你设置管理员 Token(也可在部署时通过环境变量 ADMIN_TOKEN 预置和找回密码)。
接入博客
在你的 Hexo 博客中(以 Fluid 主题为例),adapter.js 本质上是一个客户端接入的 Demo,你可以直接使用它,也可以根据自己的需求进行修改。
你可以将仓库中的 client/adapter.js 下载下来,放到博客的 source/js 目录下(例如重命名为 openkounter.js),然后在主题配置中引入:
yaml
### _config.fluid.yml
custom_js:
- /js/openkounter.js
即使你的主题(如 Fluid)已经内置了 OpenKounter 支持,你也可以通过这种方式引入自定义脚本来覆盖默认行为(需先禁用主题自带的 OpenKounter 或确保脚本执行顺序)。
同时,在博客配置文件中添加 OpenKounter 的服务地址配置(adapter.js 依赖此配置):
yaml
### _config.yml
web_analytics:
enable: true
openkounter:
server_url: "https://你的项目域名.edgeone.pages.dev"
# 可选:是否忽略本地开发环境 (localhost)
ignore_local: true
当然,同时还需要修改主题的其他配置文件,通常是 analytics.ejs、statistics.ejs 等文件。修改完成后,重新生成并部署博客,刷新页面,你就能看到阅读量统计恢复正常了!
如果你是 Fluid 主题用户,可以查看我的这个 Fork 的这次 Commit: Mintimate/hexo-theme-fluid/commit/dace585020440402641db7e87a189245aa83a0a3,它展示了如何修改主题来兼容 OpenKounter。

{% note info %} 如果你使用的是其他主题,可能需要修改 adapter.js 中读取配置(CONFIG 对象)和获取 DOM 用于显示的逻辑。 {% endnote %}
管理后台
为了管理方便,我在 OpenKounter 里内置了一个基于 Vue 3 的可视化管理后台。

访问你的 EdgeOne Pages 域名,首次访问会引导你设置管理员 Token。之后,你可以:
- 查看计数器列表:按更新时间排序,支持分页。
- 手动修改计数值:比如从 LeanCloud 迁移数据时,可以批量导入。
- 配置域名白名单:防止恶意刷量。
- 导出/导入数据:一键备份所有计数器数据。
- Passkey 无密码登录:支持生物识别(指纹、Face ID)登录,告别繁琐的 Token 密码输入。
管理后台的设计非常简洁,所有操作都在一个页面内完成,无需跳转。
数据迁移
如果你之前使用 LeanCloud,可以通过以下步骤迁移数据:
- 在 LeanCloud 控制台导出数据(JSON 格式)。
- 登录 OpenKounter 管理后台。 进入"数据备份"页面,点击"导入数据"。
- 上传 JSON 文件,系统会自动解析并导入。

导入完成后,所有计数器的值都会恢复到迁移前的状态。
常见问题(FAQ)
Q: OpenKounter 能直接替代我原来的 LeanCloud 计数吗?
A: 能 --- 对于阅读数/站点 PV/UV 等"计数"功能,adapter.js 已经兼容常见主题的调用方式;但它不是完整的 LeanCloud SDK,复杂的 AVObject/查询逻辑需手动替换或迁移。✅
Q: 怎样防止被刷量?
A: 使用域名白名单(管理后台配置 system:allowed_domains)+ 后端的 checkOriginAllowed 校验;对于管理接口请使用 Token 或 Passkey。🔒
Q: 免费额度够用吗?
A: 对于个人博客,大多数情况下 EdgeOne Pages 的免费额度(100 万请求/月、100 GB 流量、1 GB KV)足够;高流量网站请考虑付费方案或缓存优化。💡
Q: 如何在本地调试管理后台?
A: 进入项目目录并运行:
bash
cd open-kounter
# 安装 EdgeOne CLI 工具(如果尚未安装)
npm install -g edgeone
# 登录 EdgeOne CLI
edgeone login
# 关联 EdgeOne Pages 项目
edgeone pages link
# 运行本地开发服务器
edgeone pages dev
具体的调试,可以参考官方文档:
Q: 如何导出备份?
A: 管理后台提供"导出"功能,或使用 API action: "export_all"(需管理员 Token),会返回 counters 和 allowedDomains 的完整 JSON。📦
END
OpenKounter 不仅是一个工具,更是一种思路的展示:利用 Serverless 和边缘计算,我们可以用极低的成本构建高可用、高性能的应用。
LeanCloud 的关停,看似是一场危机,实则是一次机遇------它让我们重新思考:我们真的需要一个"大而全"的 BaaS 平台吗?
对于个人博客来说,答案是否定的。我们需要的只是一个简单、可靠、可控的计数器服务。而 OpenKounter,正是这样一个存在。
如果你正在为 LeanCloud 的替代方案发愁,不妨试试 OpenKounter。它开源、免费、且完全属于你。
- 项目地址 : github.com/Mintimate/o...
欢迎 Star 和 Fork,如果有任何问题,欢迎在 Issue 中反馈!
最后,如果你觉得本篇教程对你有帮助,欢迎加入我们的开发者交流群: 812198734 ,一起交流学习,共同进步。