hyapp-server/docs/flutter对接/幸运礼物Flutter对接.md

20 KiB
Raw Blame History

幸运礼物 Flutter 对接

本文描述 Flutter App 对接幸运礼物所需的 HTTP 接口、请求参数、返回值、错误处理和腾讯云 IM 自定义消息。幸运礼物支持多个互相独立的奖池App 只负责传入 pool_id,不计算 RTP、倍率概率、奖池水位或中奖结果。

基础约定

本地开发地址:

http://127.0.0.1:13000

业务接口统一响应 envelope

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {}
}
字段 说明
code 成功固定 OK;失败为稳定错误码。
message 短错误文案,不用于客户端分支判断。
request_id 服务端链路追踪 ID客户端日志要记录。
data 成功业务数据;失败时通常不存在。

通用请求头:

Header 必填 说明
Authorization Bearer <access_token>
X-App-Code App 编码,默认 lalu;登录态接口最终以 token 内 app_code 为准。
Content-Type POST 必填 application/json

所有 *_ms 都是 Unix epoch milliseconds。金额单位都是整数金币。

核心概念

概念 说明
gift_id 用户实际送出的幸运礼物 ID例如某个 100 金币、5000 金币或 500000 金币礼物。
pool_id 后台配置的幸运礼物奖池 ID例如 pool_95pool_98。不同 pool_id 的 RTP、奖池、风控和用户阶段完全隔离。
command_id 本次送礼动作幂等键。用户同一次点击产生一个值HTTP 超时或重试必须复用。
coin_spent 服务端按礼物价格和数量计算的实际扣费金额App 不上传该字段。
multiplier_ppm 倍率的 ppm 表达,1x=10000000.5x=500000100x=100000000
base_reward_coins 基础 RTP 返奖,计入平台成本控制。
effective_reward_coins 用户可见总奖励,等于基础返奖、房间气氛奖励、活动补贴之和。

App 不要展示后台 RTP 数值,不要展示实时概率,不要本地推算“下一次该中”。客户端只展示服务端返回的结果。

当前实现状态

当前仓库已经具备:

能力 状态
POST /api/v1/rooms/gift/send 支持 pool_id 入参 已有
房间送礼 IM room_gift_sent 可携带 pool_id 已有
POST /api/v1/activities/lucky-gifts/check 已有
activity-service 内部 gRPC CheckLuckyGift / ExecuteLuckyGiftDraw 已有
后台按 pool_id 配置多奖池 已有
送礼响应直接返回抽奖结果 data.lucky_gift 已有
房间 IM lucky_gift_drawn 已有
中奖返奖入账并触发 WalletBalanceChanged 已有

简洁版接入清单见 幸运礼物接口闭环Flutter对接.md

客户端流程

  1. 进入房间后调用 GET /api/v1/rooms/{room_id}/gift-panel 获取礼物面板、金币余额和收礼人。
  2. 找到 gift_type_code=luckygift_type_code=super_lucky 的礼物,读取该礼物对应的 pool_id。当前可从 presentation_json.lucky_pool_id 下发,后续建议提升为顶层字段 lucky_pool_id
  3. 用户点击幸运礼物前,可调用 POST /api/v1/activities/lucky-gifts/check 判断奖池是否启用;当前 check 接口要求 pool_id 必填。
  4. 用户确认送礼时调用 POST /api/v1/rooms/gift/send,请求体带上 pool_id
  5. 成功响应里如果有 lucky_gift,立即播放中奖或未中奖反馈。
  6. 房间内监听 room_gift_sentlucky_gift_drawn IM给其他用户同步展示。
  7. 金币余额以 WalletBalanceChanged 私有 IM 或 GET /api/v1/wallet/me/balances 刷新为准,不在本地推算。

获取礼物面板

GET /api/v1/rooms/{room_id}/gift-panel
Authorization: Bearer <access_token>
X-App-Code: lalu

路径参数:

参数 类型 必填 说明
room_id string 当前语音房 ID。

