国庆节假日出行,我用华为云码道做了个“该不该现在下高速“的鸿蒙元服务

国庆节假日出行总是被堵在路上,历经各种各样的事,高速服务区休息打盹地一小会,我思考了一个非常硬核的问题,于是便有了这篇文章。
摘要
高速免费只看驶离出口的时刻,与上高速时间无关。临近截止赶不到目的地时,"就近下去等过 0 时再上"能省下已行驶里程的费用。本文用纯逻辑规则引擎 + 25 项断言算清这道题,跑起来抓到 3 个真实 bug,再交给华为云码道生成鸿蒙元服务版,全程零权限、纯离线,并刻意拒绝"加速赶路"选项。
目录
- 一、起因:我发现所有人都在算这道题,而且算错
- 二、为什么是元服务,而不是一个 App
- 三、先写引擎,再写界面
- 四、跑起来之后,它抓到我自己三个 bug
- 五、交给华为云码道做鸿蒙版
- 六、这个产品拒绝提供的那个选项
- 七、复盘:码道强在哪,又在哪把你骗过去
- 八、产物
- 九、总结## 一、起因:我发现所有人都在算这道题,而且算错
10 月 7 号晚上九点多,我在家族群里看到有人问:“现在从 XX 下高速还来得及吗?”
底下七嘴八舌,说什么的都有。我去查了规则,发现真正的答案比所有人想的都更不直观:
**高速免不免费,只看一个时刻——你驶离出口收费车道的时间。**跟你几点上的高速没有任何关系。
由此推出三条,我保证大部分人只知道第一条:
- **提前上高速是对的。**9 月 30 日晚上驶入,只要 10 月 1 日 0 时之后才下,全程免费。
- 压哨下高速是最亏的。窗口内上了高速、10 月 7 日 24 时之后才下,收的是全程通行费,不是超出的那一段。
- **正解是"中途先下去"。**临近截止还到不了目的地,就近驶出收费站,等过了 0 时再重新驶入——前面那段因为驶离时刻在窗口内而全免,你只需要付最后一段。
还有两个每年都有人中招的坑:只有春节、清明、劳动节、国庆四个节假日免费,中秋、元旦、端午照常收费(国发〔2012〕37 号就列了那四个);以及人工道进就得人工道出、ETC 进就得 ETC 出,错配可能导致系统读不到入口信息、抬杆失败。
规则是清楚的。可是没有人会在高速上、在疲惫中、在导航催你的时候,冷静地算"我剩余 260 公里、还要开 195 分钟、已经开了 800 公里、现在 22:00,我到底该不该在这个出口下去"。

**这道题需要被算,而且需要被很快地算出来。**所以我做了个工具。
使用华为云码道对话开发过程如下,非常轻松:

对话过程中,华为云码道AI不仅深度思考,还非常贴心地提供了选项让我选择,以便明确需求,我觉得这点非常好!

选择数据输入方式:

视觉风格:

代码验证:

完成功能开发如下:

