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

94 lines
5.7 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.
# 问题 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.ts``host()` 取最低编号 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 内存在线表(心跳)
```ts
// 房间 -> { 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/heartbeat`body `{ 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 }
```
仅房主可删。逻辑:
```ts
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 }
```
仅房主可踢。逻辑:
```ts
// 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`)。
- 挂载后启动 `setInterval``heartbeat`(房主 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空实现也行至少清 `seated`server 用 `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。