成功响应节选:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "coin_balance": 1000000,
    "recipients": [],
    "tabs": [],
    "gifts": [
      {
        "gift_id": "lucky_star_100",
        "name": "幸运星",
        "gift_type_code": "lucky",
        "charge_asset_type": "COIN",
        "coin_price": 100,
        "presentation_json": "{\"lucky_pool_id\":\"pool_95\"}",
        "resource": {
          "asset_url": "https://cdn.example/lucky_star.png",
          "animation_url": "https://cdn.example/lucky_star.svga"
        }
      }
    ],
    "quantity_presets": [1, 9, 99, 999]
  }
}

幸运礼物识别规则:

字段 说明
gift_type_code=lucky 普通幸运礼物。
gift_type_code=super_lucky 超级幸运礼物。
presentation_json.lucky_pool_id 建议下发的奖池 ID为空时 App 传 default 或不传,由服务端按默认奖池处理。
coin_price 单个礼物价格;实际扣费仍以服务端钱包价格为准。

检查幸运礼物状态

已实现 App HTTP 接口:

POST /api/v1/activities/lucky-gifts/check
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json

请求体:

{
  "room_id": "lalu_xxx",
  "gift_id": "lucky_star_100",
  "pool_id": "pool_95"
}

参数:

字段 类型 必填 说明
room_id string 当前房间 ID。
gift_id string 用户准备送出的幸运礼物 ID。
pool_id string 奖池 ID当前 gateway check 接口必填。

成功响应:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "enabled": true,
    "reason": "enabled",
    "pool_id": "pool_95",
    "gift_id": "lucky_star_100",
    "rule_version": 12,
    "experience_pool": "novice",
    "server_time_ms": 1779259000000
  }
}

返回字段:

字段 类型 说明
enabled bool 是否允许当前用户在当前房间抽该奖池。
reason string 服务端原因,客户端只做日志或灰度排查。
pool_id string 实际使用的奖池 ID。
gift_id string 礼物 ID。
rule_version int64 当前奖池规则版本。
experience_pool string noviceintermediateadvanced,只影响展示文案或动效强度。
server_time_ms int64 服务端当前时间。

客户端处理:

场景 处理
enabled=true 允许点击送礼。
enabled=false 置灰幸运抽奖入口,但普通礼物展示可继续。
接口失败 不在本地兜底开奖;可以提示稍后重试或隐藏幸运玩法。

注意:room-service 内部仍会把空 pool_id 归一化为默认奖池,但 App check 接口不接受空值。客户端必须从礼物面板拿到明确 pool_id 后再走幸运礼物流程。

送礼并抽奖

已存在 App HTTP 入口:

POST /api/v1/rooms/gift/send
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json

请求体:

{
  "room_id": "lalu_xxx",
  "command_id": "gift_1779259000000_10001_01",
  "target_type": "user",
  "target_user_ids": [10002],
  "gift_id": "lucky_star_100",
  "gift_count": 1,
  "pool_id": "pool_95"
}

参数:

字段 类型 必填 说明
room_id string 当前房间 ID。
command_id string 本次用户动作幂等键;同一次重试必须复用。
target_type string 当前主流程使用 user;为空按 user
target_user_ids int64[] 收礼用户 IDtarget_type=user 时必须且只能 1 个。
target_user_id int64 单目标兼容字段;新代码优先用 target_user_ids
gift_id string 礼物 ID。
gift_count int32 礼物数量,必须大于 0。
pool_id string 幸运礼物必填 幸运礼物奖池 ID不同奖池完全隔离。普通礼物可不传。

