每个加油 App 都会列价格。Revala 回答的是你在车上真正想问的—— 这条路上,哪一站值得停。
它在 2026 年 3 月以 Servo Map 起步,是一个澳洲油价地图,后来长成三部分:iPhone App、在 Cloudflare 上读取所有官方油价数据的 core,以及只有我能打开的 console。10 月加入英国,11 月加入充电桩,名字也改成了 Revala。
- 技术栈
- SwiftUI
- ·Cloudflare Workers
- ·Hono
- ·D1
- ·R2
- ·Next.js
- 分类
- iOS
为什么做
3 个问题只会列价格同类 App 大多是很多年前设计的,看起来也是:一张数字表格,看不出品牌、也不给答案,行密、按钮小,在车里很难用。最早的决定之一,就是给地图上每个站挂上品牌 logo,再把最便宜的一家标出来,读之前就能看到答案。
真实需求是「顺路」我很少专门出门加油,通常是回家路上顺便加。所以问题不是「离我最近的是哪家」,而是「这条路上哪家值得停」:沿路线找站、下一站的实时活动、只算到达时还在营业的站,以及一键选择的快捷答案 —— Cheapest、Balanced、Quickest。
没人算过这笔账绕路对你自己的油箱划不划算、一个地区星期几最便宜、一年下来每次加油到底花了多少 —— 我手上的 App 都没有。Revala 用它保存的价格历史把这些算出来,停车加完油就能顺手记一笔。
在手机上
iPhone · 澳洲它回答的三个问题,每个都是地图上的一张卡片。
能开到吗?加一次,在哪加价格放在附近均价里看值得开过去吗?多 17 公里,不值 从一个州的数据,到两个国家
2026 年 3 – 11 月每一步都是把底座变宽,而不是在上面再加一个特例。
1一个澳洲油价地图3 月。八个州和领地各有各的油价接口、各有各的格式;core 的第一件事,是把它们读成一份数据。
2原生 iPhone App9 月。基于 iOS 26 的 SwiftUI,带 CarPlay、小组件、手表 App 和下一站的实时活动。
3国家就是一份数据10 月。没有把英国做成「第二个澳洲」,而是把底座改成所有国家通用的一套规则 —— 地区、货币、单位、时区都是数据 —— 第一个接入的是英国的 Fuel Finder。AUUK
4加入充电桩,由车来决定11 月。充电桩成为一个国家用数据就能加上的图层,第一个问题变成「你开什么车」:电车打开就是充电桩,油车就是加油站。
5Servo Map 改名 Revala「servo」是澳洲人对加油站的叫法;一个不再只属于澳洲的 App,需要一个走得出去的名字。
同一套代码,换一个国家
澳洲 · 英国澳洲和英国的趋势页,以及英国的品牌页 —— 同一套代码,换了一个国家的数据。
加油站和充电桩,是两种问题
4 个决定油价按批次读取
定时器每五分钟醒一次,只读当下到期的数据源,一次读整个州;各州的发布节奏不同,从昆士兰的十分钟到塔斯马尼亚的四小时。
充电桩是另一种东西
充电站点从来不是加油站:它按 OCPI 2.2.1 建模,有接口、费率和实时状态,按协议而不是按国家读取 —— 任何国家多一家 OCPI 运营商,只是多一行数据。
营业时间要自己找
大多数官方数据不带营业时间,而「到达时还开着」需要它,所以从品牌官网的站点查询、FuelCheck 网站和 OpenStreetMap 收集,并把公共假期算进去。
许可条款决定缓存时长
昆士兰的数据许可要求价格变动三十分钟内展示、且不得转载,所以它前面的每一层缓存,都是按这个上限倒推出来的。
每一次请求都必要吗?
按百万用户设计按百万用户算账
上线前,我用一周的真实流量测量了每个 Worker、KV 和 D1 数据库,并算出同一套系统在一百万用户时的成本;由此定下的目标是:一百万日活司机时,Cloudflare 每月不超过 100 美元。
只算一次,从边缘分发
一个地区的油价对所有人都一样,所以每次读取后只写一次,存成 R2 上的文件,经 CDN 带签名分发。App 先读这里,API 只做兜底。
手机持有整个州
设备一次持有整个州的站点,开车时只按时间刷新价格;挪动地图不再向服务器请求任何东西。
下一步:只传变化
现在一个加油站改一个价,仍会重发整个州。只传变化的价格正在做:以新州一个平静的晚上为例,是 0.7 KB 而不是 66 KB。
一个价格从哪里来
8 个官方数据源一个价格走过的路径:从数据源读取,入库前先判断,只存一次,再从边缘分发到每一个客户端。如果一次读取的站点数不到上次的一半,就拒绝写入,而不是覆盖掉好的数据。
取自控制台的数据流视图,重新画成一个价格走过的路径。
用一个控制台把它运营起来
运营一个没有自己地址的控制台
控制台是一个独立的 Next.js 应用,放在 Cloudflare Access 后面。它自己不持有任何数据库:每一次查询和写入都归 core 所有,暴露在一个没有公网地址的入口上 —— 只有控制台的服务绑定能访问到。
挂了、旧了、慢了 —— 我先知道
一个独立于 App 的监控探测服务并发布状态;控制台显示每一项检查、最近九十天的事故和每天的通过率。价格变旧或响应变慢时,会在司机察觉之前先给我发邮件。
在哪看到,就在哪修
失败的后台任务可以直接在它那一行重新运行,只限白名单里的任务,有风险的选项要二次确认。站点信息错了,就写一条覆盖所有数据源、下一次采集也不会冲掉的修正,每一次修改都留下谁改了什么的记录。
每个月花了多少
成本与配额在同一页,和每周发到我邮箱的周报用的是同一套计算:可以看任意月份、按产品拆分、每项额度的用量,以及发票本身。
上线靠开关,不靠发版
每个国家、每个功能都有一个阶段 —— 关闭、内部、测试、上线 —— 在控制台里切换,所以一个国家可以先对 TestFlight 开放、再对所有人开放,不需要重新发版。同一个页面还从 App Store Connect 读取 TestFlight 和 App Store 的状态。
让 agent 在边界里工作
200 多份决策记录先写决策记录,再写代码
Revala 大部分代码是和编程 agent 一起写的,所以每个功能都先写一份编号的决策记录 —— 问题、选项、我的选择 —— 然后才实现。目前已经有两百多份。
每个事实只归 core 所有
品牌、国家、单位,以及每个国家不同的说法,都只在 core 里存一份,再生成进 App。core 还发布一份清单,列出每个接口和共享函数;任何一个缺失或改名,iPhone App 的 CI 都会立刻失败。
把 AI 拿掉,也是一个决定
App 里的助手一度长成了一个服务端 agent,用大约四百个评测用例把关。11 月我把它关掉了:快捷答案改为在手机上用 App 自己的算法得出,设备支持时由 Apple Intelligence 理解问题。
技术栈
3 行- iPhone
- SwiftUI
- ·MapKit
- ·WidgetKit
- ·ActivityKit
- ·CarPlay
- ·watchOS
iOS 26;真机性能数据来自 MetricKit,不接任何第三方 SDK。
- Core
- Cloudflare Workers
- ·Hono
- ·D1
- ·KV
- ·R2
- ·Cron Triggers
- ·Analytics Engine
没有一台自己的服务器。
- 网站与控制台
revala.app(每个国家一个站点),以及放在 Cloudflare Access 后面的控制台。
现在的状态
Revala 目前覆盖澳洲和英国的油价,以及英国的充电桩。iPhone App 正在 App Store 审核中,期间通过 TestFlight 发布测试版本;哪个国家、哪个功能对用户开放,是在控制台里切换的,不需要重新发版。