hyapp-server/docs/房内红包需求接口拆分.md

19 KiB
Raw Blame History

房内红包需求接口拆分

1. 结论

房内红包拆成两类通道:

  • HTTP 接口:承载有状态业务,包括配置、发送、领取、详情、记录、退款后台操作;房间在线态使用语音房通用接口。
  • IM 消息:承载实时 UI 触达,包括房内弹窗、延时红包同语区飘窗、倒计时结束强弹、侧边栏数量刷新。

本需求以产品文档为准:

  • 红包类型:即时红包、延时红包。
  • 发送成功立刻扣除金币。
  • 发送者本人可以领取自己发出的红包。
  • 红包有效领取时间为 24 小时,过期未领取金额退回发送者。
  • 即时红包不触发同语区广播飘窗。
  • 延时红包发送成功后触发同语区广播飘窗。
  • 当前房间内无论即时还是延时,发送成功后都需要立刻弹出抢红包窗口。

现有历史文档中的 2 小时退款 不适用于本次新需求,应改为 24 小时。

2. 统一响应约定

现有 Go 网关成功响应会同时返回 bodydata,客户端读取任意一个都可以。

成功响应:

{
  "status": true,
  "errorCode": 0,
  "errorMsg": "success",
  "body": {},
  "code": "success",
  "message": "success",
  "data": {},
  "time": 1780000000000
}

错误响应:

{
  "status": false,
  "errorCode": 409,
  "errorCodeName": "voice_room_red_packet_expired",
  "errorMsg": "red packet expired",
  "body": null,
  "code": "voice_room_red_packet_expired",
  "message": "red packet expired",
  "data": null,
  "time": 1780000000000
}

接口文档下文只定义 body/data 中的业务字段。

3. 哪些需要使用 IM

场景 IM key 发送范围 使用 IM 的原因
红包创建后当前房间立刻弹窗 ROOM_RED_PACKETevent=CREATEDscope=ROOM 当前房间 IM 群 房内所有在线用户需要实时收到弹窗,不能依赖轮询
延时红包同语区飘窗引流 ROOM_RED_PACKETevent=CREATEDscope=REGION 发送者所在同语区 IM 群 首页和其他房间用户没有主动请求,现有区域飘屏能力通过语区 IM 群触达
延时红包倒计时结束强弹 ROOM_RED_PACKETevent=OPEN_DUEscope=ROOM 当前房间 IM 群 用户可能提前关闭弹窗,到点必须再次强制弹出
红包抢完、过期、退款后刷新侧边栏 ROOM_RED_PACKETevent=STATUS_CHANGEDscope=ROOM 当前房间 IM 群 房内侧边栏红包数量需要实时减少

不使用 IM 的动作:

动作 原因
发送红包 需要鉴权、扣款、档位校验、日上限、幂等落库
抢红包 需要鉴权、并发防超卖、领取幂等、钱包入账
查询配置 普通读接口,礼物面板打开时请求即可
查询详情和记录 需要分页、权限和稳定数据
过期退款 服务端定时任务和后台补偿,不应由客户端 IM 触发

4. IM 消息格式

现有腾讯 IM 自定义消息通过 TIMCustomElem.MsgContent.Data 发送 JSON 字符串。业务层统一使用外层:

{
  "type": "ROOM_RED_PACKET",
  "data": {}
}

腾讯 IM 实际 payload

{
  "MsgType": "TIMCustomElem",
  "MsgContent": {
    "Data": "{\"type\":\"ROOM_RED_PACKET\",\"data\":{...}}",
    "Desc": "customBody",
    "Sound": "dingdong.aiff"
  }
}

4.1 CREATED

当前房间弹窗和同语区飘窗都使用 CREATED,通过 scope 区分。

{
  "type": "ROOM_RED_PACKET",
  "data": {
    "event": "CREATED",
    "scope": "ROOM",
    "packetNo": "VRRP2026052712340001",
    "packetId": "VRRP2026052712340001",
    "roomId": "9001",
    "regionCode": "AR",
    "packetMode": "DELAYED",
    "senderUserId": "10001",
    "actualAccount": "10001",
    "userNickname": "Tom",
    "userAvatar": "https://example.com/avatar.png",
    "totalAmount": 15000,
    "totalCount": 5,
    "remainCount": 5,
    "status": "ACTIVE",
    "delaySeconds": 300,
    "claimStartTime": 1780000000000,
    "expireTime": 1780086400000,
    "serverTime": 1779999700000
  }
}