成功响应节选:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_xxx",
  "data": {
    "result": {
      "applied": true,
      "room_version": 14,
      "server_time_ms": 1779259000000
    },
    "billing_receipt_id": "br_xxx",
    "room_heat": 123456,
    "gift_rank": [],
    "room": {},
    "treasure": {},
    "lucky_gift": {
      "enabled": true,
      "draw_id": "lucky_draw_xxx",
      "command_id": "gift_1779259000000_10001_01",
      "pool_id": "pool_95",
      "gift_id": "lucky_star_100",
      "rule_version": 12,
      "experience_pool": "novice",
      "selected_tier_id": "novice_2x",
      "multiplier_ppm": 2000000,
      "base_reward_coins": 200,
      "room_atmosphere_reward_coins": 0,
      "activity_subsidy_coins": 0,
      "effective_reward_coins": 200,
      "reward_status": "granted",
      "stage_feedback": false,
      "high_multiplier": false,
      "created_at_ms": 1779259000000
    }
  }
}

lucky_gift 字段已实现。普通礼物或未执行幸运礼物抽奖时可以为空。

data 字段:

字段 类型 说明
result.applied bool 本次命令是否实际落地;幂等重放可能为 false
result.room_version int64 房间版本。
result.server_time_ms int64 服务端时间。
billing_receipt_id string 钱包扣费回执;存在表示送礼扣费成功。
room_heat int64 房间热度。
gift_rank array 房间礼物榜快照。
room object 房间快照。
treasure object 宝箱状态;如果房间宝箱开启可直接更新。
lucky_gift object 幸运礼物抽奖结果;已实现。

lucky_gift 字段:

字段 类型 说明
enabled bool 本次是否执行了幸运礼物抽奖。
draw_id string 抽奖记录 ID。
command_id string 和送礼请求一致的幂等键。
pool_id string 本次使用的奖池 ID。
gift_id string 礼物 ID。
rule_version int64 抽奖时使用的规则版本。
experience_pool string noviceintermediateadvanced
selected_tier_id string 服务端选中的奖档 ID。
multiplier_ppm int64 已返回;用于直接展示倍率。客户端不要从 selected_tier_id 解析倍率。
base_reward_coins int64 基础 RTP 返奖。
room_atmosphere_reward_coins int64 房间气氛补贴奖励。
activity_subsidy_coins int64 活动补贴奖励。
effective_reward_coins int64 用户可见总奖励。
reward_status string pendinggrantedfailed
stage_feedback bool 是否触发阶段反馈。可能没有金币奖励。
high_multiplier bool 是否属于高倍结果,可用于强化动效。
created_at_ms int64 抽奖结果生成时间。

展示规则:

场景 处理
lucky_gift 为空 按普通礼物成功展示。
effective_reward_coins=0stage_feedback=false 展示未中奖或普通送礼效果。
stage_feedback=true 展示阶段进度、幸运值或轻量动画;不要本地加金币。
effective_reward_coins>0reward_status=granted 展示中奖金额和到账效果;余额仍以钱包 IM 为准。
effective_reward_coins>0reward_status=pending 展示“奖励处理中”,等待钱包 IM 或余额刷新。
reward_status=failed 不本地加金币;提示稍后刷新或走补偿查询。
high_multiplier=true 播放高倍中奖动效,并等待房间 IM 同步其他用户。

错误处理

通用错误:

HTTP code 说明 Flutter 处理
400 INVALID_JSON JSON 解析失败。 修正客户端请求体。
400 INVALID_ARGUMENT 参数不合法,例如缺 room_idgift_idcommand_id 不重试,提示操作失败并打点。
401 UNAUTHORIZED token 无效或过期。 走登录刷新或重新登录。
403 PROFILE_REQUIRED 用户未完成资料。 引导补全资料。
403 PERMISSION_DENIED 没有权限。 提示不可操作。
409 INSUFFICIENT_BALANCE 金币不足。 弹充值或余额不足提示。
409 ROOM_CLOSED 房间已关闭。 退出或刷新房间状态。
409 RULE_NOT_ACTIVE 幸运礼物奖池未启用。 置灰幸运玩法,不本地开奖。
409 IDEMPOTENCY_CONFLICT 同一 command_id 对应的请求内容不一致。 生成新的 command_id 重新发起新动作;旧动作不再重放。
409 CONFLICT 规则、奖池、水位或风控状态冲突。 提示稍后再试,必要时刷新面板。
502 UPSTREAM_ERROR room、wallet、activity 等依赖暂不可用。 可保留同一个 command_id 重试同一次送礼。
500 INTERNAL_ERROR 服务端内部异常。 记录 request_id,提示稍后重试。

