可靠性、健康與錯誤
送達語意
- 至少一次送達:本平台以
(sourceId, table, seq)去重。- 同一個
seq、內容相同(以正規化 JSON 比較,鍵的順序不影響):忽略並照常 ack。 - 同一個
seq、內容不同:nack SEQ_CONFLICT。
- 同一個
- 每條串流內保序:來源必須依
seq遞增送出;不同桌之間不保證順序。 - 缺口偵測:事件的
prev不等於本平台已接收的最後一個seq時,回resync {table, from},來源必須從from之後重送。 - ack 在持久化之後才送:事件寫入後才回 ack,ack 是累積式的:
{"B001": 1538}表示這條串流 ≤ 1538 的事件都已接收。 - 重連續傳:WebSocket 連線建立時,
welcome.resume給出每條串流已 ack 的最後seq,來源從它之後補送。HTTPS 來源可以呼叫GET /ingest/v1/resume?tables=…。 - 來源保留期:來源至少要保留 24 小時內可重送的事件。
- 無法重送時:送出
table.snapshot(gap: true)後接續即時事件。這筆快照不要求prev對上,但seq仍須大於本平台已接收的最後一個seq;之後的事件從快照的seq接續。缺口期間未完成的局會轉為異常局,逾時自動作廢退款。 - 串流單一擁有者:同一條串流同時只能經由一條 WebSocket 連線送達。新連線在
hello宣告同一張桌時會接管,舊連線再送這張桌的事件會收到nack STREAM_TAKEN_OVER。 - 未綁定的來源桌:事件照常 ack 並歸檔,但不套用;平台完成綁定後開始套用。
回應格式
HTTPS(POST /ingest/v1/events、/validate)一次回覆整批的結果:
json
{
"acks": { "B001": 1538 },
"nacks": [{ "code": "INVALID_EVENT", "table": "B002", "seq": 77, "message": "data.result.cards.P1: must match ^(A|[2-9]|10|J|Q|K)[SHDC]$" }],
"resync": [{ "table": "B003", "from": 812 }]
}WebSocket 則逐一送出 {"op":"ack","acks":{…}}、{"op":"nack",…}、{"op":"resync","table":"…","from":…}。
nack帶accepted: true時(RESULT_INCONSISTENT),事件已接收並持久化,串流照常前進,只是警告。- 其他
nack表示事件沒有被接收,串流停在前一筆;修正後以相同的seq重送。 - 同一批中某條串流出現
nack或resync後,這條串流在這批裡剩下的事件都不處理;其他串流不受影響。 - 所有事件(包含被拒絕的)都會寫入原始歸檔,作為爭議與稽核依據。
健康與心跳
心跳(不占用串流序號)每 5 秒送一次,逐桌回報健康狀態
ok、degraded、down:json{"op":"heartbeat","ts":"2026-09-24T03:00:35.000Z","tables":{"B001":"ok","B002":"down"}}來源知道某張桌的上游已經中斷時,要立即把該桌標成
down,本平台會馬上停止該桌收注。15 秒內沒有心跳也沒有事件,視為來源失聯:所有綁定的桌停止收注;進行中的局等待恢復,逾時自動作廢退款。
最後一條 WebSocket 連線中斷時,這條連線負責的桌也會立即停止收注;來源重連並送出心跳或事件後恢復。
錯誤碼
| 代碼 | 意義 | 來源應採取的行動 |
|---|---|---|
UNAUTHORIZED | 簽章錯誤、時間窗外、nonce 重複、傳輸方式未開放或 IP 不在白名單 | 檢查金鑰、時鐘與設定 |
SOURCE_DISABLED | 來源已停用 | 聯絡平台 |
UNSUPPORTED_VERSION | v 不支援 | 升級 |
TABLE_NOT_ALLOWED | 這個來源無權發布這張桌 | 聯絡平台 |
INVALID_EVENT | 結構錯誤、欄位不合法 | 修正這一筆後,以相同 seq 重送 |
SEQ_GAP(resync) | prev 對不上 | 從 from 之後重送 |
SEQ_CONFLICT | 同一個 seq 內容不同 | 來源端錯誤,需要人工排查 |
RESULT_CONFLICT | 同一個 rev 的結果不同(保留;目前版本維持原結果並記錄,不回傳 nack) | 改用 round.correct |
RESULT_INCONSISTENT | 牌面與點數、輸贏或補牌規則不符(牛牛:重複的牌、牌型或輸贏與牌面不符、發牌順序不符,見牛牛) | 已接收但保留待審;可以送 round.correct 更正 |
STREAM_TAKEN_OVER | 這張桌已由新連線接管 | 停止在舊連線送這張桌 |
RATE_LIMITED | 超出流量(保留;目前版本不回傳) | 退避後重試 |
WebSocket 關閉碼:
| 關閉碼 | 意義 |
|---|---|
4001 | 認證失敗(例如 hello.source 與憑證不符) |
4002 | 版本不支援 |
4003 | 來源已停用 |
4008 | 違反限制(訊息或批次過大) |
1012 | 本平台重新啟動:立即重連並續傳 |
重連退避:1 秒起、每次加倍、上限 30 秒,另加隨機延遲。
來源信任等級
平台為每個來源設定信任等級,決定自動化程度。每一種情況都有自動處置,差別只在護欄的寬嚴:
| 等級 | 不完整結果(complete: false) | 結果驗證不一致 | 結果修正(round.correct) | 結算後作廢(round.cancel) |
|---|---|---|---|---|
trusted | 可結算的投注區照常結算,其餘退款 | 以來源的 winner 結算並標記 | 自動重算 | 自動沖回 |
standard | 同上 | 保留不結算,逾時自動作廢退款 | 護欄內自動重算;護欄外維持原結果並列為爭議局 | 同左 |
restricted | 同上 | 同上 | 同上 | 同上 |
護欄:修正或作廢在結算後 30 分鐘內送達,而且新結果完整、通過補牌規則驗證。超出護欄時不動任何金額,只記為爭議局。
WebSocket 對話範例
text
← {"op":"welcome","v":1,"session":"s_8f2…","heartbeat":5,"maxBatch":500,"maxInflight":2000,
"resume":{"B001":1530,"D001":0},"serverTime":"2026-09-24T03:00:29.950Z"}
→ {"op":"hello","v":1,"source":"acme-live","agent":"acme-feeder/2.0",
"tables":["B001","D001"],"capabilities":{"cardByCard":true,"countdown":true,"snapshot":true,"replayHours":168}}
→ {"op":"events","events":[
{"v":1,"type":"round.open","table":"B001","seq":1531,"prev":1530,"ts":"2026-09-24T03:00:30.000Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12",
"data":{"closesAt":"2026-09-24T03:00:55.000Z","betSeconds":25}}]}
← {"op":"ack","acks":{"B001":1531}}
→ {"op":"heartbeat","ts":"2026-09-24T03:00:35.000Z","tables":{"B001":"ok","D001":"ok"}}
→ {"op":"events","events":[
{"v":1,"type":"round.close","table":"B001","seq":1532,"prev":1531,"ts":"2026-09-24T03:00:55.010Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12","data":{}},
{"v":1,"type":"round.result","table":"B001","seq":1538,"prev":1532,"ts":"2026-09-24T03:01:20.400Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12",
"data":{"rev":1,"complete":true,"result":{
"cards":{"P1":"2C","B1":"4H","P2":"3D","B2":"2S","P3":"10S"},
"player":5,"banker":6,"winner":"B","playerPair":false,"bankerPair":false}}}]}
← {"op":"ack","acks":{"B001":1538}}seq可以不連號(上例 1532 之後是 1538),但每筆的prev必須等於前一筆的seq。hello的source必須與X-Source-Id相同,否則以4001關閉。hello.tables宣告這條連線負責的桌,新連線會接管同一張桌。
上線流程
- 向平台申請來源:代號、類型、傳輸方式、IP 白名單 → 取得 HMAC 金鑰。
- 先在測試環境(
elite-dev-ingest.ewin888.com)串接;可以用POST /ingest/v1/validate乾跑,或用 GFI 事件驗證器 檢查事件格式。 - 通過相容性檢查:斷線續傳(任意
seq)、resync重送、無法重送時送快照、心跳與down標記、修正與作廢、時鐘偏移 < 500 ms、錯誤事件的處理。 - 平台把來源桌綁定到公開桌並設定優先順序後上線。