接好 RTC SDK 只是语聊房的开始。谁能进房间、麦位上是谁、礼物扣了多少钱,这些业务逻辑声网 SDK 统统不处理,全部由服务端承担。没有通用的语聊房服务端 SDK,但设计模式是固定的。
这篇文章覆盖语聊房搭建开发的四个核心模块:Token 签发服务(鉴权入口)、房间和麦位状态管理(实时状态的权威来源)、用户权限分层(房主/管理员/普通用户)、礼物和内容安全的后端设计。技术栈无关,侧重设计原则和接口边界。

一. 服务端的职责边界
声网 SDK 管什么:音频流的传输、编解码、弱网对抗、全球路由。
声网 SDK 不管什么:谁能进哪个房间、谁在麦位上、礼物怎么算钱、用户是否被封禁。这些业务逻辑全部在服务端。
客户端不应该是任何业务状态的权威来源。麦位是否有人、用户是否被禁言、礼物是否已支付——这些状态如果只在客户端内存里,重新进房时就会丢失,多端并发时就会冲突。服务端是唯一权威,客户端只做展示。
服务端和声网 SDK 的交互只有两个点:签发 Token(初始鉴权)和 renewToken(续期)。其余的连接管理、音频路由、断线重连全部由 SDK 内部处理,不需要服务端介入。
二. Token 签发服务
Token 是用户加入 RTC 频道的身份凭证,由服务端用 App Certificate 签名生成。App Certificate 只存在服务端,绝不能出现在客户端代码或配置文件里。
Token 签发服务需要提供一个接口,客户端进入频道前调用:
// 接口设计示例(伪代码,技术栈无关)
POST /api/token
Request: { channelName, uid, role }
Response: { token, expireAt }
几个设计细节:
Token 有效期:建议 2-6 小时。语聊房用户单次在线时间通常在 1-3 小时,有效期过短会频繁续期,过长则密钥暴露风险更高。配合 SDK 的 onTokenPrivilegeWillExpire 回调(过期前 30 秒触发),在用户无感知的情况下完成续期。
Role 统一用 PUBLISHER:语聊房里通常对所有用户签发 PUBLISHER 权限的 Token,实际的发布行为由客户端的角色切换(setClientRole)来控制。好处是用户上麦时不需要重新获取 Token。如果在 Token 级别区分权限,上麦时需要重新签发,增加服务端压力和延迟。
Token 和业务鉴权分离:Token 签发接口本身应该有业务层面的鉴权——用户是否已登录、是否有权进入该房间、是否被封禁。Token 只是声网的鉴权,不代表你的业务允许这个用户进入。
三. 房间状态管理
服务端需要维护每个房间的实时状态,供客户端进入时拉取初始状态,以及在状态变化时推送更新。
房间状态包含两类:
- 实时状态(高频读写):在线人数、麦位占用情况、禁言状态。这类状态变化频繁,读写延迟敏感,适合用内存型存储(如 Redis)维护。
- 持久化数据(低频读写):房间创建时间、历史礼物记录、进退房日志。这类数据需要持久化和事后查询,适合存关系型数据库。
客户端进入房间的正确序列(与 A-2-0 中详述的一致):先订阅信令频道,再向服务端请求全量状态快照。顺序不能反,否则订阅和快照之间的间隙里发生的状态变化会被遗漏。
房间销毁的触发条件:房主主动解散,或者房间内所有人都离开后超时(通常 30-60 秒)。超时销毁需要服务端有定时检测机制,不能只依赖客户端主动通知。
四. 麦位状态的服务端权威
麦位是语聊房里并发冲突最集中的地方。两个用户同时申请同一个空麦位,如果服务端不做原子操作,两人都可能收到”上麦成功”的响应。
正确的上麦流程:
客户端 → 服务端:申请上麦(roomId + seatIndex + uid)
服务端:原子操作写麦位状态(只有空位才能写入,并发安全)
服务端 → 客户端:通知切换 broadcaster 角色
客户端:调用 setClientRole(BROADCASTER)
服务端 → 信令频道:广播麦位变更给房间内所有人
客户端不能自己决定”我上麦成功了”然后直接切换角色,必须等服务端的通知。否则并发上麦时,两个客户端都切换了角色,但服务端只记录了一个人,状态就乱了。
意外断线时麦位的释放也需要服务端处理:SDK 触发 onUserOffline(原因为 DROPPED)时,服务端收到通知后释放该用户占用的麦位,再广播麦位变更。如果只依赖用户主动下麦,意外断线会导致麦位永久占用。
五. 用户权限分层
语聊房通常有三个权限层级,服务端需要在每个操作里校验发起者的权限:
| 角色 | 可执行的操作 |
|---|---|
| 房主 | 踢人、批准/拒绝上麦申请、强制下麦、锁定/解锁麦位、禁言、转让房主、解散房间 |
| 管理员 | 踢人、强制下麦、禁言(部分产品有此层级) |
| 普通用户 | 申请上麦、自行下麦、发弹幕、送礼物 |
权限校验必须在服务端做。客户端隐藏了”踢人”按钮不等于用户无法发出踢人请求——请求可以被绕过。每个操作到达服务端时,服务端都要验证发起者是否有权限执行该操作。
房主转让是一个常被忽略的边界情况:房主离开时,如果没有配置”转让给谁”,需要一个降级策略(比如转给最早进房的管理员,或者直接解散房间)。不处理这个情况,房间会变成一个没有房主的”孤儿房间”,后续的上麦审批、踢人等操作都会失效。
六. 内容安全接入
语聊房的内容安全分两类:
- 音频实时检测:对上麦用户的音频流做实时违规识别(涉政、色情、辱骂等)。声网提供云端内容审核能力,可以通过服务端 API 开启对指定频道的音频检测,检测结果通过回调通知业务服务端,再由服务端决定是否触发禁言或踢人。
- 文字消息过滤:弹幕和聊天消息在服务端落地时过滤。过滤在服务端做,不在客户端,客户端过滤可以被绕过。
内容安全的违规处理通常是分级的:轻度(警告提示)→ 中度(临时禁言)→ 严重(踢出房间 + 封号)。服务端需要有违规记录日志,便于申诉和审计。
七. 礼物系统后端设计
礼物系统涉及虚拟货币的流转,必须完全在服务端实现,任何金额计算都不能放在客户端。
一次完整的送礼流程在服务端的处理:
1. 收到送礼请求(roomId、senderId、receiverId、giftId、quantity)
2. 校验幂等性(防重复提交,用请求唯一 ID 去重)
3. 校验 sender 账户余额是否充足
4. 原子扣减 sender 余额,增加 receiver 收益
5. 写礼物记录日志
6. 通过信令通道广播礼物事件给房间内所有用户
7. 返回成功响应
步骤 2(幂等性校验)是防刷的关键——客户端网络抖动时可能重试,服务端必须保证同一笔礼物只处理一次。步骤 4(余额操作)必须是原子的,不能分两步:先读余额再扣减,并发时会出现超扣。
礼物的虚拟货币充值涉及真实金钱,支付回调必须在服务端验签(不能信任客户端告诉你的充值金额),到账后才更新账户余额。
八. API 接口规划参考
鉴权和房间
POST /api/token # 签发 RTC Token
POST /api/rooms # 创建房间
GET /api/rooms # 获取房间列表
GET /api/rooms/:roomId # 获取房间详情和初始状态
DELETE /api/rooms/:roomId # 解散房间(仅房主)
麦位管理
POST /api/rooms/:roomId/seats/:index/request # 申请上麦
POST /api/rooms/:roomId/seats/:index/approve # 房主批准上麦
POST /api/rooms/:roomId/seats/:index/reject # 房主拒绝上麦
DELETE /api/rooms/:roomId/seats/:index # 下麦(本人或房主强制)
POST /api/rooms/:roomId/seats/:index/lock # 锁定麦位
POST /api/rooms/:roomId/seats/:index/mute # 禁言
礼物和用户
POST /api/gifts # 送礼物
GET /api/gifts/list # 礼物列表
POST /api/rooms/:roomId/kick # 踢人(房主/管理员)
POST /api/rooms/:roomId/transfer # 转让房主
接口设计没有唯一答案,上面只是一个参考起点。实际设计时需要根据产品形态调整——比如有些产品支持游客进房(不登录),有些产品的麦位申请是异步审批流程,这些都会影响接口的设计。