二、为什么是元服务,而不是一个 App
| 这个需求的特征 | 对应的鸿蒙能力 |
|---|---|
| 一年只用四次,绝不肯为它装 App | 元服务免安装,负一屏/扫码直达 |
| 决定发生在没解锁手机的那一刻 | 服务卡片常驻桌面 |
| 隧道里、山区没信号也要能用 | 纯离线,零网络请求 |
| 输入只是里程和时刻,不需要位置 | 不申请任何权限 |
最后一条我特意做成了硬约束:全工程 requestPermissions 必须是空的。一个只需要你手动填三个数字的工具,没有任何理由去读你的位置。
三、先写引擎,再写界面
我没有一上来就画界面,而是先把规则写成一份纯逻辑的规则引擎:不碰 DOM、不碰系统 API、只有函数和数字。这样做的唯一理由是——它能被测试。
引擎对外只暴露一个函数:
TollFree.decide({
nowISO, windowEndISO, // 现在时刻 / 免费截止时刻
etaMin, // 导航显示还要开多久
remainingKm, // 剩余里程
drivenKm, // 本次已行驶里程(决定"下去再上"能省多少)
ratePerKm, useEtc, laneIn
})
// → { code, bufferMin, fullCost, lastLeg, saved, waitMin,
// valuePerWaitMin, advice[], warnings[] }
费率口径全部来自公开整理:一类小客车四车道约 0.45 元/公里、六车道及以上约 0.6 元/公里、山区桥隧更高,日常预算按 0.5 估;收费时段 ETC 不低于 95 折,免费时段无差别。
然后写了 25 项断言。其中最关键的是这个恒等式——"下去再上"省下的钱,恰好等于已行驶里程的费用,与剩余里程无关:
const k1 = at('2026-10-07T22:00', 800, 260);
const k2 = at('2026-10-07T22:00', 800, 600);
assert(k1.saved === 400); // 800 km × 0.5
assert(k2.saved === k1.saved); // 剩余 600 还是 260,省的一样多
跑通之后,我把它喂了八组真实场景,得到这张矩阵——这也是本文最想说清楚的一张表:
| 时刻 | 已行驶 | 剩余 | 预计驶离 | 缓冲 | 硬撑要付 | 下去再上 | 省下 | 要等 | 判定 |
|---|---|---|---|---|---|---|---|---|---|
| 10/7 15:00 | 300 km | 700 km | 23:48 | +12 min | 500 元 | 350 元 | 150 元 | 540 min | 缓冲太薄 |
| 10/7 20:00 | 600 km | 200 km | 22:33 | +87 min | 400 元 | 100 元 | 300 元 | 240 min | 赶得上 |
| 10/7 21:00 | 700 km | 250 km | 00:11 | −11 min | 475 元 | 125 元 | 350 元 | 180 min | 建议现在下 |
| 10/7 22:00 | 800 km | 260 km | 01:18 | −78 min | 530 元 | 130 元 | 400 元 | 120 min | 建议现在下 |
| 10/7 23:20 | 1000 km | 90 km | 00:31 | −31 min | 545 元 | 45 元 | 500 元 | 40 min | 建议现在下 |
| 10/7 23:50 | 1000 km | 20 km | 00:08 | −8 min | 510 元 | 10 元 | 500 元 | 10 min | 建议现在下 |
| 10/8 00:20 | 1000 km | 60 km | 01:08 | −68 min | 530 元 | 530 元 | 0 元 | 0 min | 已过期 |
如何读这张表:每一行代表一个真实场景。
「时刻」是你现在的时间;
「已行驶 / 剩余」是本次已开和还要开的里程;
「预计驶离」是按导航 ETA 推算的出口时刻;
「缓冲」是预计驶离与 24:00 的差值,正数表示赶得上、负数表示来不及;
「硬撑要付」是硬开到目的地的全程费用;
「下去再上」是就近下高速、等过 0 时再上的费用;
「省下」是两者之差;
「要等」是需要在服务区等待的分钟数;
「判定」是引擎给出的建议。
重点看最后三行:23:50 时你已开 1000 公里、只剩 20 公里,下去等 10 分钟就能省 500 元;可一旦拖到 00:20,免费窗口已经关闭,下去再上要付全程 530 元,一分钱都省不下。
我来把它画的更直观一点:

决策窗口只有那几十分钟——不是几个小时,更不是一晚上。这正是深夜高速上最疲惫、最想赌一把的时刻,也是这个工具存在的意义:在侥幸之前,先把账算清楚。
看最后三行,这是整件事最残酷的地方:**23:50 下去等 10 分钟省 500 元;00:20 才反应过来,一分钱都省不下了。**决策窗口只有那么窄的几十分钟,而这恰恰是人最疲惫、最容易心存侥幸的时候。

