95 lines
4.4 KiB
Markdown
95 lines
4.4 KiB
Markdown
# 送礼连击 V2 协议
|
||
|
||
## 边界
|
||
|
||
V2 只改变客户端封批、可靠重试和结算结果重放,不改变房间状态 owner:
|
||
|
||
- Flutter 立即播放连击反馈,第一批立即请求,后续点击按远程窗口聚合。
|
||
- HTTP 仍是账务入口;`room-service` 的 Room Cell 串行提交房间状态。
|
||
- `wallet-service` 负责扣费,`lucky-gift-service` 负责逐份独立抽奖,`room-service` 只编排持久化 Saga。
|
||
- V1 路由和顶层响应字段只为已发布的旧客户端保留;当前 Flutter 固定调用 V2,不做 V1 降级。
|
||
|
||
## HTTP 契约
|
||
|
||
新客户端调用:
|
||
|
||
```text
|
||
POST /api/v2/rooms/gift/send
|
||
```
|
||
|
||
请求沿用 V1 字段,并要求:
|
||
|
||
```json
|
||
{
|
||
"room_id": "room-id",
|
||
"command_id": "stable-combo-session_b1",
|
||
"combo_session_id": "stable-combo-session",
|
||
"batch_seq": 1,
|
||
"target_type": "user",
|
||
"target_user_ids": [1001, 1002],
|
||
"gift_id": "gift-id",
|
||
"gift_count": 20,
|
||
"pool_id": "optional-lucky-pool"
|
||
}
|
||
```
|
||
|
||
`command_id` 是唯一财务幂等键。超时、429 和临时 5xx 必须复用同一请求体与同一 `command_id`;`combo_session_id + batch_seq` 只用于客户端批次顺序和审计。当前 Flutter 不在 404 或旧响应下改调 V1,发布前必须先保证 V2 路由可用。
|
||
|
||
响应继续返回 V1 顶层的 `billing_receipt_id`、`coin_balance_after`、`lucky_gift(s)`、`batch_display` 等字段,同时增加 `v2`:
|
||
|
||
```json
|
||
{
|
||
"billing_receipt_id": "legacy-compatible-receipt",
|
||
"coin_balance_after": 9000,
|
||
"v2": {
|
||
"api_version": 2,
|
||
"command_id": "stable-combo-session_b1",
|
||
"combo_session_id": "stable-combo-session",
|
||
"batch_seq": 1,
|
||
"idempotent_replay": false,
|
||
"committed_room_version": 123,
|
||
"billing_receipt_id": "legacy-compatible-receipt",
|
||
"coin_balance_after": 9000,
|
||
"balance_version": 456,
|
||
"settled_gift_count": 20,
|
||
"settled_target_user_ids": [1001, 1002],
|
||
"lucky_hits": [],
|
||
"operation_status": "committed"
|
||
}
|
||
}
|
||
```
|
||
|
||
相同 `command_id` 的重试返回首次提交的同一账务回执、余额版本、实际目标、幸运结果和批次展示,仅把 `idempotent_replay` 置为 `true`。历史 V1 command log 没有 typed 结果时保持原有 snapshot-only 行为,不伪造结算结果。
|
||
|
||
## 客户端封批规则
|
||
|
||
礼物面板响应的 `combo_config` 控制客户端策略:
|
||
|
||
- `enabled`:紧急关闭微批;关闭后仍调用 V2,只是不再聚合连续点击。
|
||
- `api_version`:服务端契约版本标记,当前 Flutter 只接受 V2,不用它选择 V1 入口。
|
||
- `window_ms`:后续点击固定封批窗口,默认 300ms。
|
||
- `max_gift_count_per_batch`:单目标数量上限,最大 999。
|
||
- `max_gift_units_per_batch`:`gift_count * 去重收礼人数` 上限,最大 5000。
|
||
- `max_in_flight`:最多 2 个 HTTP 批次在途。
|
||
- `max_pending_batches`:本地有界队列上限。
|
||
- `retry_max_attempts`、`retry_base_delay_ms`:可靠重试上限和指数退避基数;429 优先遵守 `Retry-After`。
|
||
- `rollout_basis_points`:按 `app_code + user_id` 稳定分桶,0 到 10000;只灰度微批策略,不改变 V2 HTTP 入口。
|
||
|
||
后台通过以下 app-scoped 接口读取和全量替换配置:
|
||
|
||
```text
|
||
GET /admin/app-config/gift-combo
|
||
PUT /admin/app-config/gift-combo
|
||
```
|
||
|
||
服务端在 gateway、room、wallet、lucky 四层都强制校验 `gift_count <= 999`、总 gift units `<= 5000` 和幂等键长度。远程配置只能收紧客户端行为,不能提高 owner 上限。
|
||
|
||
## 上线顺序
|
||
|
||
1. 先执行 `services/room-service/deploy/mysql/migrations/002_gift_operation_v2.sql`,再滚动发布 protobuf、wallet、lucky、room、gateway,确认 V2 可用后发布当前 Flutter;已发布的旧 Flutter 继续走服务端 V1。
|
||
2. 首次上线把 `rollout_basis_points` 设为 0,验证 V2 单批发送、重复 `command_id` 重放和 Gift Saga 恢复指标;该值不会让当前 Flutter 回退 V1。
|
||
3. 按 App 从小流量提高微批灰度;异常时把 `enabled=false` 或灰度归零,客户端仍使用 V2 单批发送。
|
||
4. MQ 先保持 `wallet_event_tag_mode=legacy`、`room_event_tag_mode=legacy`。确认所有兼容 consumer 已上线并排空存量后,再分别切为 `event_type`;不要在同一批发布中同时切 producer 和删除 legacy selector。
|
||
|
||
数据库迁移、服务发布、Flutter 微批灰度和 MQ Tag 切换是四个独立开关。任何一步都不能用“客户端已限流”替代 owner 幂等、Saga 恢复或 Broker 侧过滤。
|