hyapp-server/docs/语音房基础闭环实现.md
2026-05-12 20:07:49 +08:00

13 KiB
Raw Blame History

Voice Room Basic Loop Implementation

本文档定义语音房 v1 基础闭环的实现顺序和验收边界。目标不是做完整语音房,而是先让“登录用户创建房间、进入房间、登录腾讯云 IM、加入房间群、接收房间系统消息、离房、重连恢复”成为可测闭环。

Room Cell 的完整状态机见:

docs/房间Cell状态机.md

Scope

本阶段必须打通:

Capability Owner Required Result
用户身份 user-service gateway 能从 access token 得到稳定 user_id
HTTP 命令入口 gateway-service 所有房间命令走 /api/v1 envelope
房间业务态 room-service Room Cell 持有 presence、麦位、禁言、踢人、快照和恢复
腾讯云 IM 登录票据 gateway-service /api/v1/im/usersig 返回 sdk_app_id/user_id/user_sig/expire_at_ms
腾讯云 IM 群组 room-service RoomCreated outbox 由 worker 异步确保 room_id 对应群组存在
房间系统消息 room-service 房间事件只写 outbox腾讯云 IM 由 outbox worker 异步投递
实时权限守卫 gateway-service + room-service 腾讯云 IM 入群前和群内发言前必须回查房间业务态
腾讯云 RTC 进房票据 gateway-service JoinRoom 成功后在响应中内嵌 RTC token/api/v1/rtc/token 只做补偿和刷新

本阶段不做:

Excluded Reason
自研 WebSocket 实时连接和消息通道由腾讯云 IM 承担
全量 IM 历史库 群历史优先使用腾讯云 IM 能力
RTC 录制/审核 只先接入进房听音频所需的 token 和回调,不处理录制与审核
复杂单聊会话 先保证房间基础闭环
热门房多活 当前仍是单房间单活 + Redis lease 接管

Required Config

gateway-service 负责客户端 UserSig 和腾讯云 IM 回调入口:

tencent_im:
  sdk_app_id: 20036101
  secret_key: "TENCENT_IM_SECRET_KEY"
  user_sig_ttl: "24h"
  callback_url: "https://api.example.com/api/v1/tencent-im/callback"
  callback_auth_token: "TENCENT_IM_CALLBACK_AUTH_TOKEN"

room-service 负责服务端 REST API 建群和系统消息:

tencent_im:
  enabled: true
  sdk_app_id: 20036101
  secret_key: "TENCENT_IM_SECRET_KEY"
  admin_identifier: "900100100"
  admin_user_sig_ttl: "24h"
  endpoint: "adminapisgp.im.qcloud.com"
  group_type: "ChatRoom"
  request_timeout: "5s"

配置规则:

  • sdk_app_idsecret_key 必须在 gateway 和 room-service 中保持一致。
  • endpoint 按腾讯云 IM 应用区域填写;新加坡使用 adminapisgp.im.qcloud.com
  • secret_keycallback_auth_token 在线上必须走私有配置源;本地调试配置只用于本地联调。
  • 本地 Docker 联调时必须让 gateway 和 room-service 使用同一套腾讯云 IM 配置,否则 UserSig、建群和回调守卫会出现跨应用不一致。

Target Flow

sequenceDiagram
  participant C as Client
  participant G as gateway-service
  participant U as user-service
  participant R as room-service
  participant T as Tencent Cloud IM
  participant RTC as Tencent Cloud RTC

  C->>G: Login / RefreshToken
  G->>U: gRPC auth
  U-->>G: access token with user_id
  G-->>C: token envelope

  C->>G: POST /api/v1/rooms/create(room_name, room_avatar?, room_description?)
  G->>R: CreateRoom(actor_user_id, room profile)
  R->>R: command log + snapshot + outbox
  R-->>G: RoomSnapshot
  G-->>C: room envelope
  R-->>T: outbox worker create_group(room_id)

  C->>G: GET /api/v1/im/usersig
  G-->>C: SDKAppID + user_id + UserSig
  C->>T: SDK login(user_id, user_sig)

  C->>G: POST /api/v1/rooms/join
  G->>R: JoinRoom(actor_user_id)
  R->>R: update Room Cell presence
  R-->>G: RoomSnapshot
  G-->>C: initial room envelope + im_group_id + RTC token
  R-->>T: outbox worker send_group_msg(room_user_joined)

  C->>T: joinGroup(room_id)
  T->>G: CallbackBeforeApplyJoinGroup
  G->>R: VerifyRoomPresence(room_id, user_id)
  R-->>G: present=true
  G-->>T: allow

  C->>RTC: enterRoom(strRoomId from JoinRoom rtc.token)

  C->>T: send group message
  T->>G: CallbackBeforeSendMsg
  G->>R: CheckSpeakPermission(room_id, user_id)
  R-->>G: allowed=true
  G-->>T: allow