四、跑起来之后,它抓到我自己三个 bug
这部分是我写这篇东西最想写的。三个 bug 都不是读代码读出来的,是把东西跑起来、盯着输出看才暴露的。
Bug 1:已经过期了,它还告诉你"可省 500 元"
saved 我统一按 已行驶里程 × 费率 算。但 10 月 8 日凌晨的场景里,你现在下和开到目的地都要付全程,省钱空间是 0,引擎却照样报"可省 500 元"。
这不是数字难看的问题——**它会直接误导一个正在深夜高速上开车的人多做一次错误决策。**修法是把这个分支提到最前面单独返回,saved 强制为 0,并把文案改成"已经没有任何省钱空间了,所以完全没有赶路的理由"。
修完补了两条断言把它锁死:
const late = at('2026-10-08T00:20', 1000, 60);
assert(late.saved === 0);
assert(late.lastLeg === late.fullCost); // 没有省钱空间
Bug 2:"不值得折腾"的保护漏了一条路径
我原本只在"确定赶不上"的分支里做了性价比判断:如果等 4 小时只为省 15 元,就别折腾了。
但输出里有一行让我惊呆了:
10/7 15:00 300km 700km 缓冲 +12min 省下 150元 要等 540min → 缓冲太薄
建议是"就近出口先下去等 9 小时省 150 元"。**荒谬。**保护逻辑只挂在一条分支上,"赶得上但余量太小"这条路径绕过了它。补上之后,同一场景的建议变成:不建议下去等,把导航 ETA 当保守值用,中途该休息就休息。

补上之后,同一场景的建议变成:不建议下去等,把导航 ETA 当保守值用,中途该休息就休息。判据很朴素——每等一分钟,至少要替我省回五毛钱。
Bug 3:决策说"你赶不上",倒计时却显示"还剩 74 小时"
因为倒计时读的是真实系统时钟,而决策读的是用户在"现在时刻"里填的时间。用户手动改时刻(提前规划、事后复盘)时,两处当场自相矛盾。改成同一个时间源,并在每次输入后立刻同步刷新。
顺带一个可移植性隐患
我原先把截止时刻写成 2026-10-07T24:00:00+08:00。V8 能解析(我验证过,它等于次日 00:00),但 24:00 属于 ISO 8601 的扩展语法,ArkTS 运行时不保证支持。趁还没移植,统一换成等价但无歧义的 2026-10-08T00:00:00+08:00。
这种问题在浏览器里跑得好好的东西,到了真机上变成 Invalid Date,是最难查的一类。
五、交给华为云码道做鸿蒙版
引擎和原型是我自己的参照答案。鸿蒙版我打算丢给码道做,纪律是一轮只做一件事,而且每一轮我都有东西可对。
R1 建工程,第一件事是卡权限
建 HarmonyOS 元服务工程,Stage 模型,API 12。全工程不申请任何
ohos.permission.*,不发任何网络请求。请明确列出module.json5里的requestPermissions内容以证明这一点。
【填 1:贴码道第一次生成的 module.json5 片段。它有没有顺手加 INTERNET?你是怎么发现的?】
R2 移植规则引擎
把
engine.js逐函数移植成model/TollFreeEngine.ets,纯逻辑,不 import 任何 UI 或系统模块。函数签名和返回字段名保持一致。时间一律用带+08:00显式偏移的 ISO 字符串,禁止T24:00写法。
验收标准是把那 25 项断言移植成 hypium 单测,必须全绿。重点盯 ALREADY_LATE 分支的 saved 是不是 0——我自己刚在这儿栽过。
【填 2:ArkTS 严格模式第一次报了什么错?把报错原文贴出来。它有没有用 any / ESObject 绕过?是你指出来的还是它自己改对的?】
R3–R4 输入页与结论卡
数字输入用大号字体,考虑行车中单手操作。结论卡四态用左边框色带区分,底部固定一条安全声明,任何状态下都显示。
【填 3:状态管理它用的是 V1(@State/@Prop)还是 V2(@ObservedV2/@Trace)?有没有混用?混用会静默不刷新——你遇到这个现象了吗?】
R5 服务卡片(真正的硬骨头)
我原本设想的杀手级功能是"卡片上常驻秒级倒计时"。但据我了解,鸿蒙服务卡片的刷新频率下限是 30 分钟一次,卡片进程里也不能自己跑定时器。也就是说,秒级倒计时这个功能平台不让你做。
【⚠ 填 4:这条限制我来自既有认知,请你在 DevEco 官方文档核实后把原文和版本号补在这里。如果实际能秒级刷新,本节结论要改写。】
如果限制属实,设计必须改道,我的方案是卡片给档位、通知给精度:
- 卡片只显示
< 1 小时 / 1–3 小时 / 3–12 小时 / > 12 小时 / 已结束这样的档位,配一个醒目的截止时刻10/8 00:00。30 分钟刷新一次,足够让档位正确跳变。 - 秒级精度交给本地通知:截止前 180 / 60 / 30 分钟各推一条,内容带上"现在下高速可省 X 元"。
- 实况窗(Live View Kit)能做到准实时,但需要行业资质申请,个人开发者大概率拿不到——先按拿不到设计,别把它写进依赖。
【填 5:这一段是真机跑出来的结论还是推测?卡片实际刷新间隔是多少?截图。】
这一轮是我最想写的部分:不是"AI 帮我把功能做出来了",而是"我想要的功能和平台允许的功能不一样,我怎么改设计"。
实现应用界面如下:



