Skip to content

Repository files navigation

LINE 叫車派遣系統(fleet-dispatch)

Go + PostgreSQL(PostGIS) + Redis + LINE Bot 打造的叫車派遣系統。客戶在 LINE 傳一則「位置訊息」就能叫車,系統即時找出最近的待命司機、推播接單邀請、產生 Google Maps 導航連結,並全程回報司機位置與預估抵達時間(ETA),最後留下行程軌跡與日報表。

這是一個學習/作品專案,重點在把即時地理查詢、分散式搶單鎖、路徑 ETA、地理圍籬等後端主題用一條完整業務流程串起來。完整開發規格見 docs/spec.md,技術決策見 docs/decisions.md


系統架構

  客戶 LINE ──叫車(位置訊息)──┐
                             ├─► LINE Webhook ─► Go 後端 (Gin) ─┐
  司機 LINE / LIFF ─接單/回報─┘                                  │
                                                                 │
         ┌───────────────────────┬──────────────────────┬───────┘
         ▼                       ▼                      ▼
     Redis GEO             PostgreSQL + PostGIS     OSRM 路徑引擎
  (司機即時位置、          (訂單狀態機、軌跡、       (最短路徑 + ETA,
   最近車查詢、搶單鎖)       電子圍籬、日報表)         預設公開 router)

分層遵循 Handler → Service → Repository → DB,與 Laravel 的 Controller/Service/Repository 對應,方便從 PHP 背景切換閱讀。


端到端業務流程

一趟完整行程會經過以下狀態機(internal/constants/ride.go):

REQUESTED(0) ─► ASSIGNED(1) ─► ACCEPTED(2) ─► PICKED_UP(3) ─► COMPLETED(4)
                                                                (或任一步 CANCELLED(9))
# 步驟 觸發 系統行為
1 叫車 客戶在 LINE 傳位置 webhook 驗簽 → 建立/更新客戶 → 建立訂單 REQUESTED,pickup 存成 PostGIS geography
2 派單 建單後非同步觸發 Redis GEOSEARCH 找 pickup 半徑內最近 N 台待命司機(濾掉離線)→ 訂單轉 ASSIGNED → 推播接單邀請(含 postback 按鈕)
3 搶單 司機按「接受派單」 Redis SETNX 搶單鎖,只有第一位搶到的司機成單 → 訂單轉 ACCEPTED、寫 driver_id、司機轉載客中;其餘司機收到「手慢了」
4 導航 接單成功 回傳 https://www.google.com/maps/dir/?... deep link,司機一點開啟手機 Google Maps 導航到上車點
5 ETA 回報 司機位置更新 OSRM 算「司機→上車點」實際路網時間,推播客戶「距您 X 公尺、約 Y 分鐘抵達」
6 抵達/上車 司機進入上車點 100m PostGIS ST_DWithin 電子圍籬自動判定抵達;司機按上車 → 訂單轉 PICKED_UP,開始記錄軌跡到 ride_tracks
7 下車/完成 司機按完成 訂單轉 COMPLETED,PostGIS ST_Length 由軌跡算里程;計費里程取「軌跡 vs OSRM pickup→dropoff 路線」大者(F3,軌跡稀疏時的退路),依當前費率算好車資/手續費/司機實得,快照定格寫進該筆 ride
8 報表 隨時查詢 軌跡回放輸出 GeoJSON;日/月報表聚合各司機趟數、里程、營業額/手續費/應付總公司(=手續費+月會費);會費可產生 membership_invoices 帳單

快速開始

cp .env.example .env          # LINE 憑證可留空,留空時 API 測試免簽章
docker compose up --build -d  # 起 app + postgis + redis

curl http://localhost:8080/healthz          # → {"status":"ok"}
sh scripts/smoke_test.sh                     # 跑完整 M1-M4 流程(需乾淨 DB)

make test                                    # 整合測試(testcontainers 起真 Redis/PostGIS,需 Docker)

# 選用:起 20 台模擬司機灌位置(勿與 smoke_test 同時跑,會互相搶單)
docker compose --profile simulator up -d simulator

重跑 smoke_test.sh 前建議先 docker compose down -v 清掉舊資料,避免殘留訂單干擾。

只起相依服務、server 跑在主機(debug 常用)

docker compose up -d postgis redis
DB_HOST=127.0.0.1 DB_PORT=5433 REDIS_ADDR=127.0.0.1:6380 go run ./cmd/server

三個變數都要覆寫.env 裡的 postgisredis 是 compose 網路內的主機名, 在主機上解析不到。

Redis 一定要用 6380,不要用 6379:Mac 上常駐的 redis-server127.0.0.1:6379, Docker 綁 *:6379localhost 一律由本機那台接手——連錯不會報錯,服務照跑, 但你在容器裡 redis-cli KEYS 什麼都查不到(狀態其實寫在本機那台)。 這也是 postgis 讓開 5432 的同一個理由。用 lsof -nP -iTCP:6379 -sTCP:LISTEN 可以看誰在聽。


