Files
huajishe-tts/docs/texas/02-room-delete-host-heartbeat-kick.md
e2hang 04d0af515c feat: 登录系统落地 + 踢人修正 + 单房间约束 + 权限收口
问题8 登录系统:
- node:sqlite 账号存储(scrypt 哈希 + HMAC 签名 token),preset admin 改为 e2hang/evan1115
- /api/auth/register|login|me,me 返回 room 供重联
- 网关拦截 create/join 需登录(401),列表保持开放

踢人逻辑修正(问题2 延伸):
- kickMove 真正释放座位(folded=false, name=null)而非锁死
- war/mill 去掉永久封禁语义

管理员越权:管理员 token 可删任意房间、踢任意房间内的人

单房间约束(用户新需求):
- 同一用户同时只能在一个房间,create/join 他房返回 409
- 踢人后若房间仅剩踢人者则自动解散并释放其记录(修复 2 人局踢光对手后被困 409)
- 陈旧房间记录兜底清理

UI 清理:移除"改名"按钮与"你的名字"输入框;删除房间按钮仅房主/管理员可见

docs: 新增 12-auth-kick-single-room.md 汇总本会话全部需求/根因/修复/边界测试

Co-Authored-By: Claude (CodeBuddy) <noreply@codebuddy.ai>
2026-08-25 14:13:59 +08:00

5.7 KiB
Raw Permalink Blame History

问题 2删除房间 / 房主心跳 / 房主退出自动关房 / 踢人

通用问题所有对局War / Mill / Holdem / DragTest都适用因为房间管理在 boardgame.io Lobby 层,与具体游戏无关。

现状boardgame.io 0.50.2 的能力边界)

  • Lobby REST 只有 create / join / leave / playAgain / update没有 delete / heartbeat / kick
  • leave:最后一个玩家离开时 db.wipe(matchID) 删房间(server.js:2331);否则仅清该玩家 name/credentials。
  • 没有在线检测 / 心跳server 不知道谁“还连着”。客户端断开 socket 不会自动 leave房间会一直挂着。
  • 房主定义:本项目约定 playerID '0'(创建者)即房主(holdem.tshost() 取最低编号 seated 且有筹码者,联机下等价于 P0

需求拆解

  1. 删除房间:房主有权删除;删除后所有客户端应被踢出 / 房间消失。
  2. 房主退出自动关房:房主无 heartbeat断开超过 10min房间自动解除。
  3. 心跳房主心跳周期长10min为支持“自动关房”需要一个心跳端点。
  4. 踢人:房主有权把某个玩家踢出房间。

拟采用方案(在 packages/server 加自定义 koa 路由)

利用 Server() 返回的 server.app / server.db / server.auth,在 index.ts 里挂一组 /api/rooms/... 路由。 房主鉴权:请求携带 playerID=0 + credentials(与 lobby 一致),用 server.auth.authenticateCredentials 校验。

2.1 内存在线表(心跳)

// 房间 -> { pid: lastSeenEpochMs }
const presence = new Map<string, Record<string, number>>();
const HOST_TIMEOUT_MS = 10 * 60 * 1000; // 房主 10min
const PLAYER_TIMEOUT_MS = 3 * 60 * 1000;
  • POST /api/rooms/:id/heartbeatbody { playerID, credentials },鉴权后 presence[matchID][playerID] = Date.now()。 所有玩家都建议定时 ping房主 10min 一次,普通玩家可更频繁),至少房主必须 ping。
  • 定时器server 启动时 setInterval,如每 60s扫描 presence 若房主(playerID '0' 或当前 host超时 → 执行“自动关房”(同删除逻辑)。

2.2 删除房间

POST /api/rooms/:id/delete   body: { playerID, credentials }

仅房主可删。逻辑:

const { metadata } = await server.db.fetch(matchID, { metadata: true });
// 校验 host 凭证
await server.db.wipe(matchID);
presence.delete(matchID);
// 通知所有 socket 客户端(通过 pubSub / socket.io房间已删

客户端在 LobbyRoomList 的房间列表轮询(已有 refresh)会自然发现房间消失; 另可在 boardgame.io 的 onUpdate/自定义事件里让已挂载的 Client 监听到“房间被删”并回退到列表。

2.3 踢人

POST /api/rooms/:id/kick   body: { playerID: hostId, credentials, target: targetPid }

仅房主可踢。逻辑:

// 1) lobby metadata 清掉 target 的 name/credentials
metadata.players[target].name = undefined;
metadata.players[target].credentials = undefined;
await server.db.setMetadata(matchID, metadata);
// 2) 在游戏状态 G 里把该座位 seated=false需发一个 game move 或走 master
//    简化:用 master 的 dispatch 调一个 engine move `kickMove(target)`
// 3) presence 清掉 target
  • 关键难点:踢人既要改 lobby metadata让座位空出来可被别人加入又要改游戏内 G让被踢者从牌桌消失。 改 G 需经 boardgame.io 的 Master dispatch不能直接改 db 状态,否则客户端状态漂移)。 方案:在每个游戏里加一个 kick move通用约定server 用 Master 触发它。 为减小改动,可定义一个通用插件/约定:所有游戏实现 kick(targetPid) move 删房间时也对所有人调用。

2.4 房主退出自动关房(合并到 2.1 定时器)

定时器发现房主超时 → 等同“删除房间”。

客户端改动ui / web

  • LobbyRoomList
    • 房主视角:每个房间 pill 增加“删除房间”按钮(调 delete 房间内当前玩家列表增加“踢”按钮(调 kick)。
    • 挂载后启动 setIntervalheartbeat(房主 10min普通玩家按 PLAYER_TIMEOUT 的一半)。
  • App.tsx / HoldemBoard:无需特殊处理,房间消失后列表刷新即可;可加“房间已被删除”提示。

风险与决策点

  • Master dispatch 改 G:需要拿到 server 的 master 实例。boardgame.io Server() 没直接暴露 master 但可通过 server.db + transport 重新构造,或复用 router 内部的 db备选(更简单):把“座位空出 / 踢人”完全放进 lobby metadata游戏内 playerView/move 读取 metadata 不行G 拿不到 metadata。因此必须经一个游戏内 move。 → 建议:每个游戏加 kick move空实现也行至少清 seatedserver 用 db + 自定义 dispatch。
  • 是否现在就给所有游戏加 kick move:会影响 War/Mill/DragTest。可先做 Holdem + 通用骨架,其余游戏加最小 kick
  • 房主判定:当前 host() 取“最低编号 seated 且有筹码者”,联机下创建者 = P0但若 P0 破产/离开host 会漂移。 建议把“房主”显式固化在 metadata(创建者 playerIDserver 端以 metadata 的 createdBy 为准engine host() 也改为读 G 里的 hostId(创建时写死)。

验证

  • 房主删除 → 列表消失、其他客户端刷新后无此房间、已挂载 Client 收到“已删除”。
  • 房主断网 10min测试用短超时→ 房间被自动 wipe。
  • 房主踢人 → 被踢者 metadata 清、G 里 seated=false、座位可被重新加入。
  • 非房主调 delete/kick → 403。