LeanCloud 遗憾谢幕:基于 EdgeOne KV 打造高性能 PV/UV 访客统计
本文最后更新于 2026年2月16日 上午
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 做页面计数,你需要:
1 | |
用 EdgeOne KV,直接在 Edge Function 里写:
1 | |
KV 存储不是万能的,它不像 Redis 能执行 INCR、EXPIRE、LPUSH 等原子操作,也不适合存储大量热数据。但对于博客计数、配置开关、简单状态这类需求,它足够简单、足够快。
sequenceDiagram
participant U as 👤 用户
participant EO as ☁️ EdgeOne
participant KV as 📦 KV 存储
Note over U,EO: 🏢 传统方案
U->>EO: 访问博客页面
EO->>EO: 云服务器处理
EO->>EO: Express/Flask 应用
EO->>EO: 数据库查询
EO-->>U: 返回页面 (200-500ms)
Note over U,KV: ☁️ EdgeOne 方案
U->>EO: 访问博客页面
EO->>EO: 边缘节点处理
alt 静态资源
EO->>EO: CDN 缓存命中
EO-->>U: 返回静态资源 (< 50ms)
else Edge Functions
EO->>EO: Edge Functions 执行
EO->>KV: KV 存储读写
KV-->>EO: 返回数据
EO-->>U: 返回动态内容 (< 50ms)
else Node.js Functions
EO->>EO: Node.js Functions 执行
EO->>KV: KV 存储读写
KV-->>EO: 返回数据
EO-->>U: 返回复杂逻辑
end
Note right of EO: 零运维 + 自动扩展
EdgeOne Pages 的免费额度非常慷慨:
- 安全加速流量 / 请求:不限量
- Edge Functions 请求:300 万次/月
- Cloud Functions 请求:100 万次/月
- KV 存储空间:1 GB
对于个人开发者来说,这些额度足够用到天荒地老。当然,如果你对 SLA 有较高的要求,那么更推荐使用自有服务。
OpenKounter
有了 EdgeOne Pages 这个”地基”,我们就可以开始搭建 OpenKounter 了。首先是项目地址:
- Github 地址: https://github.com/Mintimate/open-kounter
- CNB 地址: https://cnb.cool/Mintimate/tool-forge/open-kounter

