跳轉到

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。

三層修法:

  1. DB 遷移(migrations/publisher/025_widen_devices_columns.up.sql)— os_version 擴至 128、app_version 擴至 64
  2. 應用層截斷 Device.SanitizeDeviceInfo() — 在 GuestLogin、OAuth、device management 寫入前呼叫
  3. 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

  1. Phase 3 優先項:實作 ForceLogoutAccount admin gRPC RPC,暴露現有的 RevokeAccountTokens(),讓 ban 操作能立即生效
  2. Ban hook:在 player.ban 狀態變更時自動呼叫 RevokeAccountTokens(),補齊 P0 缺口
  3. Force logout push notification:按設計文件實作 Redis Pub/Sub 機制,讓 Game Server 收到通知後能主動關閉 WebSocket 連線
  4. PendingEvents 缺口修補:在 ConsumeStamina、AddCurrency 等玩家資料異動操作加入 PlayerDataUpdated event emission,修復 Stamina HUD 顯示延遲問題
  5. TODO(phase3) 追蹤:在 Issue tracker 建立 Phase 3 validator 實作任務,確保 nil guard 在上線時被移除或轉換
  6. GDPR 合規審查:軟刪除設計是刻意的;30 天硬刪除時間線需法律團隊確認,財務紀錄可能有更長保存要求
  7. Worktree 合併:worktree-demo-test 分支上的修改尚未 commit,需確認後 cherry-pick 或 PR 至 origin/develop
  8. 帳號 019d186c 狀態確認:調查期間被設為 suspended,需確認是否已恢復 active

本文檔由 Semi-Brain 自動生成

Session ID: d088842d-abec-48fc-8a1a-779a5f74e05d

分析信心度: 95%