字段说明:

字段 类型 说明
event string 固定为 CREATED
scope string ROOM 表示房间弹窗,REGION 表示同语区飘窗
packetNo string 红包唯一编号,接口参数使用该字段
packetId string 兼容旧 Flutter 飘屏字段,值同 packetNo
roomId string 红包所属房间,点击飘窗进入该房间
regionCode string 语区编码
packetMode string IMMEDIATEDELAYED
senderUserId string 发送者用户 ID
actualAccount string 发送者账号
userNickname string 发送者昵称
userAvatar string 发送者头像
totalAmount int64 红包总金币
totalCount int 红包份数
remainCount int 剩余可领份数
status string 红包状态
delaySeconds int 后台配置的延时秒数
claimStartTime int64 可领取开始时间,毫秒时间戳
expireTime int64 过期时间,毫秒时间戳,创建后 24 小时
serverTime int64 服务端当前时间,毫秒时间戳

发送规则:

  • 即时红包:只发送 scope=ROOM 到当前房间 IM 群。
  • 延时红包:发送 scope=ROOM 到当前房间 IM 群;同时发送 scope=REGION 到同语区 IM 群。

4.2 OPEN_DUE

延时红包倒计时结束后,服务端向当前房间发送强弹消息。

{
  "type": "ROOM_RED_PACKET",
  "data": {
    "event": "OPEN_DUE",
    "scope": "ROOM",
    "packetNo": "VRRP2026052712340001",
    "packetId": "VRRP2026052712340001",
    "roomId": "9001",
    "packetMode": "DELAYED",
    "claimStartTime": 1780000000000,
    "expireTime": 1780086400000,
    "serverTime": 1780000000000
  }
}

4.3 STATUS_CHANGED

红包被抢完、过期、退款完成后,服务端向当前房间发送状态刷新消息。

{
  "type": "ROOM_RED_PACKET",
  "data": {
    "event": "STATUS_CHANGED",
    "scope": "ROOM",
    "packetNo": "VRRP2026052712340001",
    "packetId": "VRRP2026052712340001",
    "roomId": "9001",
    "status": "EXPIRED",
    "remainCount": 0,
    "refundedAmount": 3000,
    "serverTime": 1780086400000
  }
}

客户端收到后刷新侧边栏未领取数量;如果当前展示的是该红包弹窗或详情页,同步刷新状态。

5. App HTTP 接口

5.1 获取当前用户语区 IM 群

GET /app/region-im-group/current

兼容旧路径:

GET /app/voice-room/red-packet/im-group

返回:

{
  "configured": true,
  "sysOrigin": "LIKEI",
  "regionCode": "AR",
  "groupId": "@TGS#xxxx",
  "groupName": "region-im-group-LIKEI-AR",
  "status": "ACTIVE"
}

客户端处理:

  • configured=truegroupId 非空时加入腾讯 IM 群。
  • joinGroup 返回成功或已在群内都按成功处理。
  • 区域变化时退出旧区域群并加入新区域群。

5.2 通用房间心跳

POST /api/v1/rooms/heartbeat

请求:

{
  "room_id": "9001",
  "command_id": "cmd_heartbeat_9001_10001_1780000000000"
}

返回:

{
  "result": {
    "ok": true
  },
  "user": {
    "user_id": "10001",
    "room_id": "9001",
    "last_seen_at_ms": 1780000000000
  },
  "room": {}
}

用途:

  • 这是语音房通用 presence 能力,不属于红包专用接口。
  • 只刷新已经存在的业务 presence不能替代进房 JoinRoom
  • 红包发送、红包领取、侧边栏有效红包查询都依赖该 presence 判断用户是否仍在当前房间。
  • 用户不在房间、已离房、被踢或房间关闭时,心跳应失败,客户端应停止心跳并清理本地房间态。

客户端心跳间隔由房间模块统一配置,必须小于 room-service 的 presence stale 窗口。

5.3 通用离开房间

POST /api/v1/rooms/leave