OpenKounter 的设计哲学是:简单至上,性能优先。
我没有使用关系型数据库(MySQL、PostgreSQL等),完全基于 KV 存储 来实现计数逻辑:
graph TB
User[👤 访客] -->|访问博客| Blog[Hexo 博客页面]
Blog -->|加载| Adapter[🔌 OpenKounterClient.js]
subgraph EdgeOne[☁️ 腾讯云 EdgeOne]
Adapter -->|API 请求| Function[⚡️ Edge Functions]
Admin[🔑 管理员] -->|访问| Dashboard[📊 管理后台]
Dashboard -->|API 请求| Function
Function -->|读写| KV[(📦 KV 存储)]
end
style EdgeOne fill:#e1f5ff,stroke:#01579b,stroke-width:2px
style Function fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style KV fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
简单说(技术视角):
- 零运维 / 低成本 — 完全 Serverless,EdgeOne 的额度通常足够个人开发者使用。
- 毫秒级响应(O(1)) — 边缘执行 + KV 的 O(1) 读取,单次计数延迟通常 < 50ms。
- 无缝替换 LeanCloud — 提供兼容的
OpenKounterClient.js用于作为客户端接入 Demo。 - 数据可控 & 易备份 — 数据存在你的 EdgeOne 账号;同时 OpenKounter 提供管理员界面,支持导入/导出 JSON 数据,也支持 LeanCloud 数据导入。
![]()
架构设计
OpenKounter 代码量不大,核心也就几百行。但考虑到 KV 存储没有 Redis 那样的查询语句、批量查询和自带排序算法,还得兼容 LeanCloud 老数据,设计上确实得小动脑子一下。
咱们平时用惯了 MySQL 或 MongoDB,换到 KV 存储可能会有点不适应。
这东西就像 Redis,快是真快,但“毛坯”也是真“毛坯”——没复杂查询,没事务,连排序都得自己想办法。
![]()
为了解决这些痛点,我用了几种设计模式来搭”骨架”.
KV 存储模式
在 OpenKounter 中,每一个页面的阅读量都是 KV 存储中的一个键值对。
- Key:
counter:{页面路径}(例如counter:/posts/hello-world/) - Value: JSON 字符串,包含计数值和时间戳。
1 | |
这种设计使得读取特定页面的计数极其高效(O(1) 复杂度)。
当用户访问 /posts/hello-world/ 时,系统只需执行一次 KV 查询:
1 | |
相比传统数据库的 SELECT * FROM counters WHERE path = '/posts/hello-world/',KV 存储的查询速度快了数十倍。
索引旁路模式
KV 存储的一个缺点是难以进行”范围查询”或”获取所有 Key”。比如,你想在管理后台展示”最近更新的 20 个计数器”,传统数据库可以轻松实现:
1 | |
但 KV 存储没有 ORDER BY,也没有 LIMIT。怎么办?
OpenKounter 的解决方案是:维护一个索引列表。
每当创建或更新一个计数器时,系统会更新一个特殊的 Key:system:counter_index。这个 Key 存储了一个数组,包含了所有已存在的计数器 Key,按更新时间排序。
1 | |
通过这种方式,我们在管理后台就可以轻松实现”按更新时间排序”的计数器列表,而无需遍历整个 KV 数据库。
1 | |
这种模式被称为 Index-Aside(索引旁路),是 KV 存储中常用的优化技巧。
Passkey 认证
OpenKounter 内置了基于 WebAuthn 的 Passkey 认证,支持指纹、Face ID 等生物识别方式登录管理后台,告别繁琐的 Token 密码输入。
核心流程分为注册和认证两个阶段:
sequenceDiagram
participant U as 👤 用户
participant F as 🖥️ 前端
participant E as ⚡️ Edge Function
participant KV as 📦 KV 存储
Note over U,KV: 注册流程
U->>F: 点击绑定 Passkey
F->>E: generateRegistrationOptions
E->>KV: 保存 Challenge (TTL 5min)
E-->>F: 返回注册选项
F->>U: 调用 WebAuthn API
U-->>F: 返回凭证
F->>E: verifyRegistration
E->>KV: 验证并删除 Challenge
E->>KV: 保存凭证
E-->>F: 注册成功
Note over U,KV: 认证流程(管理令牌)
U->>F: 验证 Passkey 更新 Token
F->>E: generateAuthenticationOptions
E->>KV: 保存 Challenge
E-->>F: 返回认证选项
F->>U: 调用 WebAuthn API
U-->>F: 返回签名
F->>E: generateManagementToken
E->>KV: 验证并删除 Challenge
E->>KV: 保存管理令牌 (TTL 5min)
E-->>F: 返回管理令牌
F->>E: 使用令牌更新 Token
KV 存储结构设计如下:
| Key | 说明 | TTL | 说明 |
|---|---|---|---|
passkey:user:{userId} |
用户信息(token、凭证列表) | 永久 | - |
passkey:credential:{credId} |
凭证信息(公钥、计数器) | 永久 | - |
passkey:challenge:{challengeId} |
临时 Challenge | 5 分钟 | 被动过期 |
passkey:mgmt_token:{tokenId} |
管理令牌 | 5 分钟 | 被动过期 |
后端在处理注册和认证时,会先清理用户的旧 Challenge,防止脏数据堆积:
1 | |
EdgeOne KV 的 expirationTtl 参数设置的是被动过期,而非主动删除:
- 过期数据不会在到期时自动从存储中删除(毕竟不是 Redis 那样的内存数据库)。
- 只有在下次访问该 Key 时,才会检查是否过期并返回
null - 过期但未被访问的数据仍然占用 KV 存储空间
因此,代码中采用双重保障策略:
- 主动清理(主要策略):在生成新 Challenge 前删除旧 Challenge,防止脏数据堆积。
- TTL 兜底(安全策略):确保即使主动清理失败,过期的 Challenge 也不会被误用。
这种设计是 KV 存储的常见实践——TTL 是“安全网”,而非“清理工具”。
Hexo 接入示例
为了让 OpenKounter 能够无缝替代 LeanCloud,我们提供了一个 Hexo 客户端接入示例 adapter.js。
这个脚本是专门为 Hexo 博客(特别是 Fluid 主题)设计的客户端工具包,它演示了如何在静态博客中集成 OpenKounter 的计数功能。主要特性包括:
- 读取配置:从 Hexo 配置文件中读取
server_url等参数 - 智能收集:在页面加载时,自动检测页面中的计数器元素(PV、UV、页面浏览数)
- 批量请求:将多个计数器的更新请求打包成一个批量请求
- 实时显示:更新页面上的计数显示,提供即时反馈
- 本地环境过滤:支持
ignore_local配置,避免本地开发时污染数据 - UV 去重:使用 localStorage 实现 24 小时内的 UV 去重逻辑
1 | |
这个 Demo 展示了如何将 OpenKounter 集成到 Hexo 博客中。对于其他静态网站生成器(如 Hugo、Jekyll 等),你可以参考这个实现,根据自己的需求进行调整。
这样,Hexo 主题(如 Fluid)无需修改核心代码,只需引入 adapter.js 并配置 server_url 即可完成从 LeanCloud 到 OpenKounter 的迁移。
支持创作
制作教程不易,如果热心的小伙伴,想支持创作,可以加入我们的电圈(还可以解锁远程协助、好友位😃):
- Mintimate的电圈: https://afdian.com/a/mintimate
- Mintimate的微信赞赏码 👉 如果认为本教程对你很有帮助,可以请我喝咖啡 ☕
志同道合的小伙伴也是知音难觅。
- 开发者爱好群: 👉 如果你对云服务器、CDN、云数据库和Linux等云计算感兴趣,亦或者喜欢编程、设计、产品、运营等领域,欢迎加入我们的开发者爱好群,一起交流学习(目前可能就我一个人?🤔,毕竟才刚刚创建~)。
当然,也欢迎在B站、YouTube或微信公众号上关注我们:
- Bilibili: https://space.bilibili.com/355567627
- YouTube: https://www.youtube.com/@mintimate/featured
- 微信公众号: MintimateBlog
更多:
部署指南
部署 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 来测试计数功能:
1 | |
成功响应将返回 code: 0 和计数数据;管理后台首次访问会引导你设置管理员 Token(也可在部署时通过环境变量 ADMIN_TOKEN 预置和找回密码)。
接入博客
在你的 Hexo 博客中(以 Fluid 主题为例),adapter.js 本质上是一个客户端接入的 Demo,你可以直接使用它,也可以根据自己的需求进行修改。
你可以将仓库中的 client/adapter.js 下载下来,放到博客的 source/js 目录下(例如重命名为 openkounter.js),然后在主题配置中引入:
1 | |
即使你的主题(如 Fluid)已经内置了 OpenKounter 支持,你也可以通过这种方式引入自定义脚本来覆盖默认行为(需先禁用主题自带的 OpenKounter 或确保脚本执行顺序)。
同时,在博客配置文件中添加 OpenKounter 的服务地址配置(adapter.js 依赖此配置):
1 | |
当然,同时还需要修改主题的其他配置文件,通常是 analytics.ejs、statistics.ejs 等文件。修改完成后,重新生成并部署博客,刷新页面,你就能看到阅读量统计恢复正常了!
如果你是 Fluid 主题用户,可以查看我的这个 Fork 的这次 Commit: Mintimate/hexo-theme-fluid/commit/dace585020440402641db7e87a189245aa83a0a3,它展示了如何修改主题来兼容 OpenKounter。

