在线联机原型全集:第 20 章 社交大厅
社交大厅(Platform Lobby|Multi-Room Roaming & Unified Identity)
-
类别:平台层(账号 + 会话 + 大厅 + 房间目录 + 即时通信 + 漫游)
-
目标:
- 构建统一身份系统(OIDC/OAuth2 + JWT/RT + Device Binding);
- 打通多房间漫游(从大厅→若干游戏房→返回大厅)的无缝进出与断线恢复;
- 提供房间目录与搜索、全局在线/好友/队伍/公会、跨房间即时通信与内容安全;
- 支撑“一个入口,多原型切换”的产品化能力(第 14–19 章原型可从此入口跳转)。
-
原型代号:
proto-020-lobby-portal -
依赖模块:
proto-002-rps(时序/事务基元)、proto-007-snake-battle(网关/房间骨架)、proto-016/17/19(断线恢复/观战/AOI 思路) -
推荐栈:Server(Go/Java 优先,网关 + 身份 + 大厅 + 漫游协调器),Client(TS/Unity/Unreal)
-
协议栈:HTTP(注册/登录/目录/鉴权) + WebSocket(在线/IM/事件) + OIDC(外部登录)
1. 总体视图与模块划分
graph TD
U["Client(Web/PC/Mobile)"] --> GW["Gateway(WS/UDP/HTTP)"]
GW --> IDP["Identity Provider(OIDC/OAuth2)"]
GW --> LOB["Social Lobby(Presence/IM/Directory)"]
LOB --> DIR["Room Directory(Match/Query)"]
LOB --> PARTY["Party/Team"]
LOB --> MOD["Moderation/Safety"]
LOB --> ROAM["Roaming(Coordinator/Handoff)"]
ROAM --> RM1["Game Room A"]
ROAM --> RM2["Game Room B"]
IDP --> KMS["Key Mgmt(JWT/Device Keys)"]
LOB --> STORE["Profile/Relation/Inventory"]
LOB --> OBS["Metrics/Logs/Audit"]
2. 统一身份系统(Unified Identity / SSO)
2.1 账号模型
- User:
user_id(全局唯一,雪花/ULID)、tenant_id(可选多租户)、username、email/phone、status - Identity:外部绑定(
provider=wechat/google/steam/...,sub),多身份→一账号 - Profile:展示名、头像、地区、语言、年龄段标记(COPPA/未成年人保护)
- Security:
mfa、password_hash、device_keys[]、risk_score
2.2 鉴权与令牌
-
OIDC/OAuth2:
/authorize → /token;支持密码/设备码/社交登录 -
令牌对:
- Access Token(JWT):短期(5–15 分钟),作用域(
scope: lobby.read chat.send room.join) - Refresh Token(RT):长寿命(7–30 天),可绑定设备与会话
- Access Token(JWT):短期(5–15 分钟),作用域(
-
JWT Claim 建议:
sub, aud, iss, iat, exp, jti, scope, device_id, tenant_id, session_id, roles -
密钥:KMS 轮换(Kid + JWKs),支持逐版本签名与强制下线(RT 黑名单 + JWT 版本号)
2.3 设备与会话
- Device Binding:设备指纹/ID(可匿名→登录后绑定)
- 多端策略:同一账号可多设备在线;
policy: allow_one_room_per_game - 会话表:
sessions(session_id, user_id, device_id, ip, ua, created_at, last_seen, jwt_ver)
2.4 权限/角色
- 系统角色:
user, mod, gm, admin - 房间内角色:
owner, host, member, spectator, banned - 策略引擎:ABAC(属性 + 规则),例如“未成年人禁用语音/夜间限时”。
3. 社交大厅(Presence / IM / Directory)
3.1 在线状态(Presence)
- 状态:
offline, online, in_lobby, in_room(game_id, room_id), do_not_disturb - 心跳:WS 心跳 15–30s;超时转离线;
- 订阅:好友/公会/队伍订阅其在线变化,事件
PresenceUpdate。
3.2 即时通信(IM)
- 频道:
global, lobby, room, party, guild, dm - 消息类型:文本/表情/系统卡片(开房/邀请/战绩)
- 漫游存档:按频道分区,窗口滑动(例如最近 1000 条)
- 内容安全:脏词过滤、敏感样式检测、图像审核(占位,原型可文本)
3.3 房间目录(Directory)
- 查询:
list(game_id, mode, region, elo_band, keyword) - 排序:活跃度、延迟、好友在内优先
- 开房:
CreateRoom(game_id, capacity, visibility=public|friends|private, password?) - 匹配:轻量 MatchMaker(基于段位/地区/队伍人数);可对接 15–19 章的各原型
4. 多房间漫游(Roaming / Handoff)
4.1 目标
- 从大厅无缝切换到任一游戏房,再返回;
- 更换房间/游戏时会话不断开,只更新目标房间授权;
- 断线恢复:重连后自动回到上次所在房间/大厅。
4.2 漫游令牌(Room Ticket)
- Room Ticket(短签):由 Roaming 协调器签发,
ttl=60–120s,一次性消费 - 内容:
user_id, room_id, game_id, issued_at, exp, role, session_id, anti_replay_nonce, sig(kid) - 使用:客户端持大厅 Access Token 向 Roaming 请求 Ticket → 与目标房间握手 → 房间验证签名并绑定会话
4.3 切换时序
sequenceDiagram
participant C as Client
participant L as Lobby (Roaming)
participant R as Game Room
C->>L: requestRoomTicket(game_id, room_id, access_jwt)
L-->>C: room_ticket(sig), room_endpoint
C->>R: ws.connect(room_endpoint, room_ticket)
R->>R: verify(ticket.sig & jti not used)
R-->>C: join-ack(state snapshot / seat / role)
C-->>L: presence: in_room(game_id, room_id)
4.4 返回大厅 / 跨房切换
- 退出房间 → 房间发
leave到 Lobby → Presence 切回in_lobby - 跨房:直接申请新 Ticket,与新房握手后旧房自动释放座位(或开启“并房观战”权限)
4.5 断线恢复
-
客户端携
session_id + last_room重连 Lobby → Lobby 查询room_state:- 若房间仍存活且座位保留 → 再签发 Ticket 复位
- 若房间关闭 → 回大厅并推送“上局战绩/回放链接”
5. 统一资料(Profile)、好友/队伍/公会
5.1 资料与隐私
- 公开资料:昵称/头像/地区/签名/最近游玩
- 隐私控制:可见范围(公开/好友/仅自己)、开关“显示在线/显示所在房间”
- 屏蔽/拉黑:在 IM 与匹配中生效
5.2 好友与关系
- 关系图:
friend, block, follow - 邀请/同玩:发起
PartyInvite,接受后进入队伍频道 - 跨游戏邀请:发“跳转+Ticket”的系统卡片
5.3 队伍(Party)
- 结构:
party_id, leader, members[], game_lock? - 就绪流:
ReadyCheck,超时替换/开黑保护 - 一键进房:队长为全队申请房间与 Tickets,成员逐个握手
5.4 公会(Guild)
- 层级:
owner, officer, member - 频道:公会聊天室与公告;活动(固定开房模板)
6. 内容安全与治理(Moderation)
- 实时过滤:脏词/辱骂/广告;命中→降权/撤回/禁言
- 举报闭环:
Report(user_id, evidence)→ 审核队列 → 结果通知 - 安全评分:
safety_score跨房共享,影响匹配分组与聊天阈值 - 未成年人保护:夜间禁言/禁玩时段、语音关闭、家长控制 PIN
- 合规审计:日志落地(消息、踢封、举报处理结果),留存周期可配
7. 数据与存储模型(要点)
7.1 关系/KV 表
users(id, tenant_id, username, email, phone, status, created_at)identities(user_id, provider, sub, meta)sessions(id, user_id, device_id, ip, ua, jwt_ver, last_seen)profiles(user_id, nickname, avatar, locale, privacy, safety_score)relations(user_id, peer_id, kind, created_at)parties(id, leader, members_blob, game_lock)guilds(id, owner, meta)/guild_members(gid, uid, role)rooms(id, game_id, state, endpoint, cap, visibility, tags, created_at)presence(user_id, state, game_id, room_id, updated_at)chat_messages(channel_id, seq, sender, kind, content, ts)(按频道分表/流式存储)moderation_actions(id, user_id, action, reason, ts)tickets(jti, user_id, room_id, exp, used)(一次性校验与黑名单)
7.2 事件溯源(供回放/审计)
- Lobby Event:
UserLogin, UserLogout, CreateRoom, JoinRoom, LeaveRoom, PartyInvite, PartyAccept, FriendAdd, Mute, Ban - Chat Event:
Message, Recall, Report, Action - 可重放:用于重建大厅状态(在线数/队伍/房间列表)
8. API & 协议(示例)
8.1 登录/刷新(HTTP)
POST /oauth/token
grant_type=authorization_code|password|refresh_token
→ { access_token, refresh_token, expires_in, id_token?, scope }
8.2 大厅 WS(事件流)
// 订阅
{ "t":"sub", "ch":["presence","friends","party","rooms","chat:lobby"] }
// 房间查询
{ "t":"rooms.query", "game":"proto-019", "filters":{"region":"eu","mode":"squad"} }
// 邀请
{ "t":"party.invite", "to": 12345, "game":"proto-016" }
// 聊天
{ "t":"chat.send", "ch":"chat:party:789", "msg":"ready?" }
8.3 漫游 Ticket(HTTP)
POST /roam/ticket
Authorization: Bearer <access_jwt>
Body: { "room_id":"r-abc", "game_id":"proto-016" }
→ { "ticket":"base64(sig)...", "endpoint":"wss://g16.example/ws" }
9. 断线恢复与多端切换
- 大厅恢复:凭
session_id恢复 Presence 与订阅频道;离线消息补齐(光标last_seq) - 房间恢复:参照各游戏原型的关键帧 + 事件补齐;Lobby 负责 Ticket 再签发
- 多端切换:在新设备登录后,允许“迁移会话”(旧端降级为只读大厅或被踢下线,策略可配)
10. 可观测性与指标
- 身份:登录成功率、RT 刷新失败率、JWT 版本失配率
- 大厅:在线人数、并发 WS 连接数、消息吞吐、延迟分位
- 漫游:Ticket 签发 QPS、校验失败率、跨房切换耗时、断线恢复时间
- 社交:日活私聊/群聊条数、撤回/举报率、误杀率
- 治理:封禁/禁言/解封时长分布、复核通过率
- SLA:网关 p95、Lobby p95、目录查询 p95、单节点最大并发连接
11. 安全与风控
| 场景 | 风险 | 对策 |
|---|---|---|
| 令牌窃取 | 伪造或重放 | JWT Kid+过期+受众校验;RT 绑定设备/IP 风险;jti 黑名单;TLS 固钉 |
| Ticket 滥用 | 跨房复用 | Ticket 一次性 + 短期;房间验证 used;Room ↔ Lobby 双向回执 |
| 冒名顶替 | 改昵称/头像欺骗 | 昵称唯一/审核;房内展示 #user_id 简短标签 |
| 垃圾消息 | 机器人刷屏 | 频道速率限制/滑动窗口;挑战(验证码/行为学) |
| 年龄合规 | 未成年保护 | 时段/语音限制;敏感词更严格阈值 |
| 数据泄漏 | 隐私/聊天外泄 | 服务端裁剪;最小可见原则;敏感字段脱敏存储 |
12. 测试与验证
- SSO 回路:密码/社交/刷新/轮换/强制下线全链路;
- 漫游稳定性:1→N 房切换(N=20),平均切换耗时 < 800ms;断线 10s 后恢复 < 3s;
- 目录吞吐:1 万在房 + 2 千并发查询,p95 < 120ms;
- IM 压测:每秒 5 万条消息,按频道分片,丢包率 0;
- 安全回归:令牌重放/混淆输入/超速聊天/大字节消息拦截;
- 容灾:Lobby 节点滚动升级/重启不断线;KMS 轮换不中断签名校验。
13. 客户端 UX 关键点
- 统一入口:左侧游戏平台(原型列表/最近游玩/推荐),右侧聊天/好友/队伍面板
- 一键开黑:队伍一键开房并跳转(显示“正在为队伍预留座位…”)
- 漫游反馈:切换房间的过渡卡片(房间名、模式、延迟、票据校验进度)
- 错误可恢复:Ticket 过期→自动重签;房间满员→排队与预计时间
- 隐私开关:一键隐身、关闭被邀请、限制陌生人私聊
14. 版本迭代路线
| 版本 | 目标 | 要点 |
|---|---|---|
| v0.1 | 身份 + 大厅骨架 | OIDC/JWT/RT、Presence、Lobby WS、房间目录 |
| v0.2 | 漫游最小闭环 | Ticket + Handoff + 断线回到房间 |
| v0.3 | 社交增强 | 好友/队伍/邀请卡片、频道化 IM、基础过滤 |
| v0.4 | 治理与合规 | 举报/禁言/审计、未成年策略 |
| v1.0 | 规模与稳定 | 横向扩展、指标看板、压测达标(>50k 并发 WS) |
15. MVP 勾选清单
- OIDC 身份与 JWT/RT 策略(KMS 轮换 + 强制下线)
- 大厅 WS(Presence/IM/Directory)
- Roaming Ticket(一次性短签)与房间握手
- 多房间切换与断线恢复
- 基础内容安全(脏词/速率)与举报流
- 指标看板与审计日志
16. 参考实现要点(伪代码)
16.1 签发 Ticket(Go)
type RoomTicket struct {
UserID string `json:"sub"`
RoomID string `json:"rid"`
GameID string `json:"gid"`
Sid string `json:"sid"`
Nonce string `json:"jti"`
Iat int64 `json:"iat"`
Exp int64 `json:"exp"`
Role string `json:"role"`
}
func IssueTicket(user, room, game, sid string, ttl time.Duration) (string, error) {
t := RoomTicket{UserID:user, RoomID:room, GameID:game, Sid:sid,
Iat: now(), Exp: now()+int64(ttl.Seconds()), Nonce: ulid(), Role:"member"}
return jwt.Sign(t, kms.ActiveKey()) // kid embedded
}
16.2 房间校验(Go)
func (h *Handshake) Verify(ticket string) (*Claims, error) {
cl := jwt.Verify(ticket, kms.JWKS())
if used(cl.Jti) || cl.Exp < now() || cl.Aud != expectedAud { return ErrDenied }
markUsed(cl.Jti)
bindSession(cl.Sid, cl.Sub, cl.RoomID)
return cl, nil
}