请求:

{
  "room_id": "9001",
  "command_id": "cmd_leave_9001_10001_1780000000000"
}

返回:

{
  "result": {
    "ok": true
  },
  "room": {}
}

用途:

  • 这是语音房通用离房接口,不属于红包专用接口。
  • 主动关闭房间页、切换账号、被客户端确认退出时调用。
  • 离房后 room-service 移除业务 presence红包领取接口再校验该用户时应返回不在房间。
  • 如果用户在麦上,离房流程同时释放麦位。
  • LeaveRoom 后客户端必须停止房间心跳、退出 RTC 房、退出或忽略 IM 房间群消息。

5.4 红包配置

GET /app/voice-room/red-packet/config

返回:

{
  "enabled": true,
  "dailySendLimit": 10,
  "todaySendCount": 1,
  "delaySeconds": 300,
  "expireSeconds": 86400,
  "amountOptions": [15000, 30000, 70000, 100000],
  "countOptions": [5, 10, 20, 50],
  "ruleUrl": "https://example.com/red-packet-rule"
}

参数定义:

字段 类型 说明
enabled bool 红包功能开关
dailySendLimit int 每日发送上限
todaySendCount int 当前用户当天已发送数量
delaySeconds int 延时红包等待秒数,后台全局唯一配置
expireSeconds int 固定 86400 秒
amountOptions int64[] 后台配置金额档位
countOptions int[] 后台配置红包份数档位
ruleUrl string 红包规则 H5 地址

5.5 发送红包

POST /app/voice-room/red-packet/send

请求:

{
  "requestId": "client-unique-id",
  "roomId": "9001",
  "packetMode": "DELAYED",
  "totalAmount": 15000,
  "totalCount": 5
}

参数定义:

字段 类型 必填 说明
requestId string 客户端幂等 ID重复请求返回同一红包
roomId string 当前房间 ID
packetMode string IMMEDIATEDELAYED
totalAmount int64 总金币,必须命中后台金额档位
totalCount int 红包份数,必须命中后台份数档位

返回:

{
  "packetNo": "VRRP2026052712340001",
  "roomId": "9001",
  "packetMode": "DELAYED",
  "totalAmount": 15000,
  "totalCount": 5,
  "remainAmount": 15000,
  "remainCount": 5,
  "claimStartTime": "2026-05-27T21:05:00+08:00",
  "claimStartTimeMs": 1780000000000,
  "expireTime": "2026-05-28T21:00:00+08:00",
  "expireTimeMs": 1780086400000,
  "status": "ACTIVE",
  "balanceAfter": 100000
}

服务端逻辑:

  1. 校验登录态、功能开关,并通过 room-service 通用 presence 校验发送者仍在 roomId 房间内。
  2. 校验金额和份数档位。
  3. 校验每日发送上限。
  4. 校验余额并立刻扣金币。
  5. 预拆随机份额,写红包主表和份额表。
  6. 即时红包 claimStartTime = createdAt
  7. 延时红包 claimStartTime = createdAt + delaySeconds
  8. 所有红包 expireTime = createdAt + 24h
  9. 发送对应 IM 消息。

5.6 房间有效红包列表

GET /app/voice-room/red-packet/active?roomId=9001

用途:

  • 进入房间时初始化侧边栏红包数量。
  • 用户关闭弹窗后,通过侧边栏图标重新唤起红包窗口。
  • 收到 IM 状态变化后刷新列表。

返回:

{
  "roomId": "9001",
  "unclaimedCount": 3,
  "items": [
    {
      "packetNo": "VRRP2026052712340001",
      "packetMode": "DELAYED",
      "senderUserId": "10001",
      "senderNickname": "Tom",
      "senderAvatar": "https://example.com/avatar.png",
      "totalAmount": 15000,
      "totalCount": 5,
      "remainCount": 5,
      "claimStartTime": "2026-05-27T21:05:00+08:00",
      "claimStartTimeMs": 1780000000000,
      "expireTime": "2026-05-28T21:00:00+08:00",
      "expireTimeMs": 1780086400000,
      "status": "ACTIVE",
      "currentUserClaimed": false,
      "currentUserAmount": 0
    }
  ]
}

