可靠性、健康与错误
送达语意
- 至少一次送达:本平台以
(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、错误事件的处理。 - 平台把来源桌绑定到公开桌并设置优先级后上线。