hyapp-server/docs/语音房基础闭环实现.md

14 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->>T: create_group(room_id)
  T-->>R: group ready
  R->>R: command log + snapshot + outbox
  R-->>G: room DTO with im_group_id
  G-->>C: room envelope with im_group_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 房间群存在。创建房间是低频动作,主链路宁可因为腾讯 IM 建群失败返回错误,也不能让 App 在 JoinRoom 成功后遇到 group-not-ready。 同一个 actor_user_id 只能作为 owner 创建一个房间;加入、主持或管理别人的房间不受该 owner 限制影响。

主链路顺序:

validate request
acquire Redis lease
check room meta
check owner has no room
ensure Tencent IM room group
initialize RoomState
write room meta + command log + snapshot + outbox
install Room Cell
return room DTO with im_group_id

如果同步 EnsureRoomGroup 失败:

  • CreateRoom 返回失败,不写房间 meta、command log、snapshot 或 presence 投影。
  • App 不进入房间页,不调用 JoinRoom 或腾讯 IM SDK joinGroup
  • 如果腾讯 IM 已建群但后续本地持久化失败,后续同 GroupID 建群按幂等成功处理。
  • 失败原因不应该泄露腾讯云密钥、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。给单个用户的私有通知不从这个 bridge 发,统一由 notice-service 读取 owner outbox 后走腾讯云 IM C2C。

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

{
  "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

room_user_kicked 还会由 notice-service 读取同一条 RoomUserKicked 事实,给被踢用户发送 C2C room_notice。room-service 仍负责把目标用户移出腾讯 IM 群notice-service 不处理建群、退群或群成员权限。

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 建群失败 返回失败MySQL 不写 room meta/snapshot/command/outbox
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、礼物、活动、房间推荐都可以在不破坏边界的前提下继续迭代。