unclaimedCount 统计当前用户未领取且未过期、未抢完的红包数量。

5.7 抢红包

POST /app/voice-room/red-packet/claim

请求:

{
  "packetNo": "VRRP2026052712340001"
}

返回:

{
  "packetNo": "VRRP2026052712340001",
  "claimed": true,
  "amount": 3000,
  "remainCount": 4,
  "totalCount": 5,
  "claimTime": "2026-05-27T21:05:10+08:00",
  "claimTimeMs": 1780000010000,
  "status": "ACTIVE"
}

领取失败但业务可展示的情况通过错误码返回:

  • voice_room_red_packet_room_presence_required:用户不在红包所属房间内,需要先进入房间。
  • voice_room_red_packet_not_started:延时红包未到可领取时间。
  • voice_room_red_packet_expired:红包已过期。
  • voice_room_red_packet_finished:红包已抢完。
  • voice_room_region_not_match:非同语区不可领取。

领取前校验:

  • 抢红包接口不创建、不刷新房间 presence只读取 room-service 当前 presence。
  • 用户必须仍在 packetNo 对应的 roomId 房间内,否则返回 voice_room_red_packet_room_presence_required
  • 进入房间和离开房间仍由通用房间接口负责,不在红包接口里重复实现。

幂等要求:

  • 同一用户重复领取同一红包,返回第一次领取结果。
  • 钱包入账处理中,返回 claim_processing,客户端可重试或查详情。

5.8 红包详情

GET /app/voice-room/red-packet/detail?packetNo=VRRP2026052712340001

返回:

{
  "packetNo": "VRRP2026052712340001",
  "roomId": "9001",
  "packetMode": "DELAYED",
  "sendTime": "2026-05-27 21:00:00",
  "sendTimeMs": 1779999700000,
  "totalCount": 5,
  "totalAmount": 15000,
  "remainCount": 4,
  "remainAmount": 12000,
  "claimedCount": 1,
  "claimedAmount": 3000,
  "refundAmount": 0,
  "claimStartTime": "2026-05-27T21:05:00+08:00",
  "claimStartTimeMs": 1780000000000,
  "expireTime": "2026-05-28T21:00:00+08:00",
  "expireTimeMs": 1780086400000,
  "status": "ACTIVE",
  "sender": {
    "userId": "10001",
    "account": "10001",
    "nickname": "Tom",
    "avatar": "https://example.com/avatar.png"
  },
  "currentUserClaimed": true,
  "currentUserAmount": 3000
}

5.9 领取记录列表

GET /app/voice-room/red-packet/claim-records?packetNo=VRRP2026052712340001&cursor=&limit=20

返回:

{
  "packetNo": "VRRP2026052712340001",
  "totalCount": 5,
  "claimedCount": 1,
  "claimedAmount": 3000,
  "records": [
    {
      "id": "2052643780674195456",
      "userId": "10002",
      "account": "10002",
      "nickname": "Jerry",
      "avatar": "https://example.com/avatar2.png",
      "amount": 3000,
      "claimTime": "2026-05-27 21:05:10",
      "claimTimeMs": 1780000010000
    }
  ],
  "nextCursor": "2052643780674195456",
  "hasMore": false
}

5.10 发送记录列表

GET /app/voice-room/red-packet/sent-records?cursor=&limit=20

返回:

{
  "records": [
    {
      "packetNo": "VRRP2026052712340001",
      "roomId": "9001",
      "packetMode": "DELAYED",
      "sendTime": "2026-05-27 21:00:00",
      "sendTimeMs": 1779999700000,
      "totalCount": 5,
      "totalAmount": 15000,
      "refundAmount": 0,
      "status": "ACTIVE"
    }
  ],
  "nextCursor": "VRRP2026052712340001",
  "hasMore": false
}

列表页展示字段:

  • 发送时间:sendTime
  • 红包个数:totalCount
  • 红包总金额:totalAmount
  • 退回金额:refundAmount

6. 后台接口

6.1 查询配置

GET /resident-activity/voice-room-red-packet/config

返回:

{
  "enabled": true,
  "dailySendLimit": 10,
  "delaySeconds": 300,
  "expireSeconds": 86400,
  "amountOptions": [15000, 30000, 70000, 100000],
  "countOptions": [5, 10, 20, 50],
  "ruleUrl": "https://example.com/red-packet-rule"
}