如果你使用的是其他主题,可能需要修改 adapter.js 中读取配置(CONFIG 对象)和获取 DOM 用于显示的逻辑。
管理后台
为了管理方便,我在 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: 进入项目目录并运行:
1 | |
具体的调试,可以参考官方文档:
Q: 如何导出备份?
A: 管理后台提供“导出”功能,或使用 API action: "export_all"(需管理员 Token),会返回 counters 和 allowedDomains 的完整 JSON。📦
END
OpenKounter 不仅是一个工具,更是一种思路的展示:利用 Serverless 和边缘计算,我们可以用极低的成本构建高可用、高性能的应用。
LeanCloud 的关停,看似是一场危机,实则是一次机遇——它让我们重新思考:我们真的需要一个”大而全”的 BaaS 平台吗?
对于个人博客来说,答案是否定的。我们需要的只是一个简单、可靠、可控的计数器服务。而 OpenKounter,正是这样一个存在。
如果你正在为 LeanCloud 的替代方案发愁,不妨试试 OpenKounter。它开源、免费、且完全属于你。
欢迎 Star 和 Fork,如果有任何问题,欢迎在 Issue 中反馈!
最后,如果你觉得本篇教程对你有帮助,欢迎加入我们的开发者交流群: 812198734 ,一起交流学习,共同进步。

