MatchRPG 生產除錯 Session(融合版 v2):Varchar Overflow、Noop Validator、JWT 強制登出架構、PendingEvents 機制¶
文檔資訊
- 分類: debugging
- 難度: advanced
- 預估閱讀時間: 16 分鐘
- 標籤:
MatchRPG,postgresql,jwt,varchar-overflow,battle-validator,go,game-backend,1M-CCU,PendingEvents,EventStore,stamina-hud
摘要¶
MatchRPG 後端完整生產除錯 session(融合版),涵蓋四個層面:(1) Publisher 模擬器登入 503 — PG varchar 溢位三層修法;(2) 戰鬥道具校驗誤報 — Noop validator nil 語意問題;(3) JWT 強制登出架構缺口 — stateless 設計意圖釐清與現有缺口清單;(4) PendingEvents/EventStore 機制 — Stamina HUD 顯示延遲的正確修法與架構調研(進行中)。
關鍵學習¶
-
生產級 varchar 防護需三層:DB 欄位擴充 + 應用層截斷 + PG error class 22 分類,缺一不可
-
nil map 與 empty map 語意不同:Noop validator 返回 nil 代表「未校驗」,不是「零使用量」,必須明確跳過 cross-validation
-
JWT stateless 驗證不查帳號狀態是刻意設計(1M CCU / 500K RPS 效能考量),非漏洞
-
強制登出機制缺口:RevokeAccountTokens() 存在但無 admin RPC 入口,force logout push notification(gRPC Stream / Redis Pub/Sub)尚未實作
-
Stamina HUD 顯示延遲的正確修法是 PendingEvents middleware + EventStore event emission,而非在每個 response 都加 current_stamina 欄位
-
Worktree baseline 確認方式:git remote show origin + git log --oneline origin/main vs origin/develop,不要只看本地 HEAD;此 worktree 的 baseline 是 main,非 develop
-
玩家資料重置必須給初始資源(gold=100, diamond=100, stamina=30),直接清零會破壞遊戲狀態
-
前端 refresh token 持有期間,即使 device record 刪除或 redis refresh token 撤銷,stateless access JWT(1h TTL)仍然有效 — 這不是 bug
-
ConsumeStamina 等玩家資料異動操作目前沒有 emit PlayerDataUpdated event 到 EventStore,這是 PendingEvents 機制未完全覆蓋的缺口
技術細節¶
Bug 1:Publisher Guest Login 503(Varchar Overflow)¶
根本原因:devices 表 os_version VARCHAR(32) / app_version VARCHAR(32) 在 Android 模擬器的長字串裝置資訊下溢位,PG 拋出 class 22 data exception,被包裝成 500 → publisher 回傳 503。
三層修法:
- DB 遷移(
migrations/publisher/025_widen_devices_columns.up.sql)— os_version 擴至 128、app_version 擴至 64 - 應用層截斷
Device.SanitizeDeviceInfo()— 在 GuestLogin、OAuth、device management 寫入前呼叫 - PG Error Class 22 分類 — 映射為
twirp.InvalidArgument(HTTP 400),不再是 503
Bug 2:戰鬥道具校驗誤報(Noop Validator nil 語意)¶
根本原因:Phase 1-2 的 Noop battle validator 返回 ItemUsage: nil,crossValidateItemUsage 將 nil 當作「玩家使用了零道具」,與 server-tracked 的非零使用量產生 mismatch,觸發誤報懲罰。
修法:在 crossValidateItemUsage 加入 nil guard,nil = 未校驗 → 跳過;empty map = 校驗結果為零使用。同時在 Noop validator 和 gRPC stub 加上 TODO(phase3) 標記。受影響的 6 名玩家 mismatch 紀錄已清除。
架構釐清:JWT Stateless 設計意圖¶
ValidateToken 不查帳號狀態是刻意的,理由是 Game Server 需要支撐 1M CCU / 500K RPS,採用 cached JWKS 本地驗證。這不是漏洞。
實際缺口清單(已確認):
| 缺口 | 嚴重度 | 說明 |
|---|---|---|
| Admin Force Logout 無入口 | P0 | RevokeAccountTokens() 存在但無 RPC 暴露 |
| Ban 後不會自動 force logout | P0 | player.ban 改狀態但沒呼叫 RevokeAccountTokens() |
| ValidateToken 不查帳號狀態 | P0(設計意圖) | stateless 1h TTL;真正缺口是 push notification 未實作 |
| Suspend 未實作 | P1 | DB schema 有 suspended 狀態,程式碼未設置或檢查 |
| GDPR Hard Delete Worker 缺失 | P1 | 軟刪除設計是刻意的,30 天硬刪除時間線需法律審查 |
Force logout push notification(gRPC Stream 或 Redis Pub/Sub)在設計文件中規劃,尚未實作。
Stamina HUD 顯示延遲分析(新增)¶
問題現象:關卡結束後,體力 HUD 顯示舊值,需回到大廳才更新。
正確修法:使用 PendingEvents middleware + EventStore event emission,而非在 StartBattleResponse/EndBattleResponse 增加 current_stamina 欄位。
原因分析:ConsumeStamina 等玩家資料異動操作目前沒有 emit PlayerDataUpdated event 到 EventStore。PendingEvents middleware(pkg/middleware/pending_events.go)會把 events 附加在 response headers(X-Event-Batch)回傳前端,但缺少 event emission 的地方就無法觸發更新。
前端調研結果:前端已收到獎勵資料(獎勵空白 1s 是資源載入問題),但 HUD 體力值不更新是因為後端 event emission 缺口。GM debug panel(SRDebugger)已有框架但無 GM command 功能,尚未實作。
此調研已被中斷兩次,完整缺口清單尚待完成。
What Changed¶
Publisher 層(503 修復):新增 DB 遷移 025(varchar 擴充)、Device.SanitizeDeviceInfo() 方法與 truncatePtr helper、PG error class 22 sentinel(ErrDataValidation)、三個 auth service 文件加入 sanitize 呼叫,錯誤映射至 twirp.InvalidArgument。
Game Server 層(Noop Validator 修復):crossValidateItemUsage 加入 nil guard(nil → 跳過,empty map → 正常校驗)、Noop validator 和 gRPC stub 加入 TODO(phase3) 標記、相關測試更新為期望 false, nil。清除 6 名受影響玩家的 mismatch 懲罰紀錄。
運維操作:重置玩家 019d186c 至初始狀態(含初始資源 gold=100, diamond=100, stamina=30)、Publisher migration dirty state 手動修復(SET version=24, dirty=false)、確認 worktree baseline 為 origin/main(非 develop)。
架構澄清(相較舊版更新):確認 ValidateToken 不查帳號狀態是設計意圖,非 P0 漏洞;GDPR 30 天硬刪除時間線待法律審查;新增 PendingEvents/EventStore 機制作為 Stamina HUD 問題的正確修法方向(調研進行中)。
So What¶
此 session 記錄了兩個實際影響線上測試的 P0 Bug 完整修復過程(varchar overflow 503、Noop validator 誤判懲罰),以及一個 JWT 架構設計意圖的重要澄清(避免未來誤判為安全漏洞)。
相較舊版新增的價值:Stamina HUD 問題的正確修法方向(PendingEvents + EventStore,而非每個 response 塞 stamina 欄位)是一個可重複應用的架構模式。強制登出缺口清單是 Phase 3 實作的明確任務依據。
**nil vs empty map 語意區分**這個模式在 Go 後端 validator placeholder 設計中容易踩坑,直接影響玩家懲罰機制,具有長期參考價值。
Trade-offs¶
- 三層 varchar 防護 vs 只改 DB:多一層應用截斷增加維護點,但可防止其他未覆蓋路徑;PG error 分類泛用但掩蓋截斷本身應避免的語意問題
- Stateless JWT(1h TTL) vs Stateful token:前者效能好但 revoke 延遲最多 1h;後者每請求查 DB/Redis,在 500K RPS 下不可行
- Nil 跳過 cross-validation vs 報錯:跳過允許 Phase 1-2 正常運行,但若忘記補 TODO 會導致 Phase 3 validator 上線後無法自動啟用 — 靠測試和 TODO 標記防護
- PendingEvents header 傳遞 vs 在每個 response 加 current_stamina:前者架構統一、前端只需一套更新邏輯;後者簡單但每個新欄位都要修改所有 response proto
- Soft delete vs Hard delete:GDPR 被遺忘權需要硬刪除,但財務紀錄有更長法定保存要求;設計文件未指定 30 天硬刪除,需法律審查
Try It Fast¶
# 驗證 publisher migration 狀態
docker exec -it publisher-postgres psql -U publisher -d publisher \
-c "SELECT version, dirty FROM publisher_schema_migrations;"
# 查看 device 欄位寬度是否已更新
docker exec -it publisher-postgres psql -U publisher -d publisher \
-c "\d devices" | grep -E 'os_version|app_version'
# 清除指定玩家的道具誤報懲罰記錄(game server DB)
docker exec -it postgres psql -U matchrpg -d matchrpg \
-c "DELETE FROM battle_mismatch_records WHERE player_id = '<uuid>';"
# 檢查 crossValidateItemUsage nil guard 是否存在
grep -n 'validatorUsage == nil' \
internal/modules/dungeon/dungeon_service_end_battle.go
# 查看 PendingEvents middleware 現有 event 類型
grep -rn 'EventStore\|PendingEvent\|X-Event-Batch' \
pkg/middleware/ internal/modules/
# 確認玩家重置後初始資源(DB 1 = publisher Redis)
docker exec -it redis redis-cli -n 0 keys "*019d186c*"
Recommendation¶
- Phase 3 優先項:實作
ForceLogoutAccountadmin gRPC RPC,暴露現有的RevokeAccountTokens(),讓 ban 操作能立即生效 - Ban hook:在
player.ban狀態變更時自動呼叫RevokeAccountTokens(),補齊 P0 缺口 - Force logout push notification:按設計文件實作 Redis Pub/Sub 機制,讓 Game Server 收到通知後能主動關閉 WebSocket 連線
- PendingEvents 缺口修補:在 ConsumeStamina、AddCurrency 等玩家資料異動操作加入
PlayerDataUpdatedevent emission,修復 Stamina HUD 顯示延遲問題 - TODO(phase3) 追蹤:在 Issue tracker 建立 Phase 3 validator 實作任務,確保 nil guard 在上線時被移除或轉換
- GDPR 合規審查:軟刪除設計是刻意的;30 天硬刪除時間線需法律團隊確認,財務紀錄可能有更長保存要求
- Worktree 合併:
worktree-demo-test分支上的修改尚未 commit,需確認後 cherry-pick 或 PR 至origin/develop - 帳號 019d186c 狀態確認:調查期間被設為
suspended,需確認是否已恢復active