Gateway Implementation

1. UserSig

已存在入口:

GET /api/v1/im/usersig
Authorization: Bearer <access_token>

实现约束:

  • user_id 必须来自 access token不接受客户端传入。
  • 腾讯云 IM identifier 使用内部长 user_id 的十进制字符串。
  • secret_key 缺失时必须 fail-closed返回统一 envelope 错误,不能返回假票据。
  • request_id 只做链路追踪,不作为 UserSig 幂等键。

2. Callback Endpoint

新增入口:

POST /api/v1/tencent-im/callback

该入口不使用客户端 JWT。它只接受腾讯云 IM 服务端回调,并按 CallbackCommand 分发。

必须支持的回调:

CallbackCommand Purpose Internal RPC
Group.CallbackBeforeApplyJoinGroup 入群前确认用户业务上在房间内 VerifyRoomPresence
Group.CallbackBeforeSendMsg 群内发言前确认未被禁言、未被踢、房间允许聊天 CheckSpeakPermission

回调处理规则:

  • 必须校验回调来源。启用腾讯云 IM 回调鉴权后,使用 callback_auth_token 或控制台约定的鉴权参数校验。
  • CallbackCommand 不在白名单内时返回允许,但要记录日志,避免未知回调阻断腾讯云 IM。
  • GroupId 映射为 room_id
  • Operator_AccountFrom_Account 映射为内部长 user_id,只能接受十进制字符串。
  • 解析失败时拒绝该次入群或发言,避免绕过业务守卫。

建议结构:

services/gateway-service/internal/transport/http/
  tencent_im_callback.go

services/gateway-service/internal/tencentim/
  callback.go
  verifier.go
  command.go

gateway 不保存房间状态,只做回调鉴权、字段解析、调用 room guard RPC 和返回腾讯云 IM 需要的响应。

Room-Service Implementation

1. CreateRoom

CreateRoom 不等待腾讯云 IM 建群。它只提交房间权威状态和 RoomCreated outbox建群由 outbox worker 异步完成。 同一个 actor_user_id 只能作为 owner 创建一个房间;加入、主持或管理别人的房间不受该 owner 限制影响。

主链路顺序:

validate request
acquire Redis lease
check room meta
check owner has no room
initialize RoomState
write room meta + command log + snapshot + outbox
install Room Cell
return snapshot

如果后续 EnsureRoomGroup 失败:

  • CreateRoom 已经成功,不回滚房间状态。
  • outbox worker 按指数退避重试,达到上限进入 failed
  • 客户端 IM SDK 加群可能短暂遇到 group-not-ready需要按产品可接受窗口重试。
  • 失败原因不应该泄露腾讯云密钥、UserSig 或完整公网响应。

2. JoinRoom

JoinRoom 只修改 room-service 业务 presence不创建腾讯云 IM 连接。

幂等规则:

  • 用户已在房间内时刷新 last_seen_at_ms
  • 不重复增加在线人数。
  • 不重复广播进房系统消息,除非产品明确要求“重新进房提示”。

当前实现如果重复 JoinRoom 直接返回快照,需要补齐 last_seen_at_ms 刷新,否则断线重连无法把 presence 延长。

3. LeaveRoom

LeaveRoom 只移除业务 presence。

要求:

  • 用户不在房间内时幂等返回,不报错。
  • 用户离房后 VerifyRoomPresence 必须返回 present=false
  • 如果用户在麦上,离房必须同步释放麦位。
  • 离房事件写 outbox腾讯云 IM 系统消息由 worker 异步投递。

4. Guard RPC

VerifyRoomPresence 是入群前守卫。

返回原因:

Reason Meaning
room_not_found 房间不存在或不可恢复
not_in_room 用户没有业务 presence
user_banned 用户被踢或封禁

CheckSpeakPermission 是发言前守卫。

返回原因:

Reason Meaning
room_not_found 房间不存在或不可恢复
not_in_room 用户没有业务 presence
user_banned 用户被踢或封禁
user_muted 用户被禁言
chat_disabled 房间关闭公屏

守卫 RPC 不写状态,不发 outbox只读取当前 Room Cell 或恢复后的 snapshot 状态。

Tencent IM Message Contract

room-service 发给腾讯云 IM 的房间系统消息使用 TIMCustomElem

客户端只依赖以下稳定字段:

{
  "event_id": "evt_xxx",
  "room_id": "room_1001",
  "event_type": "room_user_joined",
  "actor_user_id": 10001,
  "target_user_id": 10001,
  "seat_no": 1,
  "gift_value": 20,
  "room_heat": 100,
  "room_version": 7,
  "attributes": {
    "reason": "stale_timeout"
  }
}

