site logo

Marico's space

我们在摩尔多瓦构建清醒司机预约系统的经验:使用 Node.js、Supabase 和 Vercel 实现实时调度

服务器技术 2026-04-26 17:45:41 null

2024年9月,摩尔多瓦把醉驾从行政违法重新划为刑事犯罪——最高4年监禁,罚款高达150,000 MDL(约3,000欧元)。

就这么一个法律变化,让当地的代驾服务市场一夜之间爆发。作为 PlusRent(基什尼乌的一家租车兼代驾平台)的团队,我们在周五周六晚高峰(22:00-04:00)的预订量一下子涨了3-5倍。

我们原有的人工调度系统——电话、WhatsApp消息、Excel表格——几周内就成了瓶颈。必须搞个更好的。

这篇文章记录我们如何构建一个实时代驾预约系统,处理调度、GPS追踪、ETA计算和支付集成——用 serverless 架构,创业阶段的预算。

我们面对的问题

"代驾"服务有传统网约车没有的特殊约束:

  • 司机开的是客户的车,不是自己的
  • 客户是醉酒状态——UX 必须简单到不会出错
  • 高峰时间极端——80%的预订发生在6小时窗口内
  • 地理位置精度很重要——餐厅、婚礼、私人地址
  • 信任至关重要——司机拿到的是昂贵车辆钥匙

Yandex Go 或 Uber 在摩尔多瓦没有这个服务。竞争对手用 WhatsApp + 人工调度——慢、容易出错、没法scale。

我们需要:

  • 30秒内确认的实时预订
  • 司机 GPS 实时追踪
  • 自动分配给最近的可用司机
  • 客户可见的 ETA 更新
  • 多语言 UI(罗马尼亚语、俄语、英语)
  • 支付选项(现金 + 卡)

而且预算还不能让小创业公司破产。

技术栈

评估后我们选了:

  • 前端:Next.js 14(App Router)
  • 托管:Vercel(Edge Functions)
  • 后端:Node.js + Supabase Edge Functions
  • 数据库:Supabase(PostgreSQL + Realtime)
  • 认证:Supabase Auth
  • 地图:Google Maps Platform
  • 短信:Twilio
  • 支付:Stripe + 现金处理
  • 监控:Sentry + Vercel Analytics

为什么是这个栈?

Vercel + Next.js:零配置部署,边缘函数实现全球<100ms 响应。

Supabase:PostgreSQL + Realtime WebSockets 在一个服务里。替代了我们原本需要的3-4个独立服务。

Google Maps:在摩尔多瓦地址覆盖上,没有接近的替代品。

MVP 阶段基础设施每月总成本:约40美元。扩展到当前量级后:约180美元/月。

数据库 schema:基础

数据模型需要处理三个主要实体及其关系。用了 PostGIS 扩展,地理位置查询变得很轻松。找距取车点最近的5个可用司机变成了一条查询:

SELECT
  id,
  full_name,
  ST_Distance(current_location, $1::geography) AS distance_meters
FROM drivers
WHERE status = 'available'
  AND ST_DWithin(current_location, $1::geography, 5000) -- 5km radius
ORDER BY current_location <-> $1::geography
LIMIT 5;

即使历史记录有数以千计的预订,这个查询也只需要3-8ms。

调度算法

最难的工程问题是调度算法。简单的"最近司机"不行,因为:

  • 距离1公里评分4.2的司机,不如1.5公里评分4.9的
  • 刚完成2公里外订单的司机会拒绝;在idle状态20分钟的司机会接单
  • 有些司机偏好特定区域;周五晚不应该派给新司机去刁难的地区

最终我们用了一个加权评分算法。

30秒的接单窗口加顺序 fallback 非常关键。客户不会等超过60秒来确认。

Supabase 实时更新

客户体验需要实时更新:"您的司机还有3分钟到" → "您的司机已到达" → "行程进行中"。

Supabase 的 realtime PostgreSQL 订阅让这个出奇地简单。

对于司机位置更新,我们最初也用 PostgreSQL realtime——每5秒发一次位置。这在规模上来把数据库连接冲垮了。

修复:一个独立的 Supabase Edge Function 写司机位置到类似 Redis 的缓存(我们用 Upstash),每30秒才持久化到 PostgreSQL 用于分析。实时位置通过 WebSocket 直传。

这一下把数据库负载降了95%,同时保持了亚秒级更新延迟。

多语言支持:隐藏的挑战

摩尔多瓦有3种活跃语言:罗马尼亚语(官方)、俄语(广泛使用)和英语(给游客)。

数据库层面建立了语言矩阵:每个用户/司机有 preferred_language 字段。短信、推送通知、邮件和 PDF 都查这张表。客户 UI 用 Next.js i18n,但服务端生成的内容(通知、收据)走数据库。

一个额外好处:让客服人员可以实时编辑翻译,而不用重新部署。

支付集成:卡+现金的现实

西方教程假设只用 Stripe。摩尔多瓦70%的支付是现金。这需要双流程支持。

现金处理需要额外复杂性:司机班次对账、每日结算、"信任评分"系统来标记有持续现金差异的司机。

部署与监控

用 Vercel + Supabase 部署简单得过分:

推送 main 分支,Vercel 自动构建 Next.js 应用、运行 Supabase 迁移、部署 edge functions、更新 DNS。

但要注意:edge functions 有10秒执行限制。长时运行操作(司机付款、每日对账)必须移到预定的 Supabase functions 或外部 workers。

Sentry 抓到了大部分生产问题,但最有价值的监控是定制的:实时 dashboard 显示活跃预订、平均 ETA、司机利用率和调度成功率。当 dispatch_success_rate 跌破85%时,警报触发。

如果重新来过会怎么做不同

如果今天重新开始,我们会考虑:

  • 用 TanStack Query 而不是原生 React state 来处理预订数据——我们遇到了手动实现的缓存失效问题
  • 后端函数用 Bun 而不是 Node.js——冷启动快约3倍
  • 用 PartyKit 或 Cloudflare Durable Objects 做实时位置——而不是 Upstash + Edge Functions——架构更简单
  • 从第一天就用 PostHog 做产品分析——我们在6个月后才加,损失了宝贵的行为数据

不后悔选 Supabase。PostgreSQL + Auth + Realtime + Storage + Edge Functions 在一个服务里,省了我们好几个月的集成工作。

12个月后的结果

  • 平均调度时间:22秒(从人工的8分钟以上降下来的)
  • 预订到接单时间:平均18分钟
  • 客户满意度:4.9/5(基于27+条 Google 评论)
  • 司机留存率:3个月后85%(行业平均约50%)
  • 峰值并发预订:周末晚40+单
  • 每单基础设施成本:约0.12美元

系统处理当前量级游刃有余。下一个瓶颈将是人的(司机容量),而不是技术的。

最后想法

在一个250万人口的市场构建代驾平台教会我们:"小市场"不等于"简单问题"。法律背景、语言复杂性、支付文化和信任动态需要的工程解决方案,在典型 SaaS 手册里找不到。

给在非西方市场构建本地服务的技术创始人:不要过度依赖硅谷模式。现金支付是真实的。SMS 对于较年长的人群仍然胜过推送通知。本地语言细节比你想象的更重要。

完整技术栈——Node.js、Supabase、Vercel、Next.js——证明了小团队可以在没有企业级预算的情况下交付重量级基础设施。

译者按:原文 https://dev.to/bostan/how-we-built-a-sober-driver-booking-system-in-moldova-real-time-dispatch-with-nodejs-supabase--418a