幂等和重试规则:

场景 处理
HTTP 超时、断网、502 使用同一个 command_id 重试。
409 IDEMPOTENCY_CONFLICT 不要复用该 command_id;说明客户端把同一个幂等键用于不同请求。
收到成功响应且有 billing_receipt_id 扣费已成功,不要再次生成新命令补发。
成功响应丢失但钱包余额已变化 以钱包余额和房间 IM 为准,避免重复扣费。
App 进程重启 本地持久化最近未完成的 command_id,优先用同一键查询或重试。

IM房间送礼消息

已存在房间 IM

字段
腾讯消息类型 TIMCustomElem
Desc room_gift_sent
Ext / extension room_system_message
群 ID 当前 room_id
CloudCustomData TIMCustomElem.Data 相同的 JSON 字符串

Payload 示例:

{
  "event_id": "evt_xxx",
  "room_id": "lalu_xxx",
  "event_type": "room_gift_sent",
  "actor_user_id": 10001,
  "target_user_id": 10002,
  "gift_value": 100,
  "room_heat": 123456,
  "room_version": 14,
  "attributes": {
    "gift_id": "lucky_star_100",
    "pool_id": "pool_95",
    "gift_count": "1",
    "billing_receipt_id": "br_xxx",
    "coin_spent": "100",
    "gift_point_added": "10",
    "price_version": "pv_xxx"
  }
}

处理规则:

场景 处理
Ext 不是 room_system_message 忽略。
event_type 不是 room_gift_sent 交给其他房间消息处理器。
event_id 已处理 忽略。
room_version 小于本地当前版本 忽略或只展示轻量动画,不回滚房间状态。
attributes.pool_id 为空 按普通礼物处理。
attributes.pool_id 非空 展示幸运礼物送出效果,但中奖结果仍等 lucky_gift_drawn 或 HTTP lucky_gift

IM幸运礼物开奖结果

已实现房间 IM

字段
腾讯消息类型 TIMCustomElem
Desc lucky_gift_drawn
Ext / extension room_system_message
群 ID 当前 room_id
CloudCustomData TIMCustomElem.Data 相同的 JSON 字符串

Payload 示例:

{
  "event_id": "lucky_draw_event_xxx",
  "room_id": "lalu_xxx",
  "event_type": "lucky_gift_drawn",
  "room_version": 14,
  "draw_id": "lucky_draw_xxx",
  "command_id": "gift_1779259000000_10001_01",
  "pool_id": "pool_95",
  "gift_id": "lucky_star_100",
  "gift_count": 1,
  "sender_user_id": 10001,
  "target_user_id": 10002,
  "coin_spent": 100,
  "selected_tier_id": "novice_2x",
  "multiplier_ppm": 2000000,
  "effective_reward_coins": 200,
  "reward_status": "granted",
  "stage_feedback": false,
  "high_multiplier": false,
  "created_at_ms": 1779259000000
}

字段说明:

字段 类型 说明
event_id string IM 事件幂等键。
event_type string 固定 lucky_gift_drawn
room_id string 房间 ID。
room_version int64 房间版本。
draw_id string 抽奖记录 ID。
command_id string 送礼幂等键。
pool_id string 奖池 ID。
gift_id string 礼物 ID。
gift_count int32 礼物数量。
sender_user_id int64 送礼用户。
target_user_id int64 收礼用户。
coin_spent int64 本次实际扣费。
selected_tier_id string 中奖奖档。
multiplier_ppm int64 中奖倍率,已返回。
effective_reward_coins int64 用户可见奖励金币。
reward_status string pendinggrantedfailed
stage_feedback bool 是否阶段反馈。
high_multiplier bool 是否高倍结果。
created_at_ms int64 事件创建时间。

