Files
huajishe-tts/docs/dev-log/12-unified-port-and-player-names.md
e2hang c36d2aeae7 feat(server,web): unified port (/api prefix) + player naming system
方案1 统一端口:
- vite proxy: /api 和 /socket.io 转发到内部 :8000(不 rewrite)
- server: mountApiPrefix 把 boardgame.io router 挂到 /api 前缀
- server: 用原生 fs 实现静态文件 serve(SPA fallback),生产单进程
- 客户端 createLobbyClient('') 走同源 /api,CORS 完全消失

方案2 命名系统:
- App header 加"你的名字"输入框,localStorage 持久化
- LobbyRoomList 显示所有玩家名字("Alice · Bob")替代 "2/2"
- 房间内"改名"按钮调 POST /api/games/:name/:id/update

修复:
- LobbyClient 用 fetch 包装替代继承(原 request 是 TS private)
- mountApiPrefix 抽到独立文件(避免 test import index.ts 触发 run(8000))
- vite proxy 不 rewrite(避免和 server static serve 冲突)

Tests: pnpm ts 6/6, engine 34 + server 9, E2E 4/4.
Docs: dev-log 12 + lessons §11 + decisions D27-D29.
2026-08-24 12:17:44 +08:00

155 lines
5.8 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.
# 12 · 统一端口 + 玩家命名系统
## 做什么
用户要求两个改造(先给方案审核,通过后实施):
1. **统一端口**:不映射两个端口(之前 5173 页面 + 8000 API改成页面和 API 都在同一个端口,如页面 `:5173`、API `:5173/api`
2. **命名系统**:战局内可以修改自己的名字(大厅显示所有玩家名字)。
用户还问了一个关键问题:**加新游戏(如德州扑克)是不是有 Lua 之类的脚本语言让玩家直接写?** 答案记录在本文末尾。
## 方案(审核通过)
### 方案 1统一端口
**开发环境**vite proxy 把 `/api``/socket.io` 转发到内部 `:8000`
**生产环境**server 直接 serve 静态文件 + mount `/api` 路由,单一进程。
```
浏览器 ──> :5173 (vite / server static)
├── / → index.html (SPA)
├── /api/* → boardgame.io lobby REST
└── /socket.io/* → WebSocket
```
### 方案 2命名系统
- `joinMatch` 时传 `playerName`
- 大厅显示所有玩家名字(替代 `2/2` 这种数字)
- 游戏内改名字调 `POST /api/games/:name/:id/update` `{newName}`
- 名字持久化到 localStorage
## 遇到什么问题
### 问题 1`koa-send` 装包被拒(权限)
**修法**:用 Node 原生 `fs/promises``readFile` + `stat` 自己实现静态文件 serve约 40 行),不依赖第三方包。
### 问题 2boardgame.io 的 `LobbyClient.request` 是 TS private无法继承加前缀
**修法**:放弃继承,直接写一个 fetch 包装(`packages/engine/src/lobby-client.ts`),行为兼容 `LobbyClient` 但请求路径自动加 `/api` 前缀。
### 问题 3`ctx.URL` 是只读 getter临时改 path 时报错
**修法**:只改 `ctx.path`Koa 里这是 getter/setter 可写),不改 `ctx.URL`
### 问题 4服务端测试 import index.ts 会触发 `server.run(8000)` 导致 EADDRINUSE
**修法**:把 `mountApiPrefix` 抽到独立文件 `packages/server/src/mount-api-prefix.ts`index.ts 和 test 都从那里 import。
### 问题 5vite proxy rewrite 和 server static serve 冲突
最初 vite proxy 把 `/api` 剥掉转发到 `:8000/games`,但 server 端 static serve 拦截了 `/games`(返回 index.html导致 `/api/games` 返回 HTML。
**修法**vite proxy **不 rewrite**,保留 `/api` 转发到 `:8000/api/games`,由 server 端的 `mountApiPrefix` 剥掉 `/api` 转给 boardgame.io router。
### 问题 6`pnpm dev` 在错误目录执行
之前 `cd``packages/server` 后跑 `pnpm dev` 启动的是 servertsx watch导致 EADDRINUSE。**教训**:跑命令前明确 `cd apps/web`
## 怎么解决(具体做法)
### 1. vite proxyapps/web/vite.config.ts
```ts
server: {
port: 5173,
strictPort: true,
host: '0.0.0.0',
proxy: {
'/api': { target: 'http://localhost:8000', changeOrigin: true }, // 不 rewrite
'/socket.io': { target: 'http://localhost:8000', ws: true, changeOrigin: true },
},
},
```
### 2. server /api 挂载packages/server/src/mount-api-prefix.ts
```ts
export function mountApiPrefix(app, router) {
app.use(async (ctx, next) => {
if (!ctx.path.startsWith('/api/')) return next();
const original = ctx.path;
ctx.path = ctx.path.replace(/^\/api/, '') || '/';
try {
await router.routes()(ctx, async () => {});
await router.allowedMethods()(ctx, async () => {});
} finally {
ctx.path = original;
}
});
}
```
### 3. server 静态文件 servepackages/server/src/index.ts
`fs/promises` 实现,非 `/api``/socket.io``/healthz` 的请求返回 `apps/web/dist/index.html`SPA fallback
### 4. 客户端 LobbyClientpackages/engine/src/lobby-client.ts
```ts
export function createLobbyClient(serverUrl: string, apiPrefix: string = '/api') {
// fetch 包装,请求路径自动加 /api 前缀
// createLobbyClient('') → 同源 + /api默认
}
```
### 5. 命名系统App.tsx + LobbyRoomList.tsx
- App header 加"你的名字"输入框localStorage 持久化
- `LobbyRoomList` 显示 `formatPlayers()`"Alice · Bob"
- 房间内"改名"按钮调 `updatePlayer`
## 成果
- Commit: 见 git log
- 测试:
- `pnpm ts` 6/6
- `pnpm -r test` engine 34 + server 9
- `pnpm test:e2e` 4/4
## 用户验证路径
```bash
# 本机 + 局域网另一台
# 都开 http://192.168.5.11:5173/(同一个端口!)
# 1. 输入名字 → 创建房间 → 顶部显示"在房间 X 作为 P0"
# 2. 另一台输入名字 → 刷新列表 → 看到 "Alice" → 加入 P1
# 3. 大厅显示 "Alice · Bob"
# 4. 点"改名" → 输入新名字 → 大厅同步
```
## 关于加新游戏(用户问的问题)
**当前系统没有 Lua/脚本语言层。** 所有游戏都用 TypeScript 写(`packages/engine/src/games/*.ts`),编译进 server 进程,运行期不能替换。
加德州扑克需要(不是"调一下 Lua 文件"
1.`texas-holdem.ts`reducer + 5 个 phase + 8 个 move + 隐藏信息,约 400 行 TS
2.`TexasHoldemBoard.tsx`(手牌 + 公共牌 + 筹码 + 行动按钮,约 500 行 TSX
3. 手牌评估库10 种牌型 + kicker约 200 行)
4. 隐藏信息 plugin每人只看自己手牌
5. 注册到 server + web
6. 预估 3-4 周
如果要支持"玩家自己写游戏",需要单独做 **脚本化层**Lua/JS 沙箱 + game-DSL + 热加载),那是另一个 1-2 个月的产品方向。详见 `docs/impl.md` Phase 3。
## 关联
- [11-user-test-feedback-round-1.md](./11-user-test-feedback-round-1.md) — 上一轮用户反馈
- [10-cors-and-join-bugs.md](./10-cors-and-join-bugs.md) — CORS 修复(统一端口后不再需要)
- lessons-learned §12.1-12.6 — 本轮的坑
- decisions D27-D28 — 统一端口、命名系统