六、这个产品拒绝提供的那个选项
值得单独说一句。
任何"高速免费截止倒计时"类产品,最讨好用户的设计是告诉你"还有 22 分钟,抓紧开还来得及"。
**我明确不做这个。**界面上有一条固定显示、任何状态都不消失的声明:
🛡 这个工具不会给你"加速赶在 24 点前下高速"这个选项。省两百块钱不值得拿命换。
技术上它体现为:缓冲时间不足 15 分钟时,引擎不会说"赶得上",而是说"缓冲太薄,建议就近下去";已经过期时,它把 saved 归零并告诉你"现在没有任何省钱空间了,所以完全没有赶路的理由"。
写完这个我意识到,Bug 1 之所以严重,不是数字算错,而是那个错误恰好会把人推向超速。好的产品立场不是喊出来的,是写在分支判断里的。
七、复盘:码道强在哪,又在哪把你骗过去
【填 6:以下三点请根据你的真实体验改写或推翻,不要照抄我的推测】
- 强的地方:ArkTS 语法层面的合规性、元服务工程骨架、目录结构。
- 需要盯着的地方:凡是"必须跑起来才知道对错"的问题——时间解析、卡片刷新、状态管理——它给的代码都是"看起来对"的。
- 我的实际体感:AI 把写代码的成本压得很低,但没有压低判断代码对不对的成本。所以我先花半天写了一份可测试的参照答案,再让码道去逼近它。这份卷子的价值,比它帮我敲的那几千行代码大。
八、产物
- 仓库:<https://atomgit.com/guzhangMVP/highway>
web/engine.js规则引擎(纯逻辑,195 行)web/test.js25 项断言,node test.js直接跑web/smoke.js无浏览器冒烟测试web/decision_matrix.txt上面那张表的原始输出web/index.html在线试算页,双击即用docs/handoff.md鸿蒙版八轮提示词与验收标准
在仓库可以看到:

九、总结
这道题本身并不复杂——高速免不免费只看驶离出口的时刻。真正难的是在深夜高速上、在疲惫和侥幸里,冷静算出"该不该现在下去"。所以我把它写成一份可测试的规则引擎,用 25 项断言锁死逻辑,跑起来抓到 3 个真实 bug,再交给华为云码道生成鸿蒙元服务版。
回头看,最有价值的不是那几千行代码,而是两件事:先写可测试的参照答案,再让 AI 去逼近它——这让我能一眼看出它哪里"看起来对";以及把产品立场写进分支判断——不给你"加速赶路"的选项,省两百块钱不值得拿命换。
如果你也在高速上遇到过这道题,希望这个工具能帮你少一点纠结、多一点从容。下次免费窗口,记得:赶不上就就近下去,等过了 0 时再上。
更多推荐



所有评论(0)