展示规则:

场景 处理
当前用户是 sender_user_id 如果 HTTP 已展示过同一 draw_id,只更新状态,不重复播中奖动画。
当前用户不是 sender_user_id 展示房间内中奖横幅、飘屏或公屏提示。
effective_reward_coins=0 不展示大奖飘屏,只保留普通送礼效果或阶段反馈。
high_multiplier=true 展示高倍动画。
reward_status=pending 文案使用“奖励处理中”,不要展示已到账。
JSON 解析失败或字段缺失 忽略并记录客户端日志。

IM钱包余额变更

幸运礼物扣费和返奖都应通过钱包私有 IM 同步余额。该 IM 已在钱包文档中定义。

字段
腾讯消息类型 TIMCustomElem
Desc WalletBalanceChanged
Ext / extension wallet_notice
通道 C2C 私有消息

Payload 示例:

{
  "event_type": "WalletBalanceChanged",
  "event_id": "wev_xxx",
  "app_code": "lalu",
  "transaction_id": "tx_xxx",
  "command_id": "gift_1779259000000_10001_01",
  "user_id": "10001",
  "asset_type": "COIN",
  "available_delta": -100,
  "frozen_delta": 0,
  "available_after": 999900,
  "frozen_after": 0,
  "balance_version": 18,
  "created_at_ms": 1779259000000,
  "source_created_at_ms": 1779259000000,
  "metadata": {
    "biz_type": "gift_debit",
    "gift_id": "lucky_star_100",
    "pool_id": "pool_95"
  }
}

返奖到账时 available_delta 为正数,metadata.biz_type 建议使用 lucky_gift_reward

处理规则:

场景 处理
Ext 不是 wallet_notice 忽略。
event_type 不是 WalletBalanceChanged 忽略。
event_id 已处理 忽略。
本地 asset_type 的版本 >= balance_version 忽略。
长时间没有收到余额 IM GET /api/v1/wallet/me/balances 兜底刷新。

IM高倍全站或区域播报

已实现区域播报 IM用于高倍中奖或运营配置的大额中奖展示当前服务端触发阈值是 multiplier_ppm >= 1000000 且有 visible_region_id

字段
腾讯消息类型 TIMCustomElem
Desc lucky_gift_big_win
Ext / extension im_broadcast
群 ID 全站播报群或区域播报群

Payload 示例:

{
  "event_id": "lucky_big_win_xxx",
  "broadcast_type": "lucky_gift_big_win",
  "scope": "region",
  "app_code": "lalu",
  "region_id": 1001,
  "room_id": "lalu_xxx",
  "sender_user_id": 10001,
  "target_user_id": 10002,
  "pool_id": "pool_95",
  "gift_id": "lucky_star_100",
  "multiplier_ppm": 100000000,
  "effective_reward_coins": 10000,
  "sent_at_ms": 1779259000000,
  "action": {
    "type": "enter_room",
    "room_id": "lalu_xxx"
  }
}

处理规则:

场景 处理
当前用户已在该房间且已收到 lucky_gift_drawn 可以只展示一次,避免重复刷屏。
当前用户不在该房间 展示全站或区域飘屏,点击 action 进房。
event_id 已处理 忽略。

App 最小对接顺序

  1. 登录后调用 GET /api/v1/im/usersig,登录腾讯云 IM。
  2. 进入房间后加入房间 IM 群,并调用 GET /api/v1/rooms/{room_id}/gift-panel
  3. 从礼物面板识别幸运礼物和对应 pool_id
  4. 用户点击送礼时生成 command_id,调用 POST /api/v1/rooms/gift/send
  5. 使用 HTTP 响应里的 lucky_gift 做当前用户即时反馈。
  6. 监听房间 IM room_gift_sentlucky_gift_drawn,同步其他用户看到的表现。
  7. 监听 C2C IM WalletBalanceChanged,更新金币余额。
  8. 任意 IM 丢失、页面重进或余额不一致时,重新拉取礼物面板和钱包余额纠正状态。