dbx 插件市场流量统计 Worker 全解析:从 Analytics Engine 采集到 KV 永久归档
2026/9/21 19:22:06 网站建设 项目流程
  • 数据库客户端
  • 数据库
  • 桌面应用
  • CLI
  • 后端
  • MCP 服务
  • AI 应用

【免费下载链接】dbx

25 MB lightweight cross-platform database client for 90+ databases, including MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, SQL Server, and Dameng. Built-in AI, MCP Server, CLI, desktop and Docker. | 轻量级跨平台数据库管理工具,支持 MySQL、PostgreSQL、SQLite、Redis、MongoDB、达梦等 90+ 数据库,提供桌面端、Docker、CLI、内置 AI 助手和 MCP Server。

项目地址:https://gitcode.com/gh_mirrors/dbx7/dbx
点击查看免费下载

插件市场(Marketplace)是 dbx 桌面端分发插件的重要渠道,但真实的下载与安装数据此前一直没有可靠的统计手段。deploy/plugin-stats-worker/下的 Cloudflare Worker 解决了这个问题:它在不触碰.dbxp制品字节流的前提下,对每一次真实的插件下载(dl)、全新安装(inst)与版本更新(updt)进行计数,通过 Workers Analytics Engine 完成近乎免费的采集,再以每日 cron 聚合到 KV 命名空间形成永久归档。读完本文,你将掌握这套从采集、存储、聚合到查询的完整统计管线,以及它在 桌面端安装信标 一侧的接入方式与全部配置细节。

一、整体架构:一次请求两条路

