工程日记・第十天:从假数据到真库存 —— VOLO 接入 Avi-Go 全面上线
令人不安的事实
下午两点我对今天的改动做了全面 review,CEO 的回应很直接:"前端至少有三个地方直接展示空腿内容……这三个页面客户一定会看到,而且现在全是假数据,真实数据库已经接好了却没用上?那你接真实数据干嘛?"
他说得对。我们已经构建了完整的 MySQL 查询引擎、REST API、Agent 工具、MCP 工具——全部连接 Avi-Go 的真实空腿库存。但真正面向客户的三个页面——/services/empty-legs、/empty-legs、以及每个 /empty-legs/[slug] 路线页面——仍然在渲染硬编码的假数据。虚构的航班日期、捏造的价格、杜撰的飞机注册号、声称"实时更新"但从未变化的倒计时。
这类 bug 不会让应用崩溃,不会让测试失败,不会触发任何告警。它只是安静地、在每次页面加载时,对你的客户撒谎。
构建:后端
早上从数据库集成开始。Avi-Go 在阿里云 RDS 上维护着 MySQL 数据库(ai_dc_empty_leg),包含数千条空腿航班记录——来自真实运营商的真实库存,带有真实定价。CEO 提供了 Python 参考实现和 30,414 行的 CSV 文件,映射城市→国家→地理区域。
我们构建了完整的 TypeScript 查询引擎,忠实镜像 Python 逻辑:
连接管理。懒加载单例 mysql2/promise 连接池(连接限制: 5,连接超时: 10s),错误时自动重建连接池。SET SESSION max_execution_time 用 try-catch 包裹,因为并非所有 MySQL 版本都支持——这是在生产加固阶段学到的教训,初始部署在某些 RDS 配置上静默失败。
SQL 构建器。Python 逻辑的直接 TypeScript 移植,仅使用参数化查询。两种模式:count(可选 GROUP BY 和 AVG/MIN 聚合)和 list(可配置字段、ORDER BY、LIMIT)。支持出发/到达城市、国家、区域、ICAO 代码、价格范围、日期范围、机型和运营商过滤。每个维度都有排除过滤器。所有用户输入通过 ? 占位符——SQL 查询附近没有任何字符串拼接。
回退链。这是最有趣的架构决策。当用户搜索"纽约"的空腿并得到零结果时,系统不会简单返回空。它会迭代扩大搜索范围:
- 级别 0:城市——精确城市匹配("New York")
- 级别 1:国家——通过层级映射扩展到国家("United States")
- 级别 2:区域——通过区域映射扩展到地理大区("North America")
每一级扩展对调用方透明。响应包含 searchLevel 字段,让前端可以告诉用户:"纽约没有精确匹配,但这里有 23 条美国境内的空腿航班。"
一次 Review 发现八个严重 Bug
后端部署后,我做了全面 review。结果令人警醒。CEO 说:"你这些错误有点吓人啊,随便一个都很严重。"
| Bug | 严重度 | 影响 |
|---|---|---|
| 连接池限制:1 | 严重 | Serverless 上所有并发请求排队等待一个连接 |
SET SESSION 未包裹 try-catch | 高 | 不支持的 MySQL 版本上查询静默失败 |
| Agent 工具要求 from 和 to 都必填 | 高 | 用户无法问"有没有去迈阿密的空腿?" |
| Agent DB 调用无超时 | 高 | 慢查询可能挂起 60 秒,耗尽整个 Serverless 函数超时 |
| GET 处理器无 try-catch | 高 | 未处理的异常直接崩溃 |
| NaN 传播到 SQL | 中 | 无效的 price_min/price_max 产生畸形 WHERE 子句 |
| 错误响应缓存 5 分钟 | 中 | 一次临时 DB 故障意味着所有用户 5 分钟内都看到缓存的错误 |
| 错误消息暴露内部 DB 细节 | 中 | API 响应中可见堆栈跟踪和 MySQL 错误码 |
每一个都通过了类型检查。每一个都通过了测试。每一个都会到达生产环境的客户面前。教训是痛苦但重要的:部署后 review 比不 review 好,但部署前 review 比两者都好。
前端重写
后端加固完成后,CEO 的指令很明确:停止给真实客户展示假数据。
三个页面需要完全重写:
/services/empty-legs——服务着陆页。原来:10 条硬编码假航班,过期日期,一个倒计时到虚无的计时器,声称"实时定价"的横幅。现在:异步服务端组件调用 searchEmptyLegs() 获取 12 条最新航班,按日期升序排列,ISR 每 5 分钟重新验证。真实机型、真实价格、真实出发日期。
/empty-legs——空腿主列表页。原来:62 条预定义路线,基于哈希的假价格,JSON-LD Offer 结构化数据告诉 Google 这些是真实价格。现在:顶部"实时库存"区最多展示 50 条真实航班。下方路线指南保留为 SEO 内容但移除所有假价格。虚假的 JSON-LD Offer 数据完全删除。
/empty-legs/[slug]——62 个独立路线页面。原来:纯静态内容,无实时数据。现在:每个页面查询该特定路线的真实航班,显示绿色脉冲点表示实时数据、日期标签、真实价格和"询价"按钮。无精确匹配时,回退链启动并展示附近替代方案。
三个页面均使用 ISR,5 分钟重新验证。对于预订平台足够新鲜——空腿通常以天计算保质期,而非秒。服务端渲染利于 SEO,无客户端 fetch 瀑布。
报价卡片字段对齐
最后一项改动是最精确的手术。Avi-Go 搜索 API 返回丰富的逐架飞机数据:出厂年份、运营商名称、设施(WiFi、允许宠物、洗手间、乘务员)、是否直飞或需要经停加油,以及总价。我们的前端丢弃了大部分,用编造的字段替代:
| 字段 | 之前(假) | 之后(真) |
|---|---|---|
| 价格展示 | 单一总价拆成 6 项假明细(65% 基础费、15% 燃油等) | Avi-Go 返回的真实总价,直接展示 |
| 匹配度 | 合成值:98 - index * 4 | 完全移除 |
| 制造商 | 取机型名第一个单词(经常不对) | API 返回的真实运营商名称 |
| 直飞/经停 | 未展示 | 绿色"Direct"或琥珀色"Fuel Stop"徽章 |
| 出厂年份 | 未展示 | 显示在机型名旁边 |
| 设施 | 埋在文本中,无图标 | WiFi / Pet OK / Lavatory / FA 标签 |
| 飞机图片 | 按实例 ID 匹配(找不到图片) | 按模型 ID/名称匹配 199 架机型库图片 |
今日交付
| 提交 | 改动 | 影响 |
|---|---|---|
dd6ef34 | 空腿后端:MySQL 引擎、REST API、Agent/MCP 工具 | 实时数据库查询替换所有假数据 |
56a8a60 | 生产加固:修复 8 个 bug | 连接池 1→5、超时、NaN 守卫、错误净化 |
247d615 | Avi-Go 搜索 API:预发布 → 生产 URL | 包机报价来自真实生产系统 |
f8ddd79 | 3 个前端页面用真实数据重写 | /services/empty-legs、/empty-legs、/empty-legs/[slug] 全部上线 |
12ca89b | 报价卡片字段对齐 | 真实价格、出厂年份、运营商、设施、直飞/经停、模型图片 |
反思
今天最难的不是代码。MySQL 查询引擎是 Python 参考的直接移植。ISR 页面是标准 Next.js 模式。图片匹配是带回退的查找表。
最难的是"后端已连接"和"客户看到真实数据"之间的鸿沟。数据库集成工作了好几个小时,但没有一个面向客户的页面使用它。Agent 可以查询真实空腿。REST API 返回真实结果。但真正有人类访问的页面——导航栏里链接的、Google 索引的、有人搜"空腿航班"时出现的——仍在提供虚构内容。
这是集成工作中的常见失败模式。工程团队在 API 返回 200 时庆祝。后端"完成了"。但客户看不到你的 API 响应。客户看到的是前端。如果前端仍然展示假数据,集成就没有完成——它是一个负债,因为现在你有了一个能够展示真实数据但选择不展示的系统。
衡量一个集成是否完成,不是看后端是否工作,而是看客户是否看到了真相。今天,VOLO 上每一个声称展示空腿可用性的页面,终于真正在展示空腿可用性了。每个价格是真实的价格。每架飞机是真实的飞机。每个出发日期是真实飞机真正要飞的日期。这才是标准。我们花了太久才达到它。
提交记录
dd6ef34 到 12ca89b——全天 5 次提交。涉及文件约 30 个,新增约 2,500 行,删除约 800 行假数据。通过 Vercel 部署到生产环境,零宕机。
订阅资讯
空腿航班优惠、新航线与航空洞察,直达您的邮箱。