25 KiB
Tencent RTC Token And UID Mapping Development Guide
本文档定义 Phase 2 “腾讯 RTC 语音接入”中,gateway-service 如何签发腾讯 RTC 进房凭证,以及系统内部 user_id、room_id 如何映射到腾讯 RTC SDK 参数。目标是让用户在 JoinRoom 成功后可以进入真实语音频道,同时不把 RTC 在线态塞进 room-service。
Scope
本阶段只做:
| Capability | Owner | Required Result |
|---|---|---|
| RTC UserSig 签发 | gateway-service |
已鉴权且已进房用户可以拿到腾讯 RTC 所需 SDKAppID/UserID/UserSig |
| UID 映射 | gateway-service |
内部不可变 user_id 稳定映射为 RTC UserID 字符串 |
| 房间 ID 映射 | gateway-service + room-service |
内部 room_id 稳定映射为 RTC strRoomId |
| 进房权限 | gateway-service + room-service |
只有 room-service presence 中的用户才能拿 RTC token |
| 客户端进频道 | Client | 客户端使用返回参数调用腾讯 RTC SDK 进入语音房 |
本阶段不做:
| Not Included | Reason |
|---|---|
| 自研 RTC 网关 | 音频链路由腾讯 RTC SDK 和腾讯云承载 |
| RTC 在线态持久化 | RTC 连接态属于腾讯 RTC,不进入 Room Cell 核心状态 |
| 多端同时进同一 RTC 房 | 腾讯 RTC 对同一 UserID 同房多端有冲突约束,首版只支持单用户单端进同一 RTC 房 |
| 音频流审核/旁路直播 | 需要独立产品和合规设计 |
私有进房权限 privateMapKey |
首版不启用房间级腾讯侧权限;只适用于普通开放语音房,私密、付费或强权限房必须先补 privateMapKey |
Official Constraints
需要遵守的腾讯 RTC 规则:
| Rule | Design Impact |
|---|---|
| UserSig 是腾讯 RTC/Chat 的用户鉴权凭证,生产环境应由业务服务端实时生成,不能把 SecretKey 放在客户端 | gateway-service 签发,客户端永远拿不到 secret_key |
RTC SDK 进房需要 SDKAppID + UserID + UserSig |
HTTP 响应必须返回这三项 |
UserID 允许字母、数字、下划线、连字符等有限字符集合 |
内部 int64 user_id 转十进制字符串,天然满足限制 |
同一 UserID 不能在同一 TRTC 房间用两个设备同时进房 |
首版不做多端同房;后续要多端时必须新增 rtc_user_id 映射表和端标识 |
TRTC 同时支持数字 roomId 和字符串 strRoomId,二者不能混用 |
本项目内部 room_id 是字符串,统一使用 strRoomId,客户端不要传数字 roomId |
UserSig 只校验 SDKAppID + UserID 是否有权使用 TRTC 服务,不绑定具体房间 |
VerifyRoomPresence 只能控制本服务是否签发 token;没有 privateMapKey 时腾讯 RTC 后端不会阻止同一 UserSig 进入其他 strRoomId |
privateMapKey 才是 TRTC 房间级权限凭证 |
首版不开启应用级房间权限控制;一旦控制台开启,客户端必须传 privateMapKey,否则无法进房 |
参考官方文档:
Ownership
| Domain | Owner | Rule |
|---|---|---|
| HTTP RTC token API | gateway-service |
只做鉴权、参数校验、room guard 查询和 UserSig 签发 |
| 房间业务 presence | room-service |
仍然是用户是否属于房间的事实来源 |
| RTC 音频连接 | 腾讯 RTC | 持有真实音频连接、音频流、频道内在线状态 |
| 用户身份 | user-service |
user_id 发号和用户状态,不进入 RTC 高频链路 |
| 客户端音频操作 | Client | 根据 token 进房、切换麦克风、处理 RTC SDK 回调 |
边界:
gateway-service不能接受客户端自报user_id或rtc_user_id。room-service不签发 RTC token,不保存 RTC socket 或音频流状态。room-service.VerifyRoomPresence是签发 RTC token 前的业务守卫。- 腾讯 RTC
UserID不是展示号,不能使用display_user_id、昵称、手机号或 provider subject。 - 首版
VerifyRoomPresence + UserSig是业务入口守卫,不是腾讯 RTC 房间级强制鉴权。已拿到有效 UserSig 的客户端理论上可以在 TTL 内请求进入其他strRoomId,因此首版只面向普通开放语音房。 - 私密房、付费房、活动专属房、合规要求强隔离的房型不能复用首版 token 方案,必须先新增
privateMapKey签发、客户端传参、验收测试和回滚策略。
UID Mapping
首版映射规则:
internal user_id int64 -> strconv.FormatInt(user_id, 10) -> rtc_user_id / UserID
示例:
| Internal | Tencent RTC |
|---|---|
user_id=918274650129384448 |
UserID="918274650129384448" |
user_id=10001 |
UserID="10001" |
规则:
- 映射必须稳定、可重复、无数据库查询。
rtc_user_id必须等于腾讯 IM identifier,方便客户端和排障把 IM 用户、RTC 用户、房间业务用户对齐。display_user_id只用于展示、搜索、登录名,不能参与 RTC 身份。- access token 中的展示号快照不能作为 RTC 身份。
- 如果未来要支持同一用户多端同时进入同一 RTC 房,不能简单追加设备后缀;必须新增显式映射,例如
rtc_user_sessions(user_id, device_id, rtc_user_id, room_id, expires_at_ms),并重新设计 IM/RTC/房间事件中的身份还原规则。
Room ID Mapping
首版映射规则:
internal room_id string -> RTC strRoomId
RTC numeric roomId -> always 0 or omitted
响应字段建议同时返回业务名和 RTC 名,避免客户端混用:
{
"room_id": "room_1001",
"rtc_room_id": "room_1001",
"rtc_room_id_type": "string",
"str_room_id": "room_1001"
}
规则:
- 客户端进腾讯 RTC 房时只使用
strRoomId。 - 客户端不能把
"123"转成数字123;这在 TRTC 中是两个不同房间。 - 新建房间时的
room_id必须满足 RTC 字符串房间 ID 限制;校验要前移到CreateRoomHTTP/gRPC 入口或 room-service 领域层,不能创建出业务可用但永远拿不到 RTC token 的房间。 /api/v1/rtc/token仍要重复校验room_id,作为内部调用和入口绕过场景的 fail-closed 保护;非法房间 ID 返回INVALID_ARGUMENT。- 为同时满足腾讯 IM 群组和 RTC 房间限制,
room_id格式收敛到:^[A-Za-z0-9_-]{1,48}$。如果产品需要更丰富字符,必须同时确认 IM group id、RTCstrRoomId、OpenAPI 和客户端 SDK 都接受。
External HTTP API
RTC token gateway 入口:
POST /api/v1/rtc/token
Authorization: Bearer <access_token>
Request:
{
"room_id": "room_1001"
}
Response data:
{
"sdk_app_id": 1400000000,
"user_id": "10001",
"rtc_user_id": "10001",
"user_sig": "usersig_xxx",
"expire_at_ms": 1777000000000,
"room_id": "room_1001",
"rtc_room_id": "room_1001",
"rtc_room_id_type": "string",
"str_room_id": "room_1001",
"app_scene": "voice_chat_room",
"role": "audience"
}
字段规则:
user_id保留腾讯 SDK 常用字段名,值是字符串形式内部长user_id。rtc_user_id是同一个值,用于客户端代码显式区分业务user_id和 RTCUserID。user_sig是腾讯 RTC UserSig,不是腾讯 SecretKey。expire_at_ms让客户端在 SDK 报 UserSig 过期前主动刷新。role是当前推荐入房角色:不在麦上为audience,已在麦上或房主/主持需要发流时为anchor。VoiceChatRoom 场景下audience不能发布音频流,客户端只有在MicUp成功后才能切到anchor并开始采集。- 首版 token 响应可以先返回
audience;后续如果 gateway 要按麦位返回anchor/audience,必须从 room-service guard 或只读快照获取麦位事实,不能在 gateway 缓存房间状态。
错误映射:
| Case | HTTP | Code | Rule |
|---|---|---|---|
| 未登录 | 401 | UNAUTHORIZED |
middleware 拦截 |
room_id 为空或不符合 RTC 限制 |
400 | INVALID_ARGUMENT |
gateway 本地校验 |
| 不在房间 | 403 | PERMISSION_DENIED |
VerifyRoomPresence.present=false |
| 房间不存在 | 404 | NOT_FOUND |
guard reason 为 room_not_found |
| 用户被踢/封禁 | 403 | PERMISSION_DENIED |
guard reason 为 user_banned |
| RTC 配置缺失 | 500 | INTERNAL_ERROR |
fail-closed,不返回假 token |
| room-service 不可用 | 502 | UPSTREAM_ERROR |
gateway 不能自判 presence |
显式房间心跳入口:
POST /api/v1/rooms/heartbeat
Authorization: Bearer <access_token>
Request:
{
"room_id": "room_1001",
"command_id": "cmd-heartbeat-1700000000"
}
规则:
- 心跳只刷新已经存在的 room-service presence 的
last_seen_at_ms。 - 用户不在房间时返回
NOT_FOUND,不能通过心跳重新进入房间。 - 客户端应按小于
room_presence_stale_after的间隔发送心跳;HTTP 重试同一次心跳必须复用同一个command_id。 - 主动离房仍然必须调用
/api/v1/rooms/leave;心跳停止只代表后台 stale worker 最终清理。
腾讯 RTC 服务端事件回调入口:
POST /api/v1/tencent-rtc/callback?app_code=default
Header: SdkAppId: <sdk_app_id>
Header: Sign: base64(hmacsha256(callback_sign_key, raw_body))
消费事件:
| Tencent Event | Internal Event | Room Cell Effect |
|---|---|---|
EventGroupId=2, EventType=203 |
audio_started |
当前麦位为 pending_publish 时确认成 publishing |
EventGroupId=2, EventType=204 |
audio_stopped |
释放当前用户麦位 |
EventGroupId=1, EventType=104 |
room_exited |
释放当前用户麦位,不移除业务 presence |
回调边界:
- gateway 只负责验签、校验
SdkAppId、解析 Tencent 字段和派生稳定command_id。 - 房间状态变更只调用 room-service
ApplyRTCEvent,仍进入 Room Cell、command log 和 outbox。 - 未知事件在验签成功后返回
{"code":0}并忽略,避免腾讯新增事件导致无意义重试。 - room-service 暂时不可用时返回非 200,让腾讯 RTC 按平台策略重试。
room_exited只代表 RTC 媒体频道退出,用户仍可能停留在房间页重连;业务 presence 只能由LeaveRoom、踢人、关房或 stale worker 清理。- 如果一个旧
room_exited回调晚于新的业务心跳到达,room-service 会保留较新的业务 presence 和新麦位会话,避免旧回调清掉重连用户。
Internal Flow
sequenceDiagram
participant C as Client
participant G as gateway-service
participant R as room-service
participant T as Tencent RTC
C->>G: POST /api/v1/rooms/join
G->>R: JoinRoom(actor_user_id, room_id)
R-->>G: RoomSnapshot
G-->>C: JoinRoom envelope
C->>G: POST /api/v1/rtc/token(room_id)
G->>G: verify JWT, derive user_id
G->>R: VerifyRoomPresence(room_id, user_id)
R-->>G: present=true, room_version
G->>G: map user_id -> rtc_user_id
G->>G: map room_id -> strRoomId
G->>G: generate UserSig
G-->>C: SDKAppID/UserID/UserSig/strRoomId
C->>T: enterRoom(SDKAppID, UserID, UserSig, strRoomId)
T-->>C: RTC room entered
loop heartbeat before stale timeout
C->>G: POST /api/v1/rooms/heartbeat(room_id)
G->>R: RoomHeartbeat(actor_user_id, room_id)
R-->>G: refreshed last_seen_at_ms
end
T->>G: RTC callback 203/204/104 with Sign
G->>G: verify HMAC-SHA256 over raw body
G->>R: ApplyRTCEvent(audio_started/audio_stopped/room_exited)
R-->>G: RoomSnapshot
G-->>T: {"code":0}
签发顺序:
validate HTTP JSON
extract user_id from access token
validate room_id format
call room-service.VerifyRoomPresence(room_id, user_id)
map user_id to rtc_user_id string
generate RTC UserSig from tencent_rtc config
return token response
Gateway Implementation
Config
新增配置块:
tencent_rtc:
enabled: true
sdk_app_id: 1400000000
secret_key: "TENCENT_RTC_SECRET_KEY"
user_sig_ttl: "2h"
room_id_type: "string"
app_scene: "voice_chat_room"
callback_url: "https://api.example.com/api/v1/tencent-rtc/callback?app_code=default"
callback_sign_key: "TENCENT_RTC_CALLBACK_SIGN_KEY"
配置规则:
secret_key只能存在服务端配置或密钥系统,不能下发客户端。callback_sign_key只用于腾讯 RTC 回调验签,不要复用 UserSigsecret_key。tencent_rtc与tencent_im分开配置,避免误把 IM 应用和 RTC 应用混用。- 如果腾讯控制台实际使用同一个应用承载 Chat/RTC,可以让两块配置填同一组
sdk_app_id/secret_key,但仍必须显式配置。 user_sig_ttl不宜过长;建议 2h 到 24h,具体按客户端刷新能力和泄露窗口决定。room_id_type首版固定string,其他值启动失败。
Package Boundary
当前 pkg/tencentim.GenerateUserSig 已实现 Tencent UserSig v2 算法。RTC 接入时不要让 gateway 的 RTC handler 直接依赖 pkg/tencentim 语义。
推荐做法:
pkg/tencentsig/ 通用 UserSig 算法
pkg/tencentim/ IM REST 和 IM UserSig wrapper
pkg/tencentrtc/ RTC token response 和 UserSig wrapper
迁移规则:
- 先把通用签名算法抽到
pkg/tencentsig。 - 保持现有
/api/v1/im/usersig行为不变。 - 新增
pkg/tencentrtc.GenerateUserSig或GenerateToken包装通用算法。 - 单测用固定时间验证 IM 和 RTC 生成结果稳定,避免重构破坏现有 IM 登录。
Handler
新增:
services/gateway-service/internal/transport/http/rtc_handler.go
路由:
mux.Handle(apiV1Prefix+"/rtc/token", apiHandler(jwtVerifier, h.issueTencentRTCToken))
handler 不写房间状态,只做:
- decode
room_id - 从 context 取 authenticated
user_id - 校验
room_id - 调
roomGuardClient.VerifyRoomPresence - 根据 guard response 映射 HTTP envelope
- 签发 UserSig
- 返回 token data
handler 装配规则:
services/gateway-service/internal/config.Config.TencentRTC必须从app.New传入 HTTP handler,不能只停留在 config 包。NewHandlerWithConfig或后续构造器必须同时接收TencentIMConfig和TencentRTCConfig,并保持 IM usersig 行为不变。- handler 签发前校验
tencent_rtc.enabled=true、sdk_app_id>0、secret_key非空、user_sig_ttl>0、room_id_type=string、app_scene=voice_chat_room;任一不满足都 fail-closed。 - 配置错误返回
INTERNAL_ERROR,依赖 room-service 失败返回UPSTREAM_ERROR,两者不能混用。
Room Guard Client
复用现有 RoomGuardClient.VerifyRoomPresence。
不新增 room-service RPC,除非后续需要更细的 RTC 进房策略,例如:
- 只有上麦用户能以 anchor 进房。
- 房间关闭 RTC。
- 房间分区域或分线路。
- 多端设备踢旧逻辑。
Room-Service Changes
room-service 不保存 RTC socket 或在线态,但必须保存“业务占麦后是否完成 RTC 发流确认”的麦位状态,避免把 MicUp 当成音频已经发布成功。
当前麦位发流状态:
| State | Meaning | Transition |
|---|---|---|
empty / idle |
空麦或没有发流会话 | MicUp 后进入 pending_publish |
pending_publish |
业务麦位占用成功,等待客户端或 RTC webhook 确认音频已发布 | ConfirmMicPublishing 后进入 publishing;超时自动 MicDown(reason=publish_timeout) |
publishing |
当前 mic_session_id 已确认发布音频 |
MicDown、LeaveRoom、KickUser 后清空 |
需要确认的稳定行为:
VerifyRoomPresence能区分room_not_found/not_in_room/user_banned。JoinRoom重连刷新last_seen_at_ms。LeaveRoom后VerifyRoomPresence返回 false。- Kick 后
VerifyRoomPresence返回user_banned。 MicUp返回mic_session_id和publish_deadline_ms,并在SeatState写入pending_publish。ConfirmMicPublishing(mic_session_id, room_version, event_time_ms)只确认当前麦位会话。- pending 超过
publish_deadline_ms未确认时,room-service 自动下麦并在事件 reason 中写publish_timeout。
可选增强:
RoomSnapshot.room_ext后续可加入rtc_enabled=true,但首版不需要。- 如果要根据麦位返回
role=anchor/audience,gateway 可以从 room-service 增加只读查询或扩展 guard response;不要让 gateway 自己缓存麦位状态。 - 后续接 RTC webhook 时,webhook payload 必须带
mic_session_id、room_version、event_time_ms;room-service 只接受当前 session 且事件时间不旧于已接受事件的数据,旧 audience/leave 事件不能清掉新上麦状态。
Client Flow
标准进房:
1. Login / RefreshToken
2. POST /api/v1/rooms/join
3. POST /api/v1/im/usersig
4. IM SDK login + join group
5. POST /api/v1/rtc/token
6. RTC SDK enterRoom with SDKAppID/UserID/UserSig/strRoomId
7. Use JoinRoom RoomSnapshot as business UI state
上麦发布音频:
1. POST /api/v1/rooms/mic/up
2. Client receives mic_session_id, publish_deadline_ms, and RoomSnapshot with publish_state=pending_publish
3. RTC SDK switchRole(anchor)
4. RTC SDK startLocalAudio
5. Client receives local publish success callback
6. POST /api/v1/rooms/mic/publishing/confirm with room_id, mic_session_id, room_version, event_time_ms, source=client
7. RoomSnapshot seat publish_state becomes publishing
8. If step 6 is not completed before publish_deadline_ms, room-service automatically MicDown(reason=publish_timeout)
重连:
1. Refresh access token if needed
2. POST /api/v1/rooms/join to refresh business presence
3. If RTC UserSig expired or SDK requires re-enter, POST /api/v1/rtc/token
4. RTC SDK re-enter same strRoomId
5. Client reconciles UI with latest RoomSnapshot
离房:
1. Client stops local audio if publishing
2. Client exits RTC room
3. Client exits IM group if product requires
4. POST /api/v1/rooms/leave
5. room-service presence removed
6. Later /api/v1/rtc/token for same room is denied
服务端原则:
- RTC 断线不立即等于业务离房。
- 主动
LeaveRoom才是明确离房。 - 超过 presence stale timeout 后,业务 presence 被清理,新的 RTC token 会被拒绝。
- 客户端收到 Kick 系统消息后必须
stopLocalAudio、退出 RTC 房并退出 IM 群;服务端后续 token 签发也会拒绝。 - 下麦成功后客户端必须
stopLocalAudio,并在 VoiceChatRoom 场景调用switchRole(audience)或按 SDK 推荐方式停止发布。 - 客户端只能把
publishing当成“服务端已接受音频发布确认”;pending_publish只能展示为“正在连接麦克风/等待发流确认”。 - 客户端重复确认同一 SDK 成功回调时必须复用同一个
command_id;如果没有复用,room-service 仍会用mic_session_id + room_version + event_time_ms防止旧事件覆盖新会话。
Security Rules
- UserSig 必须服务端生成。
- SecretKey 不进客户端、不进日志、不进错误响应。
- token endpoint 必须要求 access token。
- token endpoint 必须先查
VerifyRoomPresence,不能只要登录就签 RTC token。 room_id、user_id、rtc_user_id、request_id可以记录日志;user_sig和secret_key不能记录。- UserSig 过期只影响 RTC SDK 鉴权,不应改变房间业务 presence。
- 如果
tencent_rtc.enabled=false或配置不完整,接口 fail-closed。 - UserSig 不是房间级权限凭证。首版普通房接受“签发入口校验 + 短 TTL + 客户端退出协议”的边界;需要腾讯侧强制房间准入时必须启用
privateMapKey。 - 不启用
privateMapKey时,不要把/api/v1/rtc/token文案、产品策略或风控策略描述成“腾讯 RTC 后端已强制禁止跨房间进入”。
Observability
日志字段:
| Field | Meaning |
|---|---|
request_id |
gateway HTTP 追踪 ID |
user_id |
内部长 ID |
rtc_user_id |
腾讯 RTC UserID |
room_id |
内部房间 ID |
str_room_id |
RTC 字符串房间 ID |
room_version |
guard 返回的房间版本 |
mic_session_id |
单次上麦发流会话 ID |
publish_state |
pending_publish / publishing |
publish_deadline_ms |
发流确认截止时间 |
publish_event_time_ms |
客户端 SDK 或 RTC webhook 事件时间 |
result |
issued / denied / config_error / upstream_error |
reason |
guard 或配置失败原因 |
指标建议:
gateway_rtc_token_issued_total
gateway_rtc_token_denied_total
gateway_rtc_token_config_error_total
gateway_rtc_token_upstream_error_total
gateway_rtc_token_latency_ms
room_mic_publish_confirmed_total
room_mic_publish_timeout_total
room_mic_publish_stale_event_ignored_total
腾讯 RTC 外部进房成功率由客户端 SDK 上报或日志采集,不放进 gateway readiness。
Implementation Order
- 新增
docs/腾讯RTC票据UID映射开发.md并确认产品边界。 - 抽出通用 Tencent UserSig 算法到
pkg/tencentsig,保持 IM usersig 测试通过。 - 新增
pkg/tencentrtc,定义 RTC token config/result 和GenerateToken。 - 扩展 gateway config:
tencent_rtc。 - 三份 gateway config 增加
tencent_rtc示例。 - 把
config.TencentRTC从app.New装配到 HTTP handler,并补 handler 构造器单测。 - 新增 gateway
/api/v1/rtc/tokenhandler 和 route。 - handler 调
RoomGuardClient.VerifyRoomPresence。 - 在
CreateRoom入口或 room-service 领域层补统一room_id格式校验,并保持/api/v1/rtc/token二次校验。 - 扩展 room proto:
SeatState.publish_state/mic_session_id/publish_deadline_ms、MicUpResponse、ConfirmMicPublishing。 - room-service 在
MicUp写入pending_publish,确认后写publishing,超时自动MicDown(reason=publish_timeout)。 - gateway 增加
/api/v1/rooms/mic/publishing/confirm并透传 request_id 到 room-service。 - 更新
docs/openapi/gateway.swagger.yaml和docs/openapi/room.swagger.yaml。 - 增加 gateway transport、room-service command、stale event 和 timeout 单测。
- 跑 Phase 2 最小手工联调:JoinRoom 后签 RTC token,客户端用
strRoomId进入语音房,再完成 MicUp -> ConfirmMicPublishing -> MicDown。
Acceptance Tests
必须覆盖:
| Test | Expected Result |
|---|---|
| 已登录且已进房用户签发 RTC token | 返回 sdk_app_id/user_id/rtc_user_id/user_sig/str_room_id/expire_at_ms |
| 未登录用户请求 | HTTP 401 envelope |
| 未 JoinRoom 用户请求 | HTTP 403 envelope,不返回 UserSig |
| 房间不存在 | HTTP 404 envelope |
| 被踢用户请求 | HTTP 403 envelope |
| RTC 配置缺失 | HTTP 500 envelope,不返回假 UserSig |
room_id 为空或格式非法 |
HTTP 400 envelope |
CreateRoom 使用不符合 RTC/IM 限制的 room_id |
HTTP 400 envelope,房间不创建 |
| UserID 映射 | user_id=10001 时 rtc_user_id == "10001" |
| request_id 透传 | response envelope 和 guard RPC 使用同一个 request_id |
tencent_rtc.enabled=false |
HTTP 500 envelope,不返回 UserSig |
room_id_type 非 string |
handler fail-closed,不返回 UserSig |
| IM UserSig 不回归 | 现有 /api/v1/im/usersig 测试仍通过 |
| MicUp 后未确认发布 | 麦位进入 pending_publish,响应返回 mic_session_id/publish_deadline_ms |
| ConfirmMicPublishing 当前 session | 麦位进入 publishing |
| ConfirmMicPublishing 旧 session/旧版本/旧 event_time | 不覆盖当前麦位状态 |
| publish deadline 超时 | 自动 MicDown(reason=publish_timeout) 并释放麦位 |
建议补充手工验证:
Login
-> CreateRoom
-> JoinRoom
-> Get IM UserSig
-> IM SDK login and join group
-> Get RTC token
-> RTC SDK enterRoom(strRoomId=room_id, userId=rtc_user_id)
-> MicUp
-> SeatState.publish_state=pending_publish, client receives mic_session_id
-> RTC SDK switchRole(anchor)
-> Client starts publishing audio
-> ConfirmMicPublishing(mic_session_id, room_version, event_time_ms)
-> SeatState.publish_state=publishing
-> Retry stale ConfirmMicPublishing with old mic_session_id/event_time_ms and verify no state rollback
-> MicDown
-> Client stops publishing audio and switches to audience
-> MicUp again but do not confirm
-> publish_deadline_ms expires and room-service auto MicDown(reason=publish_timeout)
-> LeaveRoom
-> RTC token denied for same room
Verification Commands
涉及代码实现后至少运行:
go test ./pkg/tencentim
go test ./pkg/tencentsig
go test ./pkg/tencentrtc
go test ./services/gateway-service/internal/config
go test ./services/gateway-service/internal/transport/http
go test ./...
涉及配置后运行:
docker compose config
涉及 protobuf 时运行:
make proto
go test ./...
Done Definition
完成标准:
Authenticated user
-> JoinRoom succeeds
-> /api/v1/rtc/token checks room-service presence
-> gateway maps internal user_id to RTC UserID
-> gateway maps room_id to RTC strRoomId
-> gateway signs RTC UserSig on server
-> client enters Tencent RTC room
-> MicUp creates pending_publish mic_session
-> client confirms publishing with mic_session_id, room_version, event_time_ms
-> stale RTC/client events cannot clear a newer mic_session
-> unconfirmed pending_publish session auto MicDowns after deadline
-> LeaveRoom/Kick/stale cleanup makes later RTC token request fail
只要这条链路稳定,后续麦位上行、角色切换、音频审核和多端 RTC 策略都可以在不破坏 Room Cell 状态边界的前提下继续迭代。