6.2 保存配置

POST /resident-activity/voice-room-red-packet/config

请求:

{
  "enabled": true,
  "dailySendLimit": 10,
  "delaySeconds": 300,
  "amountOptions": [15000, 30000, 70000, 100000],
  "countOptions": [5, 10, 20, 50],
  "ruleUrl": "https://example.com/red-packet-rule"
}

说明:

  • expireSeconds 固定为 86400不建议后台配置。
  • amountOptionscountOptions 必须都是正整数数组。
  • totalAmount >= totalCount,保证每份最小金额大于 0。

6.3 红包记录查询

GET /resident-activity/voice-room-red-packet/records?sysOrigin=LIKEI&roomId=&senderUserId=&status=&startTime=&endTime=&page=1&pageSize=20

返回:

{
  "page": 1,
  "pageSize": 20,
  "total": 1,
  "records": [
    {
      "packetNo": "VRRP2026052712340001",
      "roomId": "9001",
      "senderUserId": "10001",
      "senderNickname": "Tom",
      "packetMode": "DELAYED",
      "totalAmount": 15000,
      "totalCount": 5,
      "claimedAmount": 3000,
      "claimedCount": 1,
      "refundAmount": 0,
      "status": "ACTIVE",
      "sendTime": "2026-05-27 21:00:00",
      "expireTime": "2026-05-28 21:00:00"
    }
  ]
}

6.4 退款重试

POST /resident-activity/voice-room-red-packet/refund/retry

请求:

{
  "packetNo": "VRRP2026052712340001"
}

返回:

{
  "packetNo": "VRRP2026052712340001",
  "refundAmount": 3000,
  "refundStatus": "SUCCESS"
}

7. 错误码

code 场景 客户端处理
voice_room_red_packet_disabled 功能关闭 隐藏入口或提示暂不可用
voice_room_red_packet_amount_invalid 金额不在后台档位 刷新配置
voice_room_red_packet_count_invalid 个数不在后台档位 刷新配置
voice_room_red_packet_daily_limit_exceeded 今日发送上限 提示今日发送次数已达上限
voice_room_red_packet_room_presence_required 用户不在当前房间在线态 提示需在房间内操作
voice_room_red_packet_insufficient_balance 余额不足 拉起充值弹窗
voice_room_region_missing 用户区域缺失 提示暂不可参与
voice_room_region_not_match 非同语区领取 提示非同语区不可领取
voice_room_red_packet_not_found 红包不存在 刷新列表
voice_room_red_packet_not_started 延时红包未到时间 展示倒计时
voice_room_red_packet_expired 红包已过期 提示已过期并刷新侧边栏
voice_room_red_packet_finished 红包已抢完 提示已抢完并刷新侧边栏
claim_processing 钱包入账处理中 展示处理中,可重试或查详情
voice_room_red_packet_wallet_deduct_failed 扣款失败 提示发送失败
voice_room_red_packet_wallet_income_failed 入账失败 提示领取处理中或稍后重试
voice_room_red_packet_refund_failed 退款失败 后台重试

8. 验证点

功能验证:

  • 发送即时红包,当前房间用户收到 ROOM_RED_PACKET CREATED scope=ROOM,同语区群不收到飘窗。
  • 发送延时红包,当前房间用户收到 scope=ROOM 弹窗,同语区用户收到 scope=REGION 飘窗。
  • 延时红包未到 claimStartTime 领取返回 voice_room_red_packet_not_started
  • 延时红包到点后房间收到 OPEN_DUE,已关闭弹窗的用户被强弹。
  • 发送者本人可以领取自己发送的红包。
  • 同一用户重复领取同一红包返回同一领取结果。
  • 并发领取不会超过 totalAmounttotalCount
  • 红包创建 24 小时后,未领取金额退回发送者。
  • 领取成功、抢完、过期后房间侧边栏未领取数量实时减少。

接口验证:

  • 所有成功接口同时返回 bodydata
  • 所有失败接口 status=falsebody/data=nullcodeerrorCodeName 一致。
  • requestId 重复发送只生成一个红包。
  • packetNo + userId 重复领取只生成一条领取记录。