跳轉到

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)

根本原因:devicesos_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: nilcrossValidateItemUsage 將 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%