字段规则:

  • event_id 是客户端去重键。
  • room_version 是房间状态排序参考,不替代腾讯云 IM 消息序列。
  • event_type 使用 snake_case不能直接暴露 protobuf 事件名。
  • 系统事件字段按当前 OpenAPI/proto 契约输出;新增字段必须同步 gateway、room-service 和客户端测试。

首版系统事件:

Event Type Source
room_user_joined RoomUserJoined
room_user_left RoomUserLeft
room_mic_changed RoomMicChanged
room_user_muted RoomUserMuted
room_user_kicked RoomUserKicked
room_gift_sent RoomGiftSent

Recovery Rules

节点重启或接管时:

  1. 新节点获得 Redis lease。
  2. 从 MySQL 读取最新 snapshot。
  3. 回放 snapshot version 之后的 command log。
  4. 安装 Room Cell。
  5. 客户端通过 JoinRoom 幂等刷新 presence。
  6. 客户端重新获取 UserSig 或复用未过期 UserSig 登录腾讯云 IM。
  7. 客户端重新加入房间群,并用最新 RoomSnapshot 覆盖本地状态。

恢复不做:

  • 不恢复本地 socket。
  • 不重新扣费。
  • 不重复调用腾讯云 IM 发送历史系统消息。
  • 不把 Redis 当恢复来源。

Implementation Order

按这个顺序实现,避免先做复杂能力导致闭环不可测:

  1. 锁定配置:确认 gateway 和 room-service 的 sdk_app_idsecret_key、新加坡 endpoint、管理员账号一致。
  2. 补 gateway 回调 handler:实现 /api/v1/tencent-im/callback,支持入群前和发言前回调。
  3. 补 room guard clientgateway 内部 client 增加 RoomGuardService 调用。
  4. 补 JoinRoom 重连刷新:重复 JoinRoom 刷新 last_seen_at_ms,但不重复发进房系统消息。
  5. 补 LeaveRoom 麦位释放:离房时如果在麦上,释放麦位并产生一致的房间事件。
  6. 补 presence stale worker:按 last_seen_at_ms 清理超时 presenceRoomUserLeft
  7. 补集成测试:覆盖 UserSig、CreateRoom outbox 建群、JoinRoom、回调守卫、LeaveRoom、重连恢复。

Acceptance Tests

必须有自动化测试覆盖:

Test Expected Result
UserSig 签发 已登录用户拿到 sdk_app_id/user_id/user_sig,匿名用户失败
UserSig 配置缺失 返回统一错误,不返回假票据
CreateRoom 后建群失败 房间状态已提交outbox 重试;超限后进入 failed
CreateRoom 成功 MySQL meta/snapshot/command/outbox 都存在
JoinRoom 成功 presence 增加,返回 RoomSnapshot
重复 JoinRoom 刷新 last_seen_at_ms,不重复创建 presence
入群前回调允许 已进房且未被 ban 的用户返回 allow
入群前回调拒绝 未进房或被踢用户返回 deny
发言前回调允许 已进房、未禁言、房间聊天开启
发言前回调拒绝 被禁言、被踢、未进房或房间聊天关闭
LeaveRoom 后发言 CheckSpeakPermission 返回 not_in_room
room-service 接管恢复 snapshot + command log 恢复后守卫 RPC 继续正确
outbox 投递失败 房间状态已提交outbox 进入 retryable 或最终 failed

Operational Checks

上线前必须人工确认:

  • 腾讯云 IM 控制台应用区域是新加坡REST endpoint 使用 adminapisgp.im.qcloud.com
  • 腾讯云 IM 控制台已配置回调 URL且 URL 指向 gateway 公网入口。
  • 回调鉴权 token 和 gateway 配置一致。
  • 已启用 Group.CallbackBeforeApplyJoinGroupGroup.CallbackBeforeSendMsg
  • gateway 和 room-service 的 SDKAppIDSecretKey 一致。
  • 线上配置没有把真实 secret 写入仓库。
  • room outbox 积压、腾讯云 IM REST 错误率、UserSig 签发失败率都有监控。

Done Definition

基础闭环完成的标准:

User Login
  -> CreateRoom
  -> JoinRoom
  -> Get UserSig
  -> Tencent IM SDK Login
  -> Join Tencent IM Group
  -> Callback VerifyRoomPresence allow
  -> Send Room Chat
  -> Callback CheckSpeakPermission allow
  -> Mic/Gift/Kick/Mute system event delivered
  -> LeaveRoom
  -> Callback guard denies further speak/join
  -> Reconnect via JoinRoom + UserSig + IM group join

只要这条链路稳定,后续 RTC、礼物、活动、房间推荐都可以在不破坏边界的前提下继续迭代。