19 KiB
19 KiB
房内红包需求接口拆分
1. 结论
房内红包拆成两类通道:
- HTTP 接口:承载有状态业务,包括配置、发送、领取、详情、记录、退款后台操作;房间在线态使用语音房通用接口。
- IM 消息:承载实时 UI 触达,包括房内弹窗、延时红包同语区飘窗、倒计时结束强弹、侧边栏数量刷新。
本需求以产品文档为准:
- 红包类型:即时红包、延时红包。
- 发送成功立刻扣除金币。
- 发送者本人可以领取自己发出的红包。
- 红包有效领取时间为 24 小时,过期未领取金额退回发送者。
- 即时红包不触发同语区广播飘窗。
- 延时红包发送成功后触发同语区广播飘窗。
- 当前房间内无论即时还是延时,发送成功后都需要立刻弹出抢红包窗口。
现有历史文档中的 2 小时退款 不适用于本次新需求,应改为 24 小时。
2. 统一响应约定
现有 Go 网关成功响应会同时返回 body 和 data,客户端读取任意一个都可以。
成功响应:
{
"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_PACKET,event=CREATED,scope=ROOM |
当前房间 IM 群 | 房内所有在线用户需要实时收到弹窗,不能依赖轮询 |
| 延时红包同语区飘窗引流 | ROOM_RED_PACKET,event=CREATED,scope=REGION |
发送者所在同语区 IM 群 | 首页和其他房间用户没有主动请求,现有区域飘屏能力通过语区 IM 群触达 |
| 延时红包倒计时结束强弹 | ROOM_RED_PACKET,event=OPEN_DUE,scope=ROOM |
当前房间 IM 群 | 用户可能提前关闭弹窗,到点必须再次强制弹出 |
| 红包抢完、过期、退款后刷新侧边栏 | ROOM_RED_PACKET,event=STATUS_CHANGED,scope=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 | IMMEDIATE 或 DELAYED |
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=true且groupId非空时加入腾讯 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 | 是 | IMMEDIATE 或 DELAYED |
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
}
服务端逻辑:
- 校验登录态、功能开关,并通过 room-service 通用 presence 校验发送者仍在
roomId房间内。 - 校验金额和份数档位。
- 校验每日发送上限。
- 校验余额并立刻扣金币。
- 预拆随机份额,写红包主表和份额表。
- 即时红包
claimStartTime = createdAt。 - 延时红包
claimStartTime = createdAt + delaySeconds。 - 所有红包
expireTime = createdAt + 24h。 - 发送对应 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,不建议后台配置。amountOptions和countOptions必须都是正整数数组。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,已关闭弹窗的用户被强弹。 - 发送者本人可以领取自己发送的红包。
- 同一用户重复领取同一红包返回同一领取结果。
- 并发领取不会超过
totalAmount和totalCount。 - 红包创建 24 小时后,未领取金额退回发送者。
- 领取成功、抢完、过期后房间侧边栏未领取数量实时减少。
接口验证:
- 所有成功接口同时返回
body和data。 - 所有失败接口
status=false,body/data=null,code与errorCodeName一致。 requestId重复发送只生成一个红包。packetNo + userId重复领取只生成一条领取记录。