Open Kounter 是一个基于 EdgeOne Pages Functions 和 Blob 存储的无服务器计数器服务,旨在替代 LeanCloud 为静态网站(如 Hexo)提供 PV/UV 统计功能。它包含一个完整的管理后台,支持数据管理、导入导出、域名白名单、旧版 KV 一键迁移、Passkey 无密码登录和 OIDC 单点登录。
更详细的介绍和部署指南请参考:
你可以通过 EdgeOne Pages 一键部署或手动配置构建:
一键部署:
注意:点击上方按钮前,建议先 Fork 本仓库,并在跳转后的页面中确认仓库地址为你 Fork 后的地址。
手动部署配置:
- 框架预设:Node.js(或留空,EdgeOne 会识别
edgeone.json) - 构建命令:
npm run build - 输出目录:
dist - Node 版本:
22
更多 EdgeOne Pages文档:https://pages.edgeone.ai/zh/document/product-introduction
Open Kounter 早期使用 EdgeOne Pages KV 保存计数器、配置和认证信息,但 KV 的全球同步是最终一致模型,在多节点场景下会有明显同步延迟。
- 计数器刚写入后,后台列表或下一次读取可能短时间内看不到最新值。
- 域名白名单、Token、Passkey 相关状态在边缘节点之间同步时,也可能出现短暂不一致。
- 对于计数器这类“写完就要立刻读到最新结果”的场景,KV 的全球同步延迟会直接影响可用性。
现在主存储已经切换为 Blob,并在核心读取路径中使用强一致读取。这样做的目标很明确:解决 KV 全球同步存在延迟的问题,让计数、配置和认证状态在写入后能更快、更稳定地读到最新值。
本项目使用 Blob 作为主存储,部署后会在 cloud-functions 中自动创建并使用名为 open-kounter 的 Blob Store。
- 开启 Blob 能力
- 在 EdgeOne Pages 控制台进入项目设置 > 存储
- 确认项目已开通 Blob 能力
- 首次访问自动创建 Store
- 默认会使用
open-kounter作为 Blob Store 名称 - 如需自定义名称,可设置环境变量
OPEN_KOUNTER_BLOB_STORE
- Passkey 域名配置(可选)
- Passkey 默认会使用当前页面
Origin的 hostname 作为 RP ID - 如需固定 RP ID 或跨环境统一配置,可设置环境变量
PASSKEY_RP_ID - 如需自定义显示名称,可设置环境变量
PASSKEY_RP_NAME
- OIDC 单点登录配置(可选)
- 如需启用 OIDC 登录,请先在 EdgeOne Pages 环境变量中配置 OIDC 参数,并在 OIDC Provider 中把 Redirect URI 设置为
https://你的域名/api/oidc/callback - 配置完成后,管理员先使用 Token 登录后台,在 OIDC 登录模块中绑定身份;绑定成功后,登录页会自动显示 OIDC 登录按钮
- 具体变量说明和使用流程见下方“环境变量一览”和“登录方式”章节
- 重新部署项目
- 启用 Blob 或调整环境变量后建议重新部署
如果你是从旧版 KV 存储迁移,请额外绑定旧 KV 命名空间,变量名仍为:OPEN_KOUNTER。
- 新部署且没有历史数据时,不需要绑定 KV。
- 已有旧 KV 数据时,可在登录页或后台“数据备份”面板中点击“旧 KV 迁移到 Blob”。
部署并配置完成后,访问你的项目网址,首次访问将引导你设置管理员 Token。
.
├── client/
│ └── adapter.js # 客户端适配器,模拟 LeanCloud 行为
├── cloud-functions/ # 主后端逻辑 (Blob API)
│ └── api/
│ ├── auth.js # 认证逻辑
│ ├── counter.js # 核心计数器逻辑
│ ├── init.js # 初始化与迁移接口
│ ├── passkey.js # Passkey 相关逻辑
│ └── oidc/ # OIDC 单点登录
│ ├── login.js # OIDC 登录发起
│ ├── callback.js # OIDC 回调处理
│ └── status.js # OIDC 绑定状态查询
├── edge-functions/ # 兼容旧版 KV 的迁移出口
│ └── legacy-api/
│ └── migrate.js # 导出旧 KV 数据供 Blob 导入
├── src/ # 前端管理后台 (Vue 3 + Vite)
│ ├── components/
│ │ ├── dashboard/
│ │ │ ├── CounterList.vue # 计数器列表
│ │ │ ├── DataBackup.vue # 数据备份与恢复
│ │ │ ├── DomainConfig.vue # 域名白名单配置
│ │ │ ├── OidcManager.vue # OIDC 绑定管理
│ │ │ ├── PasskeyManager.vue # Passkey 管理
│ │ │ ├── SingleCounterManager.vue # 单个计数器管理
│ │ │ └── TotalStats.vue # 统计概览
│ │ ├── Dashboard.vue # 仪表盘主组件
│ │ └── Login.vue # 登录组件
│ ├── utils/ # 工具函数
│ ├── App.vue # 主应用组件
│ ├── main.js # 入口文件
│ └── style.css # 全局样式
├── edgeone.json # EdgeOne 配置文件
├── index.html # HTML 入口
├── package.json # 项目依赖
├── tailwind.config.js # Tailwind 配置
└── vite.config.js # Vite 配置
所有 API 的基础路径为 /api。
- URL:
GET /api/counter - 参数:
target(必填,计数器的 Key,如site-pv) - 响应:
{ "code": 0, "data": { "time": 100, "target": "site-pv", "created_at": 1700000000000, "updated_at": 1700000000000 } }
- URL:
POST /api/counter - Body:
{ "action": "inc", "target": "site-pv" } - 说明: 受域名白名单限制。
- URL:
POST /api/counter - Body:
{ "action": "batch_inc", "requests": [ { "target": "site-pv" }, { "target": "/posts/hello-world/" } ] } - 说明: 常用于同时更新站点总 PV 和单页 PV。受域名白名单限制。
需要在 Header 中携带 Authorization: Bearer <YOUR_TOKEN>。
- URL:
POST /api/counter - Body:
{ "action": "set", "target": "site-pv", "value": 1000 }
- URL:
POST /api/counter - Body:
{ "action": "delete", "target": "site-pv" }
- URL:
POST /api/counter - Body:
{ "action": "list", "page": 1, "pageSize": 20 }
- URL:
POST /api/counter - Body:
{ "action": "export_all" } - 响应: 包含所有计数器数据和配置的 JSON 对象。
- URL:
POST /api/counter - Body:
{ "action": "import_all", "data": { ... } // 导出的 JSON 数据 }
- URL:
POST /api/counter - Body:
{ "action": "set_config", "allowedDomains": ["example.com", "*.example.com"] }
OIDC 相关接口用于单点登录绑定、登录回调和状态管理。
- URL:
GET /api/oidc/login?mode=login|bind - 说明:
mode=login:发起 OIDC 登录。mode=bind&token=<ADMIN_TOKEN>:管理员登录后绑定 OIDC 身份。- 后端会生成
state/nonce,写入 Blob 后重定向到 OIDC Provider 授权端点。
- URL:
GET /api/oidc/callback?code=xxx&state=yyy - 说明:
- 由 OIDC Provider 回调。
bind模式会验证管理员 Token 并把绑定身份写入system/state.json。login模式会校验sub是否与已绑定身份一致,成功后生成一次性 OIDC Session。
- URL:
POST /api/oidc/status - Body:
{ "action": "get_status" } - 说明: 查询 OIDC 是否已配置、是否已绑定;解绑需携带
Authorization: Bearer <YOUR_TOKEN>并传入{ "action": "unbind" }。
- URL:
POST /api/auth - Body:
{ "action": "oidc_verify", "oidcSession": "<session-id>" } - 说明: 前端在 OIDC 回调后自动调用;Session 是一次性的,验证后会被删除。
| 变量名 | 必需 | 说明 |
|---|---|---|
OPEN_KOUNTER_BLOB_STORE |
否 | 自定义 Blob Store 名称,默认 open-kounter |
ADMIN_TOKEN |
否 | 预设管理员 Token(优先级高于 Blob 中存储的 Token) |
PASSKEY_RP_ID |
否 | Passkey RP ID,默认使用当前域名 |
PASSKEY_RP_NAME |
否 | Passkey 显示名称,默认 Open Kounter |
OIDC_ISSUER |
否 | OIDC Issuer URL,例如 https://auth.example.com/realms/master |
OIDC_CLIENT_ID |
否 | OIDC 客户端 ID |
OIDC_CLIENT_SECRET |
否 | OIDC 客户端密钥,仅保存在环境变量中 |
OIDC_REDIRECT_URI |
否 | OIDC 回调地址,例如 https://your-domain.com/api/oidc/callback |
Open Kounter 支持三种登录方式,登录页会自动检测可用的登录方式并渐进式展示:
- Token 登录(始终可用):使用管理员 Token 直接登录,也用于首次初始化和找回访问权限。
- Passkey 登录(按需展示):在后台绑定 Passkey 后,登录页自动显示 Passkey 登录按钮。
- OIDC 登录(按需展示):配置 OIDC 环境变量并在后台绑定 OIDC 账号后,登录页自动显示 OIDC 登录按钮。
OIDC 的使用顺序建议如下:
- 在 EdgeOne Pages 环境变量中配置
OIDC_ISSUER、OIDC_CLIENT_ID、OIDC_CLIENT_SECRET、OIDC_REDIRECT_URI。 - 在 OIDC Provider 中配置回调地址:
https://你的域名/api/oidc/callback。 - 使用管理员 Token 登录 Open Kounter 后台。
- 在后台的 OIDC 登录模块中点击“绑定 OIDC 身份”。
- 绑定成功后退出登录,登录页会出现“使用 OIDC 登录”按钮。
OIDC 绑定身份会保存在 Blob 的 system/state.json 中;登录过程中的临时 state 和一次性 Session 会写入 oidc/states/*.json 与 oidc/sessions/*.json,并通过 expiresAt 做应用层过期控制。
登录页加载时会先进行环境检测(显示加载动画),检测完成后仅展示已配置/已绑定的登录方式,保持界面简洁。
本项目基于 MIT License 开源。
