我如何使用 Laravel Horizon、Redis 和极简 REST API 构建漏斗分析引擎

发布: (2026年3月29日 GMT+8 02:35)
6 分钟阅读
原文: Dev.to

Source: Dev.to

核心问题

漏斗分析听起来很简单:用户先做 A,然后是 B,最后是 C。有什么比例的用户能够从 A 到达 C?他们在哪一步掉队?

实际上,要做好这件事相当棘手:

  • 事件是异步到达且可能乱序
  • 漏斗需要具备灵活性——不同的步骤、不同的时间范围
  • 计算必须快速,即使面对成千上万的事件
  • 开发者的集成工作量必须尽可能少

目标: 开发者应能在 5 分钟内通过一次 HTTP POST 开始跟踪。

架构概览

Browser/App → REST API → Event Storage → Queue → Funnel Engine → Dashboard

技术栈

  • 后端: Laravel 12 与模块化架构 (nwidart/laravel-modules)
  • 队列: Laravel Horizon + Redis
  • 前端: Next.js 14 + TypeScript
  • 数据库: MariaDB
  • 支付: Stripe

API层

集成故意保持最小化:

POST /api/v1/events
X-OL-Tenant-Key: your-tenant-key
X-OL-App-Key: your-app-key
Content-Type: application/json

{
  "event_name": "signup_completed",
  "user_identifier": "user_123",
  "metadata": {
    "plan": "pro",
    "source": "landing_page"
  }
}

两个请求头,一个 JSON 正文——这就是全部契约。
控制器会验证密钥,识别租户和被跟踪的应用,持久化事件,并立即返回 200。在请求生命周期中没有繁重的处理。

漏斗引擎

当用户在仪表盘中构建漏斗时,例如 visited_pricing → started_trial → upgraded,系统必须计算每一步的转化率。

所有事件写入都会调度一个后台任务:

ProcessFunnelEngineJob::dispatch($event)->onQueue('funnels');

该任务:

  1. 取出事件。
  2. 加载关联应用的所有漏斗。
  3. 使用滑动窗口方法重新计算每一步的转化率。
  4. 将结果缓存,以供仪表盘使用。

为什么使用队列?

  • 性能: 无论需要重新计算多少个漏斗,API 响应都保持快速。
  • 弹性: 任务失败会自动重试;对瞬时错误不会导致数据丢失。

Laravel Horizon 提供实时仪表盘,可监控任务吞吐量、失败情况和队列深度,无需额外基础设施。

多租户

Tracetics 设计上是多租户的。每个租户(公司)可以拥有多个 TrackedApps,每个都有自己的事件和漏斗。层级结构:

Tenant
 └── TrackedApp (X-OL-App-Key)
      └── Events
      └── Funnels
           └── FunnelSteps

认证使用两个 Header:

  • X-OL-Tenant-Key — 标识租户
  • X-OL-App-Key — 标识事件所属的应用

这使得开发者的集成保持简洁,同时在后端强制严格的数据隔离。

计划限制与计费

每个订阅计划定义了 TrackedApps、Funnels 和每月 Events 的限制。限制在控制器层面执行,在任何写入操作之前进行检查:

if ($tenant->trackedApps()->count() >= $tenant->plan->app_limit) {
    return response()->json(['error' => 'App limit reached'], 403);
}

Stripe 通过 webhook 处理订阅。一个值得注意的挑战是:Stripe 的新 API 将 current_period_end 移动到了 items.data[0] 中,而不是订阅根对象——这是一个细微的破坏性更改,导致我们花了一个小时进行调试。

TypeScript SDK

对于更喜欢使用类型化客户端而非原始 HTTP 的开发者,提供了一个 TypeScript SDK:

npm install tracetics-sdk
import { Tracetics } from 'tracetics-sdk';

const client = new Tracetics({
  tenantKey: 'your-tenant-key',
  trackedAppKey: 'your-app-key',
  endpoint: 'https://tracetics.com'
});

await client.track({
  event_name: 'signup_completed',
  user_identifier: 'user_123'
});

该 SDK 使用 tsup 构建 — 双 ESM/CJS 输出,完整的 TypeScript 类型,且零依赖。

我学到的

  1. 保持写入路径简洁快速。
    验证、持久化、入队。其他所有工作都交给队列。

  2. 先队列的架构立刻见效。
    漏斗计算永不阻塞 API 响应,且各关注点保持良好分离。

  3. Stripe Webhook 不是可选的。
    仅依赖基于重定向的确认会导致订阅状态不可靠。

  4. 明确的键层次结构让多租户更容易。
    明确的 Tenant → App → Event 所有权使权限检查变得轻而易举,数据隔离坚如磐石。

What’s Next

Tracetics 已上线,提供免费计划。TypeScript SDK 在 npm 上,名称为 tracetics-sdk

如果您构建了类似的项目或对架构有疑问,欢迎在评论中分享您的想法。

0 浏览
Back to Blog

相关文章

阅读更多 »

为什么学习 Node.js?

为什么要学习 Node.js?如果你正踏入开发世界或想要提升为程序员,学习 Node.js 可以成为最具战略性的决定之一……

为什么学习 node.js

为什么学习 Node.js?🚀 如果你正在进入开发世界或想提升为程序员,学习 Node.js 可能是最…