API 端點

方法 路徑 說明
GET /healthz 健康檢查(DB + Redis)
POST /webhook/line LINE webhook(受 X-Line-Signature 保護;回應含 ride_ids 方便測試)
POST /api/driver/register 註冊司機
POST /api/driver/location 司機回報位置(進 Redis GEO;行程中同時寫軌跡)
POST /api/rides/:id/accept 接單(搶單鎖)
POST /api/rides/:id/pickup 客戶上車
POST /api/rides/:id/complete 完成行程
GET /api/rides/:id/track 軌跡回放(GeoJSON Feature)
POST /api/customer/register loginPOST /api/rides 乘客 App:註冊/登入、叫車(可帶 dropoff_lat/lng
GET /api/driver/earnings?month=YYYY-MM 司機收入(趟數/營業額/手續費/實得/會費/應付總公司)
GET/POST /api/rides/:id/messages?after= 行程內對話:歷史/發話(僅本趟乘客/司機;即時遞送走 WS chat.message
POST /api/rides/:id/lost-items 乘客對已完成行程建遺失物協尋單(處理費=車資×%,建單快照)
POST /api/customer/rides/estimate 建單車資預估(純唯讀,與完成計費共用同一套費率,R)
POST /api/customer/rides/:id/rating 乘客評分司機 1–5 星+評論,一趟一評(B5)
GET /api/rides/:id/lost-items 查該行程最新協尋單(本趟乘客/司機/admin)
GET /api/customer/lost-items/api/driver/lost-items 乘客/司機的未結案協尋清單
POST /api/lost-items/:id/found pay return close 協尋狀態機:司機尋獲→乘客付處理費→司機歸還;未尋獲可結案
GET/POST /api/customer/placesPUT/DELETE /api/customer/places/:id 常用地點(住家/公司/自訂);kind=home/work 每人各限一筆且為覆蓋語意
GET/POST /api/customer/scheduled-rides 預約行程:清單(?upcoming=1 只回未轉單)/建立;回應帶 lead_minutes
GET /api/customer/scheduled-rides/:id 單筆預約(取消撞 409 後用來重讀現況)
POST /api/customer/scheduled-rides/:id/cancel 取消預約;已轉單回 409+該筆現況(那張真訂單已在派單池,不能宣稱取消成功)
GET /liff/ 司機 LIFF 定位頁

後台 /api/admin/*(帳密登入,角色 viewer/dispatcher/superadmin):

方法 路徑 說明
POST /api/admin/login 後台登入(種子 admin/admin)
GET /api/admin/rides?status=&limit=&offset=&from=&to=&q= 訂單列表(伺服器端分頁,回 total
GET/POST /api/admin/rides/:id/api/admin/rides/:id/cancel 訂單詳情(軌跡+事件+停靠點+乘客評分)/強制取消
GET/PATCH /api/admin/drivers/api/admin/drivers/:id/status 司機列表(含 rating_avgrating_count)/啟停
POST /api/admin/drivers/:id/vehicle-review 車輛審核:核准/退回(退回須附原因,O5)
GET /api/admin/reports/daily?date=/reports/monthly?month= 日/月報表(含金額)
GET/PUT /api/admin/settings/dispatch/api/admin/settings/fees 派單參數/費率設定(費率限 superadmin)
GET/POST/PATCH /api/admin/membership-invoices[...] 會費帳單:列表/產生/標記已繳
GET/POST/PATCH /api/admin/admins[...] 後台帳號管理(superadmin)

資料模型(PostgreSQL / PostGIS)

用途 地理欄位
drivers 司機、狀態(離線/待命/載客中)
customers 客戶(依 LINE userId 唯一)
rides 訂單狀態機、上/下車點、里程、ETA、計費快照fare_amount_centscommission_amount_centsdriver_net_amount_cents pickup_point / dropoff_point geography(Point,4326)
ride_tracks 行程軌跡(按月分區) location geography(Point,4326)
fleet_settings 費率設定單列(起步價/每公里/最低車資/手續費 bps/月會費/遺失物處理費 bps,金額存分)
membership_invoices 會費帳單(每司機每月一張,金額快照、UNIQUE(driver_id, period) 防重複)
ride_messages 乘客↔司機行程內對話(WS 即時遞送的歷史真源)
lost_item_requests 遺失物協尋單(處理費快照、狀態機 open→found→paid→returned/closed、部分唯一索引擋重複未結案單)
admins 後台帳號(角色 viewer/dispatcher/superadmin)

計費設計:金額全系統存「分」(整數,避免浮點),手續費存 bps(1500=15%)。車資/手續費在行程完成當下依當前費率算好、快照定格寫進該筆 ride,日後調費率不影響歷史帳。詳見 docs/TODO.md「F. 手續費/會費/營運報表」。

Redis 鍵:drivers:geo(GEO 位置集合)、driver:{id}:loc(最新位置+時間戳)、ride:{id}:lock(搶單鎖,30s TTL)、ratelimit:{lineUserId}(叫車限流)。


驗證狀態

本專案已在本機 Docker 完整跑通並驗證(go build / go vet / go test 皆綠):

  • 端到端流程smoke_test.sh 於乾淨 DB 走完「叫車→派單→接單→上車→軌跡→完成→報表」,並斷言接單真的成功、日報表確實含該司機。
  • 搶單鎖SETNX 確保同一單只有一位司機成單。
  • LINE 簽章安全:以真實 HMAC-SHA256 簽章請求驗證——正確簽章 200、偽造簽章 401、缺簽章 401
  • PostGIS:pickup 點、ST_DWithin 圍籬、ST_Length 里程、ST_AsGeoJSON 回放均正確輸出。
  • App 推播送出路徑(2026-07-30)scripts/push_e2e.shpush_cancel_e2e.sh 以假 device token 走完整鏈路,從 log 判定收件人——10 則推播全部推對人 (行程狀態、對話、協尋各步驟),取消三條另驗到「乘客自己取消完全沒有推播」。 不需 Firebase 憑證(走 LogPusher stub);憑證到位後不必改程式碼。

接真 LINE(上手清單)

本機測試不需要 LINE 憑證;要接真帳號跑手機叫車時:

  1. LINE Developers 建 Messaging API channel,取得 Channel secretChannel access token,填入 .env
  2. 建一個 LIFF app(司機定位頁),Endpoint 指向 https://<你的網域>/liff/,取得 LIFF_ID
  3. 用 ngrok/cloudflared 對外:ngrok http 8080,把 webhook URL 設為 https://xxxx.ngrok.io/webhook/line
  4. 手機用 LINE 加官方帳號好友,傳一則位置訊息即可叫車。

完整前置清單見 docs/checklist.md


規劃中(尚未實作)

2026-07-16 加入的 N/O/P 與寵物車清潔費、司機聯絡方式已全數實作並合併進 main (PR #35–#43),本段先前寫「都還沒實作」是過期資訊,2026-07-27 修正。 各章完整規格與驗收紀錄見 docs/TODO.md

剩下的多屬量體上升後才需要待產品/外部資源,勿過早做:

  • P4 #19 付款金流:完成後付款仍是佔位(遺失物處理費目前也是記帳式確認,無真金流)。 需先定金流方案,屬產品決策。
  • P4 #20 /metrics:Prometheus 指標(派單成功率、接單耗時、在線司機數、API 延遲)。
  • P4 #18 /api/driver/rides:司機歷史列表;收入面已由 GET /api/driver/earnings(F7)涵蓋, 缺的是逐趟明細。
  • F9-7 rides 月分割:量體達千萬級時依 completed_at 做 declarative partitioning。
  • driversmembership_invoices 真分頁:逼近 MaxListRows=5000 時比照 rides 改 offset/keyset 伺服器端分頁(含前端)。
  • F3 強化(可選):軌跡稀疏偵測目前用「軌跡 vs 路線取大者」,是否再加 「後台手動校正單筆車資」待產品定。
  • 評分的營運動作(B5 下游):目前三端都只「看得到」評分,沒有低分司機的處理流程 (通知/停權/申訴)。等實際累積評分、營運說得出要做什麼再開——做在前面只會做出沒人用的流程。

已知限制

  1. LIFF 背景定位:網頁 watchPosition 需頁面在前景,司機切到導航時位置更新會變慢;正式車隊需原生 App 背景定位。
  2. LINE push 月額度:設計上盡量用 reply token(互動回覆不計額度);未設 token 時 push 自動略過。
  3. OSRM:預設用公開 router.project-osrm.org demo server、無即時路況;自架請改 OSRM_URL 並參考 docs/spec.md §4.2。

技術棧

Go 1.25 · Gin · GORM · golang-migrate · PostgreSQL 16 + PostGIS 3.4 · Redis 7 · LINE Messaging API · OSRM · Docker Compose

專案結構

cmd/server        服務進入點與路由組裝
cmd/simulator     司機模擬器(灌位置、壓測用)
internal/handler  HTTP handler(webhook、driver、ride、report、health)
internal/service  業務邏輯(dispatch 派單、ride 訂單、tracking 追蹤、eta)
internal/repository  資料存取(GORM + 原生 PostGIS SQL)
internal/redis    Redis GEO / 搶單鎖 / 限流封裝
internal/line     LINE Messaging API 輕量封裝
internal/osrm     OSRM 路徑 client
internal/middleware  LINE 簽章驗證
db/migrations     golang-migrate SQL(啟動時自動套用)
web/liff          司機 LIFF 定位頁
scripts/smoke_test.sh  端到端煙霧測試

About

叫車系統GO

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages