问题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>
58 lines
2.7 KiB
Markdown
58 lines
2.7 KiB
Markdown
# 问题 1:改名后牌桌 UI 不刷新
|
||
|
||
## 现象
|
||
点击“改名”后,**房间列表 pill 上的名字更新了**,但进入牌桌后,座位上的名字(`HoldemBoard`)**没有任何变化**。
|
||
|
||
## 根因(已定位)
|
||
存在**两个相互独立的名字来源**,改名只更新了其中一个:
|
||
|
||
1. **Lobby 名字**:`LobbyRoomList.renameMe()` → `lobby.updatePlayer(...)` →
|
||
boardgame.io `/update` 路由只改 `metadata.players[playerID].name`
|
||
(见 `boardgame.io/dist/cjs/server.js:2428`:`metadata.players[playerID].name = newName;`)。
|
||
房间列表(pill)读的就是这个,所以刷新后变了。
|
||
2. **游戏内名字**:牌桌 `HoldemBoard` 显示的是 `G.players[pid].name`,由
|
||
`sitDownMove`(`holdem.ts:363`)在入座时写入一次,**之后再也没更新过**。
|
||
|
||
因此房间列表刷新(来源 1 变了)但牌桌(来源 2 没变)出现不一致。
|
||
|
||
## 拟采用方案
|
||
让游戏内的名字成为**单一权威来源**,并把改名事件传导进游戏状态:
|
||
|
||
1. **engine(`holdem.ts`)**:新增 `renameMove({ G, ctx, playerID }, newName?)`:
|
||
```ts
|
||
export function renameMove({ G, ctx, playerID }, rawName?: string) {
|
||
const me = playerID ?? ctx.currentPlayer;
|
||
const p = G.players[me];
|
||
if (!p || !p.seated) return;
|
||
const nm = (rawName ?? '').trim().slice(0, 20);
|
||
if (!nm) return;
|
||
p.name = nm;
|
||
pushLog(G, `${nm} 改名`);
|
||
}
|
||
```
|
||
沿用现有 `sitDownMove` 的信任模型(直接用 `playerID`,局域网休闲场景可接受)。
|
||
在 `moves` 里注册 `rename: { move: renameMove, client: false }`。
|
||
|
||
2. **ui(`HoldemBoard.tsx`)**:新增 effect——当 `playerName` prop 变化且该座位已入座时,
|
||
自动 `onMove('rename', [playerName])`:
|
||
```ts
|
||
const renamedRef = useRef<string | null>(null);
|
||
useEffect(() => {
|
||
if (playerName && renamedRef.current !== playerName && G.players[me]?.seated) {
|
||
renamedRef.current = playerName;
|
||
onMove('rename', [playerName]);
|
||
}
|
||
}, [playerName, G.players, me, onMove]);
|
||
```
|
||
|
||
3. **ui(`App.tsx` 已有链路)**:`LobbyRoomList.renameMe` 成功后调 `onRename(newName)`
|
||
→ 已写入 `App.playerName`(`App.tsx:225` 的 `onRename`)→ 作为 `playerName` prop 透传给
|
||
`HoldemBoard`(`App.tsx:609`)→ 触发上面的 effect → 改掉 `G.players[me].name`。
|
||
|
||
这样改名在“房间列表”和“牌桌”两处都会刷新,且只依赖已有的 prop 链路,无需重构 Client 实例。
|
||
|
||
## 验证
|
||
- 联机:改名 → 牌桌 `holdem-seat-{pid}` 的 `p.name` 在下一帧变为新名。
|
||
- 单测:直接调 `renameMove` 断言 `G.players[me].name` 更新。
|
||
- 边界:空名 / 超过 20 字被截断;未入座不生效。
|