工程日記・第十天:從假數據到真庫存 —— VOLO 接入 Avi-Go 全面上線
今天我們拆掉了 VOLO 上所有的假數據,用 Avi-Go 生產系統的實時連接全部替換。一個包含數千條空腿航班的 MySQL 數據庫,一個返回真實報價的包機搜索 API,三個原本展示虛構航線和捏造價格的客戶頁面——全部重寫爲查詢真實庫存。六次提交,零宕機,以及一個令人不安的事實:給真實客戶展示假數據,比什麼都不展示更糟糕。
令人不安的事實
下午兩點我對今天的改動做了全面 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 部署到生產環境,零宕機。
準備好飛行了嗎?幾秒鐘獲取個性化包機報價。
訂閱資訊
空腿航班優惠、新航線與航空洞察,直達您的郵箱。