该 Worker 部署在 dbxio.com 站点所属的 Cloudflare 账户下,核心设计原则是"计数与流量转发解耦"——统计逻辑只做轻量记录,绝不改写或阻塞真实流量。整体架构可概括为:

  • 下载计数dl.dbxio.com/plugins/*路由拦截每一个.dbxp制品的GET请求,记录事件后原样透传给 R2 自定义域名,字节、请求头与 Range 语义均不被改动;
  • 安装信标dbxio.com/api/plugins/install接收桌面端在安装成功后发送的 fire-and-forget POST 信标,返回 204 即完成使命;
  • 手动归档触发dbxio.com/api/plugins/archive提供 token 门控的手动聚合入口,用于验证与历史回填;
  • 存储分层:实时事件写入 Workers Analytics Engine(绑定PLUGIN_STATS),每日 cron(30 0 * * *UTC)将增量窗口聚合进plugin_stats_archiveKV 命名空间,形成永久层。

从源码看,路由分派逻辑集中在 worker.ts 的fetch入口:按url.hostname判断,dl.dbxio.com走下载计数路径,其余(即dbxio.com的 api 前缀)走handleInstallBeaconscheduledhandler 则承载 cron 触发的聚合任务。

二、下载计数路由:只数.dbxp,透传其余

2.1 为什么只统计.dbxp制品

插件市场的页面与应用视图会频繁拉取图标等静态资源,如果把这些请求也算进下载量,既会迅速烧穿 KV 配额(KV 免费额度每日仅 1000 次写入),又会严重虚高下载数字。因此该路由严格限定统计对象为真实制品文件。

const DOWNLOAD_PATTERN = /^\/plugins\/([A-Za-z0-9._-]{1,64})\/([0-9A-Za-z.+-]{1,32})\//; const ARTIFACT_SUFFIX_PATTERN = /\.dbxp$/;

其中DOWNLOAD_PATTERN同时解析出插件 id(第 1 组捕获)与版本号(第 2 组捕获),并对其做了长度与字符集约束(插件 id 最长 64 字符,版本最长 32 字符),ARTIFACT_SUFFIX_PATTERN确保只有以.dbxp结尾的路径才会计数。对应处理逻辑见 worker.ts:先匹配路径,再校验后缀,通过后调用recordEvent(env, "dl", pluginId, version, identity)写入事件,随后return fetch(request)将原始请求转发到 R2 自定义域名。同 zone 的子请求不会重新进入 Workers,因此该透传不会产生循环。

2.2 下载者的匿名身份

dl事件没有客户端生成的 id,其"独立下载者"统计依赖服务端计算的一天级 IP HMAC。实现见ipDayIdentity(worker.ts):以CF-Connecting-IP、当前 UTC 日期与STATS_SALT秘钥做 HMAC-SHA256,截取前 8 字节输出十六进制字符串。这样聚合端能按"身份"统计独立下载者,却无需存储任何明文 IP;由于盐值仅存在于 Worker 环境变量,外部无法通过枚举 IP 反推出真实值。源码注释也明确说明这是"防刷水位的弱约束"——攻击者更换 IP 仍可能虚增,对装饰性统计而言是可接受的。

三、安装信标路由:204 即完成的异步上报

桌面端在插件安装成功后,会向dbxio.com/api/plugins/install发送一个 fire-and-forget 的 POST 信标,请求体为:

{ "id": "<plugin id>", "version": "<version>", "kind": "install" | "update", "clientId": "<random uuid>" }

其中kindclientId为可选字段——旧版本客户端不发送它们。该接口是纯粹的装饰性统计:无鉴权、不采集 PII,clientId是应用本地生成的随机安装 id,既非硬件指纹也非用户指纹。

服务端校验逻辑(worker.ts)值得展开:

  • CORS 预检OPTIONS直接返回 204,响应头允许POST, OPTIONS方法与Content-Type头,Access-Control-Max-Age: 86400让浏览器缓存预检结果一天;
  • 体积上限Content-Length超过 512 字节返回 413,避免信标被滥用于投放大负载;
  • 字段校验id必须匹配PLUGIN_ID_PATTERNversion必须匹配VERSION_PATTERN,否则 400;
  • 事件归类kind === "update"记为updt,其余一切取值(包括旧版本缺省该字段的情况)都记为inst——这样历史混合事件基数保持连续,直到整个客户端舰队升级完毕;
  • 身份回退clientId若符合 UUID 格式则直接使用;否则(旧版本或本地存储不可用)回退到前述一天级 IP HMAC;
  • 成功语义:写入 Analytics Engine 后返回 204 空响应。

四、桌面端接入:一次 keepalive 的 fetch

统计信标的发送方在桌面端 pluginMarketplace.ts:

const INSTALL_BEACON_URL = "https://dbxio.com/api/plugins/install"; const INSTALLATION_ID_STORAGE_KEY = "dbx-installation-id"; export function beaconPluginInstall(pluginId: string, version: string, kind: PluginInstallBeaconKind = "install"): void { try { void fetch(INSTALL_BEACON_URL, { method: "POST", headers: { "Content-Type": "text/plain" }, body: JSON.stringify({ id: pluginId, version, kind, clientId: installationClientId() }), keepalive: true, }).catch(() => undefined); } catch { // Statistics are best-effort. } }

几个工程细节:

  • fire-and-forgetvoid fetch(...)不 await,任何异常(网络失败、服务器 5xx)都被静默吞掉,统计失败绝不影响安装流程本身;
  • keepalive: true:保证页面/应用在即将关闭时信标仍能发出;
  • 本地随机 id 复用installationClientId()先从localStorage读取dbx-installation-id,不存在或格式非法(非 UUID)则用uuid()重新生成并落盘,后续信标复用同一 id,从而在服务端形成"每台机器一个稳定身份"。清除本地存储或重装应用会重新生成——对装饰性统计是可接受的;
  • 更新与安装分离:默认kindinstall,升级场景显式传"update",避免更新流量虚增安装数字。

对应的单元测试 pluginMarketplaceBeacon.spec.ts 覆盖了五个关键行为:默认发送installkind、updatekind 透传、uuid 生成一次后跨信标复用、损坏的存储 id 会被重新生成、存储不可用时发送空clientId。这些测试从客户端侧印证了 Worker 侧kind/clientId可选字段设计的兼容性考量。

五、存储设计:为什么弃用 KV 计数器,改用 Analytics Engine

每个事件在 Analytics Engine(绑定PLUGIN_STATS)中写入一个数据点,字段结构为:

字段说明
blobs[kind, pluginId, version, identity]kinddl(制品 GET)/inst(全新安装,旧版本无 kind 的信标也归入此类,保持历史混合事件基数连续)/updt(版本更新);identity在有clientId时取客户端随机安装 id(每台机器稳定唯一),否则用一天级 IP HMAC(所有dl事件均使用后者)
doubles[1]事件计数
indexes[pluginId]以插件 id 为索引,保证按插件的查询分片良好

选择 Analytics Engine 而非 KV 计数器的理由在 README 与源码注释中均有明确交代:

  • KV 的 read-modify-write 模式每请求消耗 1 次写入,而免费额度仅每日 1000 次写入,远低于 3 万用户基数在热门插件发布日的流量——2026-09-15 当天仅图标流量就在数小时内烧穿了配额;
  • Analytics Engine 在此规模下写入近乎免费,且写入为追加式,无需先读后写,天然适配高并发事件流。

对应的绑定配置位于 wrangler.json:analytics_engine_datasets将绑定名PLUGIN_STATS映射到数据集名DBX_PLUGIN_STATS(SQL 查询时使用数据集名而非绑定名),kv_namespacesPLUGIN_ARCHIVE指向 KV 命名空间。

六、每日聚合:从三个月窗口到永久归档

Analytics Engine 仅保留三个月的数据,因此需要一个 cron 触发器把滚动窗口的数据沉淀到永久层。cron 表达式为30 0 * * *(UTC 每日 00:30),见 wrangler.json;触发逻辑在scheduledhandler 中通过ctx.waitUntil(runAggregation(env))异步执行。

6.1 KV 归档的键设计

聚合结果写入plugin_stats_archiveKV 命名空间,键结构如下:

内容写入语义
total:{kind}:{pluginId}:{version}按插件、按版本的全时段累计事件数累加
total:{kind}:{pluginId}按插件的全时段累计事件数(跨版本汇总)累加
summary单个 JSON 块{dl: {pluginId: n}, inst: {...}, updt: {...}},供单次读取展示合并后覆盖
uniqd:{kind}:{pluginId}:{day}按天的独立身份数(分类 inst/updt 事件为机器数,dl 事件为独立下载者数)覆盖(不是累加)
meta:last-success窗口标记所有写入落盘后才推进

6.2 窗口推进与失败恢复

runAggregation(worker.ts)的核心语义是增量窗口:聚合(meta:last-success, now]之间的数据,meta:last-success仅在全部写入成功后更新。这意味着:

  • 某次 cron 运行失败时,下次运行会重试同一窗口,不会丢数据;
  • 写入中途崩溃可能造成窗口数据被重复计数——对装饰性统计是可接受的;
  • 支持x-archive-from: reset头触发从 epoch 重新聚合(用于修复后回填),但由于总量是累加语义,仅应在确认归档窗口为空时使用;
  • meta:last-success损坏时直接抛错中止,避免静默错位。

6.3 独立身份的天级去重

天级独立数的查询实现(fetchDailyUniques,worker.ts)有个值得注意的细节:Analytics Engine 的 SQL 方言拒绝在GROUP BY中使用函数表达式,因此查询先按toStartOfHour(timestamp)分小时桶做count(DISTINCT blob4),再在 JS 侧合并成天级值。注释明确承认这种跨小时边界的身份会在两个桶中各计一次、略微高估天级独立数——对装饰性统计可接受,且天然免疫"同一天发布多个版本"导致的虚增。此外,2026-09-19 之前写入的旧事件没有 blob4 字段,会被blob4 != ''条件排除在独立数之外,但原始计数仍覆盖它们。

七、部署与配置:wrangler 实战

7.1 完整配置清单

wrangler.json 的完整配置如下:

{ "name": "dbx-plugin-stats", "compatibility_date": "2026-05-07", "main": "worker.ts", "routes": [ { "pattern": "dl.dbxio.com/plugins/*", "zone_name": "dbxio.com" }, { "pattern": "dbxio.com/api/plugins/*", "zone_name": "dbxio.com" } ], "analytics_engine_datasets": [ { "binding": "PLUGIN_STATS", "dataset": "DBX_PLUGIN_STATS" } ], "kv_namespaces": [ { "binding": "PLUGIN_ARCHIVE", "id": "0cc9e49a117f4edeb931464fa0256834" } ], "vars": { "CF_ACCOUNT_ID": "7ad3349ede62c17968a90108d39fd4d0" }, "triggers": { "crons": ["30 0 * * *"] } }

其中CF_ACCOUNT_ID与 KV 命名空间 id 由 wrangler.json 直接给出,部署时需替换为你自己账户下的实际值。

7.2 部署与密钥

部署命令:在该目录下执行npx wrangler deploy(需 OAuth 登录)。聚合流程访问 Analytics Engine SQL API 需要一个单独的 token,因为 OAuth 登录并不携带 analytics 作用域:

echo "<token>" | npx wrangler secret put ANALYTICS_TOKEN

token 需在 Cloudflare Dashboard 中创建,权限为Account → Analytics → Read。除 cron 外,同一个 token 也授权手动触发聚合(用于验证与回填):

curl -s -X POST https://dbxio.com/api/plugins/archive -H "x-archive-token: <token>"

该请求对应 Worker 中x-archive-token头与ANALYTICS_TOKEN的比对(不匹配返回 401),成功后返回 JSON 形式的聚合窗口摘要{from, to, activeKeys, uniqueDays},并带Cache-Control: no-store防止缓存。

7.3 必需的 Secrets 汇总

Env类型(worker.ts)定义了 Worker 的完整运行环境,其中需要人工配置的是两个 secret:

  • ANALYTICS_TOKEN:Analytics SQL API 访问 token(通过wrangler secret put写入);
  • STATS_SALT:IP HMAC 的盐值,保证一天级身份不可被外部推算(可同样通过wrangler secret put配置)。

CF_ACCOUNT_ID作为明文 var 提供,供 SQL API 的 URL 拼接使用。

八、读取计数器:SQL REST API 查询

当前没有 HTTP 统计端点(还没有消费方,市场 UI 上线时再加),需要直接通过 Cloudflare 的 SQL REST API 查询 Analytics Engine,token 需具备Account → Analytics → Read权限:

curl -s "https://api.cloudflare.com/client/v4/accounts/<account_id>/analytics_engine/sql" \ -H "Authorization: Bearer <token>" \ --data-urlencode "query=SELECT blob1 AS kind, blob2 AS plugin, blob3 AS version, SUM(_sample_interval) AS events FROM DBX_PLUGIN_STATS WHERE timestamp > NOW() - INTERVAL '7' DAY GROUP BY 1,2,3 ORDER BY events DESC"

几个由源码印证的关键点:

  • 表名用数据集名:SQL 中写的是DBX_PLUGIN_STATS(wrangler.json 的dataset),不是绑定名PLUGIN_STATS
  • query 是 URL 参数:SQL 语句通过 URL 参数传递而非表单体(表单编码的 body 会被解析为原始 SQL 并以 422 拒绝),这与 fetchWindowCounts 的实现一致;
  • SUM(_sample_interval)即事件数:当前采样比例为 1:1,因此该聚合值就等于事件计数;
  • 响应解析的容错设计parseSqlRows(worker.ts)同时兼容 ClickHouse 风格的{meta, data}(行可能是对象数组,也可能是需与 meta 逐列 zip 的数组)与纯{result}数组,格式漂移时降级而不是崩溃。

另外需要注意,计数器从 Analytics Engine 迁移之后才开始生效;KV 时代被污染的数字(plugin_stats命名空间)已被弃用,查询时不要混淆两个数据源。

九、设计取舍小结

这套统计管线的核心权衡值得借鉴:

  1. 计数与转发解耦:下载统计只读不改,字节、头、Range 语义原样透传,零性能影响;
  2. 存储分层:Analytics Engine 承接高频写入(近乎免费),KV 只承接每天每活跃键约 1 次写入的聚合结果,绕开 KV 免费配额瓶颈;
  3. 身份最小化:客户端随机 uuid 或一天级 IP HMAC,无 PII、无用户指纹,兼具去重能力与隐私下限;
  4. 窗口幂等语义meta:last-success标记 + 写后推进,失败自动重试同窗口;总量累加、天级独立数覆盖,语义清晰且对装饰性统计足够稳健;
  5. 向后兼容kindclientId可选字段让旧版客户端信标自动归入inst,历史基数不断裂。

这套实现为 dbx 的插件市场提供了从"下载 → 安装 → 更新"全链路的量化能力,也为后续市场 UI 上线时的展示层预留了清晰的 KV 读取结构(summary单读 JSON 与uniqd天级独立数)。

  • 数据库客户端
  • 数据库
  • 桌面应用
  • CLI
  • 后端
  • MCP 服务
  • AI 应用

【免费下载链接】dbx

25 MB lightweight cross-platform database client for 90+ databases, including MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, SQL Server, and Dameng. Built-in AI, MCP Server, CLI, desktop and Docker. | 轻量级跨平台数据库管理工具,支持 MySQL、PostgreSQL、SQLite、Redis、MongoDB、达梦等 90+ 数据库,提供桌面端、Docker、CLI、内置 AI 助手和 MCP Server。

项目地址:https://gitcode.com/gh_mirrors/dbx7/dbx
点击查看免费下载
上一篇:告别繁琐上传!UEditor图片拖拽上传功能实现:HTML5 File API全解析
下一篇:如何打造独特的FlexSlider轮播控制界面:自定义导航完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询