feat(dispatch): 預約行程+常用地點——修掉停機後會把積壓的過期預約全部派車 - #70
Merged
Conversation
W. 兩張新表(migration 000025/000026)+9 支端點+一支到點轉單的背景排程器。 App 端對應 fleet-app #95。 ## 為什麼預約是獨立一張表,不是 rides 加一個狀態 rides 的查詢散布在派單池(status='requested')、乘客 active、歷史、報表、admin 訂單列表。 多塞一個「還不該被派單」的狀態進去,得逐一稽核每支查詢有沒有排除它—— 漏一支就是「司機看到一張三天後才要出發的單」。獨立表對既有路徑是純新增、零風險。 ## 到點轉單 每 30 秒掃一次,把約定時間前 15 分鐘內的 pending 轉成真訂單。 **轉單走的是與手動叫車完全同一支 RideService.CreateByCustomer**——派單、審計、 車種驗證、「同時只能有一張進行中訂單」全部自動沿用。另寫一份的話兩條路徑遲早長歪, 而且是預約那條先歪(沒人在旁邊看著)。 認領帶 attempt_count 樂觀鎖:多副本同時掃到同一筆會建出兩張要付錢的訂單。 暫時性失敗(乘客還在別的行程上)維持 pending 等下一輪; 永久性失敗(座標/車種無效)立刻判死,不佔著重試額度撐到約定時間才讓乘客發現沒車。 ## 修掉的 bug:停機後會把積壓的過期預約全部派車 FindDue 原本只有上界沒有下界。部署/當機/DB 不可用停了一段時間,重啟時 昨天早上的預約會今天下午開一台車到乘客家樓下,而且他還得付那趟錢。 修法是超過約定時間 30 分鐘就標 failed 並寫原因——標 failed 而不是留在 pending, 是因為留著它會永遠掛在乘客的「即將到來」,而那台車永遠不會來。 先寫 TestDispatcherSkipsLongExpiredSchedules 取證看到它真的被轉單,才動手修。 ## 常用地點 home/work 每人各限一筆(部分唯一索引),服務層是 upsert 語意—— 「設定住家」該把住家換成新地址,不是回一句「你已經有住家了」。 ## 驗收 go build/go vet 乾淨;internal/service 新增 15 支測試(真 PostGIS 容器)全綠。 另加 scheduled_ride_route_shape_test.go——新路由與既有 /customer/rides/:id 同層 混用靜態段與參數段,gin 的路由衝突是註冊當下 panic:服務起不來但單元測試照樣全綠。 反向驗證三次,其中兩次推翻了自己的假設:拔掉 FindDue 的時間條件會紅; 拔掉樂觀鎖 TestDispatcherIsIdempotent 仍然綠(它守的是循序重跑,沒走到認領那行); 補的併發測試第一版也是假的(兩個 goroutine 沒真重疊,拔掉樂觀鎖連跑三次都綠), 改成餵兩份 attempt_count=0 的相同快照直接進 dispatchOne 才造得出真重疊。 真環境實跑:塞一筆 5 分鐘後到點的預約,前兩輪被「已有進行中的訂單」擋下並維持 pending, 結掉擋路訂單後第三輪轉單成功(ride_id=39,起訖點與預約一致、已進派單池)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
iso_in 的 BSD date 路徑在 macOS 上實跑過,GNU 那條是照文件寫的、未實測—— 寫清楚比讓下一個人以為兩條都驗過好。順手清掉設了沒用到的 CANCEL_TARGET。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
轉單後那就是一張普通訂單,司機看不到約定上車時間。提前量 15 分鐘下多半剛好, 但司機若 5 分鐘就到,乘客可能還沒下樓,而司機不知道該等。 要做的話動的是 rides(帶 scheduled_at 或來源標記)+司機端顯示, 屬功能擴充不是 bug,所以沒有夾帶進這批——寫明條件放進待辦而不是靜靜做掉。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
第二條值得單獨記:本機 go test ./... 跑不完(service 有 172 支容器測試、約一小時), 要驗改動請跑受影響的 package。這一輪等了 90 分鐘才確認它只是慢不是卡。 而 CI 那支只花 1 分鐘是因為跳過 testcontainers——它綠不代表整合測試綠。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
常用地點是覆蓋(插槽語意),但預約會累積——每跑一次多四筆。 實跑兩條分支(帶/不帶 PSQL_DSN)時發現說明只講了前半,補上後半與清除指令。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
兩個上限本來就實作了但沒測到。常用地點那支特別驗一條容易做錯的: **已達上限時覆蓋住家仍應成功**——覆蓋不增加筆數, 若把它也擋掉會變成「地點滿了就連住家都不能改」的死結。 預約那支則驗「取消一筆就讓出一個名額」——上限算的是還沒轉單的,不是這輩子建過的。 go test 兩支皆 PASS(真 PostGIS 容器)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
172 支全過、0 失敗(ok ... 2911.952s,本機真容器跑了 48 分鐘),其中 17 支是這批新增的。 先前只寫「新增 15 支全綠」,沒有回答「既有的有沒有被改壞」——現在有了。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
W. 兩張新表(migration 000025/000026)+9 支端點+一支到點轉單的背景排程器。
App 端對應 fleet-app #95。
為什麼預約是獨立一張表,不是
rides加一個狀態rides的查詢散布在派單池(status='requested')、乘客 active、歷史、報表、admin 訂單列表。多塞一個「還不該被派單」的狀態進去,得逐一稽核每支查詢有沒有排除它——
漏一支就是「司機看到一張三天後才要出發的單」。獨立表對既有路徑是純新增、零風險。
到點轉單
每 30 秒掃一次,把約定時間前 15 分鐘內的 pending 轉成真訂單。
轉單走的是與手動叫車完全同一支
RideService.CreateByCustomer——派單、審計、車種驗證、「同時只能有一張進行中訂單」全部自動沿用。另寫一份的話兩條路徑遲早長歪,
而且是預約那條先歪(沒人在旁邊看著)。
attempt_count樂觀鎖修掉的 bug:停機後會把積壓的過期預約全部派車
FindDue原本只有上界沒有下界。部署/當機/DB 不可用停了一段時間,重啟時昨天早上的預約會今天下午開一台車到乘客家樓下,而且他還得付那趟錢。
修法是超過約定時間 30 分鐘就標
failed並寫原因——標 failed 而不是留在 pending,是因為留著它會永遠掛在乘客的「即將到來」,而那台車永遠不會來。
先寫
TestDispatcherSkipsLongExpiredSchedules取證看到它真的被轉單,才動手修。常用地點
home/work每人各限一筆(部分唯一索引),服務層是 upsert 語意——「設定住家」該把住家換成新地址,不是回一句「你已經有住家了」。
已達地點數上限時,覆蓋住家仍然成功(覆蓋不增加筆數,擋掉會變成連住家都不能改的死結)。
取消已轉單的回 409+現況
那張真訂單已經在派單池裡,司機可能正在開過來——App 要據此把畫面切成「已為你派車」
並引導去取消訂單,而不是「取消失敗,請稍後再試」(同一個病在 V 那章的 admin 端抓過一次)。
驗收
go build/go vet乾淨。internal/service完整套件 172 支全過、0 失敗(ok ... 2911.952s,真 PostGIS 容器,本機跑了 48 分鐘),其中 17 支是這批新增的;
internal/handler完整套件也綠(136s)。build-and-unit-test只花 1 分鐘,是因為 CI 環境跳過 testcontainers——它綠不代表整合測試綠,所以上面那份本機數字才是這批的證據。
scheduled_ride_route_shape_test.go——新路由與既有/customer/rides/:id同層混用靜態段與參數段,gin 的路由衝突是註冊當下 panic:服務起不來但單元測試照樣全綠。
FindDue的時間條件會紅;拔掉樂觀鎖
TestDispatcherIsIdempotent仍然綠(它守的是循序重跑,沒走到認領那行);補的併發測試第一版也是假的(兩個 goroutine 沒真重疊,拔掉樂觀鎖連跑三次都綠),
改成餵兩份
attempt_count=0的相同快照直接進dispatchOne才造得出真重疊。結掉擋路訂單後第三輪轉單成功(
ride_id=39,起訖點與預約一致、已進派單池)。那張訂單也確認顯示在乘客 App 的「車已在路上」區(跨端閉環)。
scripts/seed_demo_data.sh兩條分支(帶/不帶PSQL_DSN)都實跑過。🤖 Generated with Claude Code