hyapp-server/docs/进房与重连开发需求.md

338 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Room Entry And Reconnect Development Requirements
本文档定义下一阶段必须完成的语音房基础闭环:创建房间、进房、离房、断线重连。目标是让一个房间可以被稳定打开、进入、退出,并在客户端断线或服务短暂抖动后恢复到可继续使用的状态。
## Goal
本阶段只解决房间生命周期的最小业务闭环。
| Capability | Required Result |
| ------------ | ---------------------------------------------------------------------- |
| 创建房间 | 用户可以创建一个 active 语音房,并获得房间快照 |
| 进房 | 用户可以进入房间业务 presence并获得当前房间状态 |
| 订阅房间消息 | 用户进房成功后可以登录腾讯云 IM 并加入房间群 |
| 进入音频频道 | `JoinRoom` 成功响应内嵌腾讯 RTC token客户端可以立即进入 TRTC audience |
| 离房 | 用户主动退出后,房间 presence 被清理,后续进群和发言必须被业务守卫拒绝 |
| 断线重连 | 用户短暂断线后,可以重新鉴权、恢复房间状态、重新登录腾讯云 IM 并进群 |
| 防越权 | 未进房、被踢、被封禁用户不能继续进群或发公屏 |
## Non Goals
本阶段不做这些:
| Not Included | Reason |
| ---------------- | ------------------------------------ |
| 麦位完整玩法 | 放到下一阶段,避免和进房链路混在一起 |
| RTC 录制/审核 | 首版只处理进房听音频所需 token |
| 金币和礼物 | 需要 wallet-service 先落地 |
| 推荐流和房间列表 | 先保证单房间可用 |
| 多端复杂同步 | 首版只保证同一用户重连恢复 |
| 热门房主备热同步 | 当前仍是单房间单活 + lease 接管 |
## Ownership
| Domain | Owner | Rule |
| ----------------- | ----------------- | ------------------------------------------ |
| HTTP 房间命令 | `gateway-service` | 只做鉴权、入参校验、协议转换 |
| 房间业务 presence | `room-service` | 创建房间、进房、离房、封禁、恢复 |
| 实时连接和房间群 | 腾讯云 IM | 持有客户端长连接、群成员关系、房间消息历史 |
| 身份和 token | `user-service` | 登录、token、用户状态 |
| 真实语音链路 | 腾讯云 RTC | 客户端持 JoinRoom 内嵌 token 进入音频频道 |
关键边界:
- `room-service` 不保存 socket。
- 腾讯云 IM 不拥有房间业务 presence业务守卫仍以 `room-service` 为准。
- `gateway-service` 不信任客户端自报 `owner_user_id``host_user_id``actor_user_id`
- 所有业务身份以 access token 解析出的 `user_id` 为准。
- 腾讯云 IM 登录使用 `gateway-service` 签发的 UserSigUserSig 里的 identifier 使用不可变长 `user_id`
## Presence Model
需要明确区分三种状态:
| State | Owner | Meaning |
| ----------------- | -------------- | ----------------------------------------- |
| 房间业务 presence | `room-service` | 用户业务上属于这个房间 |
| 房间群连接态 | 腾讯云 IM | 用户当前客户端是否登录 IM、是否在房间群内 |
| RTC 在线态 | 腾讯云 RTC | 用户是否在真实音频频道中 |
本阶段由 room-service 维护业务 presence通过腾讯云 IM 群承载消息,并由 gateway 在 JoinRoom 成功后签发腾讯 RTC 进房票据。RTC 在线态仍以腾讯云 RTC 为准,不写入 room-service 业务 presence。
## External API
### Create Room
```text
POST /api/v1/rooms/create
Authorization: Bearer <access_token>
```
建议请求:
```json
{
"command_id": "cmd_create_room_550e8400",
"mode": "voice",
"room_name": "周末语音房",
"room_avatar": "https://cdn.example/room.png",
"room_description": "本房间用于轻量聊天和上麦互动"
}
```
正式客户端不应该传 `room_id``owner_user_id``host_user_id``command_id` 由客户端按创建房间动作生成同一动作重试必须复用。gateway 必须从 access token 取当前 `user_id` 写入 `RequestMeta.actor_user_id`,并生成内部 `room_id`room-service 使用该 actor 确定 owner 和默认 host。`seat_count` 可省略,省略或传 `0` 时使用后台默认座位数 `15`;传正数时必须命中后台启用座位数。`room_name``room_avatar` 必填,房间展示资料统一落在 `RoomExt`
响应必须包含:
```json
{
"result": {
"applied": true,
"room_version": 1,
"server_time_ms": 1777000000000
},
"room": {}
}
```
### Join Room
```text
POST /api/v1/rooms/join
Authorization: Bearer <access_token>
```
请求:
```json
{
"room_id": "room_1001",
"command_id": "cmd_join_room_1001_10002_1",
"role": "audience"
}
```
响应必须包含首屏房间数据、用户业务 presence、IM 房间群和 RTC token
```json
{
"result": {},
"room": {
"room_id": "room_1001",
"im_group_id": "room_1001",
"online_count": 26,
"seat_count": 15,
"version": 2
},
"viewer": {
"user_id": "10001",
"role": "audience"
},
"seats": [],
"contribution_rank": [],
"profiles": [],
"im": {
"group_id": "room_1001",
"need_join_group": true
},
"rtc": {
"need_token": true,
"available": true,
"token": {}
},
"server_time_ms": 1777000000000
}
```
`JoinRoom` 必须幂等。用户已经在房间时,不能重复增加在线人数或重复广播进房消息,但需要刷新 `last_seen_at_ms`
### Leave Room
```text
POST /api/v1/rooms/leave
Authorization: Bearer <access_token>
```
请求:
```json
{
"room_id": "room_1001",
"command_id": "cmd_leave_room_1001_10002_1"
}
```
`LeaveRoom` 必须幂等。用户已经不在房间时,返回当前快照,不报业务错误。
### Tencent IM Login And Join Group
客户端可以在 App 启动后向 gateway 获取腾讯云 IM UserSig但房间群必须等 `JoinRoom` 成功后再加入:
```text
GET /api/v1/im/usersig
Authorization: Bearer <access_token>
```
响应中的 `user_id` 是腾讯云 IM identifier必须等于内部不可变长 `user_id` 的十进制字符串。
客户端随后使用腾讯云 IM SDK 登录并加入房间群:
```json
{
"sdk_app_id": 1400000000,
"user_id": "10001",
"user_sig": "usersig_xxx",
"room_id": "room_1001",
"im_group_id": "room_1001"
}
```
进群前业务守卫仍然需要存在。首版可以由客户端先 `JoinRoom` 再进群;线上必须补腾讯云 IM 回调或 gateway 代理校验:
```text
room-service.VerifyRoomPresence(room_id, user_id, session_id)
```
只有 `present=true` 才允许进群或继续接收房间业务消息。被踢或封禁后,`room-service` 负责写 outbox 并通过腾讯云 IM 推系统事件,客户端必须按事件退房或触发重新同步。
## Required Internal Changes
当前基础接口已经存在,但本阶段需要补齐这些行为。
### gateway-service
| Requirement | Detail |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 创建房间身份收敛 | `CreateRoom` 的 owner/host 必须由 room-service 从 `RequestMeta.actor_user_id` 收敛,不接受客户端 body 自报 |
| command_id | 写命令请求体使用客户端按用户动作生成的稳定 `command_id`,用于 room-service command log 幂等 |
| 统一错误映射 | 房间不存在、被踢、无权限、重复命令要映射成稳定 HTTP envelope |
| 请求日志 | 记录 `request_id``user_id``room_id`、command type |
### room-service
| Requirement | Detail |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| CreateRoom | 创建 meta、command log、snapshot、outbox并安装 Room Cell |
| 单 owner 单房间 | 同一个 `RequestMeta.actor_user_id` 只能作为 owner 创建一个房间;已有 owner 房间时返回 Conflict |
| JoinRoom 幂等 | 已在房间时刷新 `last_seen_at_ms`,不重复广播 |
| LeaveRoom 幂等 | 不在房间时返回当前快照 |
| VerifyRoomPresence | 腾讯云 IM 回调或 gateway 代理进群前检查房间存在、用户在房间、未被 ban |
| Presence 过期 | 支持断线宽限期,超过 TTL 后自动清理 |
| Recovery | 节点重启后能通过 snapshot + command log 恢复房间 |
建议新增配置:
```yaml
room_presence_reconnect_grace: "60s"
room_presence_stale_timeout: "300s"
room_presence_sweep_interval: "30s"
```
### Tencent Cloud IM Integration
| Requirement | Detail |
| ---------------- | --------------------------------------------------------------------------------------------------- |
| UserSig | `gateway-service` 只给已鉴权用户签发腾讯云 IM UserSig |
| EnsureRoomGroup | `room-service.CreateRoom` 必须确保腾讯云 IM 群组存在 |
| PublishRoomEvent | 房间状态变更后同步推腾讯云 IM 群系统消息,失败不回滚房间状态 |
| Guard Callback | 线上需要腾讯云 IM 进群/发言回调或等价 gateway 代理调用 `VerifyRoomPresence``CheckSpeakPermission` |
| History | 房间最近消息优先使用腾讯云 IM 群历史;本仓库不再保存自研 IM 时间线 |
## Reconnect Design
断线分三种。
| Case | Expected Behavior |
| ------------------ | -------------------------------------------------------------------------------------- |
| IM 短断 | room-service presence 保留,客户端重新登录腾讯云 IM 后重新进群 |
| App 退后台后恢复 | 客户端先调用 `RoomHeartbeat` 刷新已有 presence如果返回 `NOT_FOUND` 再重新 `JoinRoom` |
| 超过 stale timeout | room-service 自动清理 presence客户端需要重新进房 |
客户端重连标准流程:
```text
1. 如果 access token 过期,先 RefreshToken
2. 调 POST /api/v1/rooms/heartbeat刷新房间业务 presence
3. 调 POST /api/v1/im/usersig拿腾讯云 IM UserSig
4. 使用腾讯云 IM SDK 登录
5. 加入 JoinRoom 返回的 `im.group_id` 对应腾讯云 IM 群
6. 拉取腾讯云 IM 群历史或等待房间系统消息补齐
7. 如果需要音频且旧 RTC token 已过期,调用 `/api/v1/rtc/token` 补签
8. 用 RoomHeartbeat 返回的 RoomSnapshot 覆盖本地房间状态
如果第 2 步返回 `NOT_FOUND`,说明业务 presence 已不存在,客户端必须走完整 `JoinRoom -> IM/RTC 重进` 流程。
```
服务端原则:
- 断线不能立刻等同于离房。
- 主动 `LeaveRoom` 才是明确离房。
- 超时清理是兜底,不能依赖客户端一定发离房。
- 被踢或封禁后,即使客户端仍在腾讯云 IM 群内,也必须通过回调守卫、系统事件和客户端同步阻断继续发言或展示在房间内。
## Data Requirements
`RoomUserState` 至少需要支持:
| Field | Meaning |
| ----------------- | --------------------------------------- |
| `user_id` | 房间内业务用户 |
| `role` | owner, host, admin, audience |
| `joined_at_ms` | 首次进入当前房间时间 |
| `last_seen_at_ms` | 最近一次 JoinRoom 或 RoomHeartbeat 时间 |
建议后续追加:
| Field | Meaning |
| ----------------------- | --------------------------- |
| `connection_state` | online, reconnecting, stale |
| `reconnect_deadline_ms` | 断线宽限截止时间 |
首版可以先不把 `connection_state` 暴露给客户端,但内部清理逻辑要能基于 `last_seen_at_ms` 判断 stale。
## Event Requirements
房间状态成功变更后,`room-service` 必须写 outbox。
| Action | Outbox Event | IM System Event |
| -------------- | ---------------- | ---------------------------------------- |
| 创建房间 | `RoomCreated` | 可不广播 |
| 进房成功 | `RoomUserJoined` | `room_user_joined` |
| 离房成功 | `RoomUserLeft` | `room_user_left` |
| stale 自动离房 | `RoomUserLeft` | `room_user_left`reason=`stale_timeout` |
| 被踢 | `RoomUserKicked` | `room_user_kicked` |
同步 `PublishRoomEvent` 失败不能回滚房间状态,仍由 room outbox 补投到腾讯云 IM 和后续活动系统。
## Acceptance Tests
必须覆盖这些测试。
| Test | Expected Result |
| -------------------------- | ----------------------------------------------------------------- |
| 创建房间成功 | 返回 version=1owner 在 online_users 内 |
| 创建房间请求带客户端 ID | gateway 忽略 `room_id`,使用客户端 `command_id` 并生成内部房间 ID |
| 普通用户 JoinRoom | presence 增加,广播进房事件 |
| 重复 JoinRoom | 不重复广播,不重复增加人数,刷新 last_seen |
| LeaveRoom | presence 移除,进群/发言守卫拒绝后续实时消息权限 |
| 重复 LeaveRoom | 幂等返回,不报错 |
| JoinRoom 后进群 | `VerifyRoomPresence` 通过,腾讯云 IM 群收到房间消息 |
| 未 JoinRoom 直接进群或发言 | `VerifyRoomPresence``CheckSpeakPermission` 返回 `not_in_room` |
| Kick 后进群或发言 | 返回 `user_banned` |
| IM 断开后 60 秒内重连 | RoomHeartbeat 刷新,腾讯云 IM 重新登录和进群成功,历史可拉 |
| 超过 stale timeout | presence 自动清理,需要重新 JoinRoom |
| room-service 重启恢复 | snapshot + command log 恢复后 JoinRoom/subscribe 继续可用 |
## Implementation Order
建议按这个顺序做:
1. 修正创建房间身份来源gateway 不接收 body 的 owner/hostroom-service 从 `RequestMeta.actor_user_id` 收敛 owner/default host。
2. 补齐 JoinRoom 幂等刷新 `last_seen_at_ms`
3. 增加显式 RoomHeartbeat只刷新已有 presence不隐式进房。
4. 补齐 LeaveRoom 后对订阅守卫的拒绝行为测试。
5. 接入腾讯云 IM UserSig 签发、CreateRoom 建群和房间系统消息发布。
6. 增加 stale presence 清理 worker。
7. 增加重连集成测试JoinRoom -> UserSig -> IM login/join group -> disconnect -> heartbeat/reconnect -> history。
8. 增加 room-service 重启恢复测试。