11 KiB
11 KiB
房间置顶与房间列表排序产品开发文档
目标
后台在「房间管理」下增加二级菜单「房间置顶」,用于运营配置公共房间发现页的置顶顺序。
置顶只影响 App 房间发现列表排序,不改变房间状态、不改变房间区域、不影响进房权限。
后台页面
菜单:房间管理 / 房间置顶
列表展示:
| 字段 | 说明 |
|---|---|
| 房间信息 | 房间头像、房间名、房间长 ID、房间短 ID |
| 房主信息 | 房主头像、昵称、用户长 ID、用户短 ID |
| 区域 | 房间当前区域 |
| 置顶时间 | pinnedAtMs 到 expiresAtMs |
| 排序 | weight,数值越大越靠前 |
| 置顶类型 | global 全区置顶;region 区域置顶 |
| 状态 | active 生效中;expired 已过期;cancelled 已取消 |
新增置顶流程:
- 输入用户短 ID 或房间 ID 搜索房间。
- 选择置顶类型:全区置顶或区域置顶。
- 选择置顶开始时间和结束时间。
- 填写排序值,数值越大越靠前。
- 点击置顶后,后台创建置顶记录。
区域规则:
- 全区置顶对所有用户国家/区域生效。
- 区域置顶只对房间当前区域生效,不需要后台单独选择区域。
- 房间后续改区域时,置顶记录不会自动改区域;后台可以取消旧置顶后重新置顶。
过期规则:
- 当前时间不在
[pinnedAtMs, expiresAtMs)内时,App 列表不再把该房间当置顶房间。 - 过期后房间自动恢复普通房间排序,不需要修改房间状态。
- 后台列表读取时会把已过期的 active 记录展示为
expired。
App 房间列表排序
地址:GET /api/v1/rooms
参数:
| 参数 | 说明 |
|---|---|
tab |
hot 或 new |
limit |
每页数量 |
cursor |
下一页游标 |
q |
搜索关键词,可不传 |
区域来源:
- gateway 使用登录用户 ID 调 user-service 获取
users.region_id。 - 客户端传的区域参数不会生效。
- room-service 按该用户区域做排序分组。
hot 排序:
- 全区置顶房间,多个房间按
weight DESC。 - 当前用户区域的置顶房间,多个房间按
weight DESC。 - 当前用户区域有人普通房,按
hot_score DESC。 - 其他区域有人普通房,按
hot_score DESC。 - 当前用户区域空房,按
hot_score DESC。 - 其他区域空房,按
hot_score DESC。
hot_score 规则:
heat_score = log2(heat + 1)
online_score = log2(min(online_count, 50) + 1)
mic_score = min(occupied_seat_count, 10)
hot_score = heat_score * 650000
+ online_score * 300000
+ mic_score * 50000
说明:
heat是房间贡献值。online_count = 0的普通房进入空房分组;只要存在有人普通房,空房不会排在有人普通房前面。- 在线人数最多按 50 人计分,防止单个房间靠人数无限放大。
- 上麦人数最多按 10 人计分,只作为活跃气氛补充。
new 排序:
- 全区置顶房间,多个房间按
weight DESC。 - 当前用户区域的置顶房间,多个房间按
weight DESC。 - 当前用户区域的普通房间,按创建时间
created_at_ms DESC。 - 其他区域的普通房间,按创建时间
created_at_ms DESC。
同一个排序分组内,如果排序值相同,后端使用过期时间和房间 ID 保证分页稳定。
返回值:
{
"code": 0,
"message": "ok",
"request_id": "request-id",
"data": {
"rooms": [
{
"room_id": "room-global-pin",
"room_short_id": "163212",
"owner_user_id": "165059",
"title": "room name",
"cover_url": "https://example.com/room.png",
"visible_region_id": 2,
"heat": 1000,
"online_count": 0,
"locked": false
}
],
"next_cursor": ""
}
}
相关 IM:
- 无新增 IM。
- 置顶只影响 HTTP 房间列表读模型,不发送房间群消息或用户私信。
房间列表在线人数更新逻辑
房间列表返回的 online_count 来自 room-service 的业务 presence,不直接读取腾讯 IM 在线人数,也不直接读取 RTC 在线人数。
更新入口:
| 入口 | 说明 | 列表人数变化 |
|---|---|---|
POST /api/v1/rooms/join |
用户进房,room-service 把用户写入 Room Cell 的 OnlineUsers |
增加或保持不变 |
POST /api/v1/rooms/heartbeat |
用户在房间内刷新 LastSeenAtMS |
人数不变,只延长在线有效期 |
POST /api/v1/rooms/leave |
用户主动离房,room-service 从 OnlineUsers 删除用户 |
减少 |
| 踢人、关房、系统驱逐 | 房间命令链路清理 presence | 减少或清零 |
| stale worker | 用户断线、杀进程、没有主动离房时,由后台扫描清理超时 presence | 延迟减少 |
投影规则:
- 每次 Room Cell 命令成功提交后,room-service 用最新快照刷新
room_list_entries。 room_list_entries.online_count = len(snapshot.online_users)。room_list_entries.occupied_seat_count来自当前被占用麦位数。hot_score使用online_count和occupied_seat_count,所以在线人数变化会影响 hot 普通房排序。- 列表投影是最终一致读模型;投影失败只记录日志,不回滚已经成功的房间命令。
延迟:
| 场景 | 预计延迟 |
|---|---|
| 正常进房 | 同一次 JoinRoom 命令内刷新,通常是毫秒级到几十毫秒 |
| 主动离房 | 同一次 LeaveRoom 命令内刷新,通常是毫秒级到几十毫秒 |
| 踢人、关房、系统驱逐 | 同一次房间命令内刷新,通常是毫秒级到几十毫秒 |
| 心跳 | 只刷新有效期,不改变列表人数 |
| App 断线或杀进程且没有主动离房 | 当前配置 presence_stale_after = 2m,presence_stale_scan_interval = 30s,通常约 2 分钟到 2 分 30 秒后列表人数减少 |
相关 IM:
- 无新增 IM。
- 在线人数是 room-service 业务 presence,不等于腾讯 IM 长连接在线人数。
- 腾讯 IM 退群、RTC 退房不会直接删除 room-service presence;业务离房只能由
LeaveRoom、踢人、关房、系统驱逐或 stale worker 完成。
后台接口
后台接口使用后台登录 token 和 /api/v1 前缀。
查询置顶列表
地址:GET /api/v1/admin/rooms/pins
参数:
| 参数 | 说明 |
|---|---|
page |
页码,默认 1 |
page_size |
每页数量,默认 20,最大 100 |
keyword |
房间 ID、房间短 ID、房间名关键词 |
status |
active、expired、cancelled、all,默认 active |
pinType |
global 或 region,不传表示全部类型 |
regionId |
区域筛选,不传表示全部区域 |
返回值:
{
"code": 0,
"message": "ok",
"data": {
"items": [
{
"id": 1,
"pinType": "global",
"status": "active",
"weight": 100,
"pinnedAtMs": 1780665600000,
"expiresAtMs": 1783257600000,
"remainingMs": 2592000000,
"room": {
"roomId": "room-global-pin",
"roomShortId": "163212",
"title": "room name",
"visibleRegionId": 2,
"regionName": "南亚区"
},
"user": {
"userId": "165059",
"displayUserId": "165059",
"username": "host name"
}
}
],
"page": 1,
"pageSize": 20,
"total": 1
}
}
搜索房间
地址:GET /api/v1/admin/rooms
参数:
| 参数 | 说明 |
|---|---|
keyword |
用户短 ID、房间短 ID、房间长 ID、房间名 |
status |
建议新增置顶时传 active |
page |
页码 |
page_size |
每页数量 |
说明:
- 输入用户短 ID 时,后台会先查用户,再按房主用户 ID 精确查房间。
- 输入房间 ID 时,后台直接按 room-service 房间列表查询。
新增置顶
地址:POST /api/v1/admin/rooms/pins
参数:
| 参数 | 说明 |
|---|---|
roomId |
房间长 ID 或房间短 ID |
pinType |
global 全区置顶;region 区域置顶;不传默认 region |
weight |
排序值,不能小于 0 |
pinnedAtMs |
置顶开始时间,Unix 毫秒 |
expiresAtMs |
置顶结束时间,Unix 毫秒 |
durationDays |
旧字段兼容;新后台优先传开始和结束时间 |
请求示例:
{
"roomId": "room-global-pin",
"pinType": "global",
"weight": 100,
"pinnedAtMs": 1780665600000,
"expiresAtMs": 1783257600000
}
返回值:返回创建后的置顶记录,结构同列表单条记录。
取消置顶
地址:DELETE /api/v1/admin/rooms/pins/{pin_id}
参数:
| 参数 | 说明 |
|---|---|
pin_id |
置顶记录 ID |
返回值:返回取消后的置顶记录,状态为 cancelled。
相关 IM:
- 无新增 IM。
- 新增、取消、过期置顶都只影响发现页排序。
数据规则
room_region_pins.pin_type = global表示全区置顶,落库visible_region_id = 0。room_region_pins.pin_type = region表示区域置顶,落库visible_region_id = 房间当前 visible_region_id。- 有效置顶条件:
status = active且pinned_at_ms <= now_ms且expires_at_ms > now_ms。 - 同一个房间可以同时有全区置顶和区域置顶;App 列表优先按全区置顶分组展示。
hot普通房先按本区域/其他区域和有人/空房分组,再按hot_score排序。new普通房只按本区域/其他区域分组,再按创建时间排序。- 置顶不进入 Room Cell、snapshot 或 command log;它是发现页运营排序读模型。
- 房间核心状态仍由 room-service 管理,后台不直接写房间库。
本地真实数据验证
验证环境:
- 本地 Docker MySQL:
hyapp-mysql,端口23306,状态 healthy。 - 用真实 MySQL 临时库执行 room-service 用例,不使用 memory repository。
验证命令:
ROOM_SERVICE_MYSQL_TEST_DSN='root:root@tcp(127.0.0.1:23306)/?parseTime=true&loc=UTC&multiStatements=true' \
go test ./services/room-service/internal/room/service \
-run 'TestRoomListSortScore|TestPinnedRoomListOrdersGlobalRegionLocalThenOtherFor(Hot|New)|TestRegionalPinOrdersPublicRoomList' \
-count=1 -v
验证结果:
TestRegionalPinOrdersPublicRoomList通过,验证区域置顶优先于同区域普通 hot 房间。TestPinnedRoomListOrdersGlobalRegionLocalThenOtherForHot通过,验证 hot 顺序为全区置顶、当前区域置顶、当前区域有人普通房、其他区域有人普通房、当前区域空房、其他区域空房。TestPinnedRoomListOrdersGlobalRegionLocalThenOtherForNew通过,验证 new 顺序为全区置顶、当前区域置顶、当前区域普通房、其他区域普通房。TestRoomListSortScoreUsesCompressedWeightedInputs通过,验证hot_score使用 65%、30%、5% 的压缩权重。TestRoomListSortScoreCapsPresenceInputs通过,验证在线人数和上麦人数有计分上限。- 测试过程创建了真实房间数据:
room-global-pin、room-region-pin、room-local-online、room-other-online、room-local-empty、room-other-empty、room-global-new、room-region-new等。
补充说明:
- 普通
hyappMySQL 用户没有创建临时库权限,所以本地真实数据验证使用 root DSN 建临时库。 - 该验证不连接线上库,不影响线上数据。