Files
erqiwang_youle/docs/client/development-guide/04-网络对接与启动编排.md
T
joywayerandClaude Opus 4.8 f776e4c817 文档修复:校正开发指南编号与失效交叉引用
- CLAUDE.md:前端指南编号由「01→05」订正为「01→06」(06 子游戏接入模式实存且已被本文件他处引用);删除已过期的 server/docs 旧副本警告(该旧副本已从工作树删除)。
- 两份 development-guide README + 各章节正文:将旧路径 server/docs/development-guide、client/docs/development-guide 统一订正为 docs/server、docs/client;docs/engineering 订正为 docs/games/engineering。
- 客户端 README 阅读顺序补齐缺失的 06 篇。
- 服务端 README 移除指向不存在的 docs/important/server 的条目。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 22:39:37 +08:00

172 lines
7.7 KiB
Markdown
Raw 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.
# 04 · 网络对接与启动编排
本篇讲**前端怎么和服务端打通、一局怎么启动**:发包链路、收包分发、新旧架构的对接边界、成败判定、启动顺序与 controllers/managers 的职责。
> **想看前后端端到端全链路**(一个包从前端出发 → 服务端三层路由/handler → 推回前端分发的完整往返、及各步在谁的哪个文件)见服务端 [`docs/server/development-guide/03 §6`](../../server/development-guide/03-数据收发与通信协议.md#6-端到端收发全链路前后端对照)。本篇聚焦**前端这一侧**的收发细节。
---
## 1. 发包:语义化发包封装 → RpcHelper
前端发起操作时**不直接拼包**,走两层封装:
```
业务/控制器
└─ 语义化发包封装(子游戏:一个操作一个语义化方法)
└─ RpcHelper(框架基座:自动注入平台字段,调引擎发送)
└─ Utl.sendData(app, route, rpc, data)
```
### RpcHelper(自动注入平台字段)
`RpcHelper` 把每个请求自动补齐平台必需字段,业务只传业务数据:
```js
// 自动注入:agentid / gameid / playerid / roomcode / seat ...
RpcHelper.sendGameRpc(rpc, gameData, options); // route = "room"/子游戏路由
```
### 语义化发包封装(子游戏)
子游戏为每个操作暴露一个语义化发包方法,内部补 `seat/roomcode` 等业务字段后经 `RpcHelper` 发送;业务侧只调这些语义化方法,不关心平台字段。
**DO**:发包一律走语义化发包封装 / `RpcHelper`。**DON'T**:在业务里直接 `Utl.sendData` 手拼包、漏注入平台字段。
> 平台字段缺失(如 `playerid` 为 0/空)应 fail-fast 暴露,不静默发出残缺包。
---
## 2. 收包:统一分发
服务端的主动推送统一格式 `{ rpc, data }`,前端有**唯一收包入口**,再按 `rpc` 分发到对应处理器。收包分发器是**纯路由表 + 分发器**:
```js
// 纯路由表 + 分发器:一 rpc 一处理器
var _handlers = {
'someRpc': function (d) { return someHandler.handleSomeRpc(d); },
'anotherRpc': function (d) { return anotherHandler.handleAnother(d); },
'error': function (d) { console.error('[dispatch]', d.message); }
// ...每个 rpc 一条
};
function dispatch(msg) { // msg = { rpc, data }
var h = _handlers[msg.rpc];
if (h) { h(msg.data); }
else { console.warn('[dispatch] 未处理的 rpc:', msg.rpc); }
}
```
**新增一种服务端推送** = 在路由表加一条 `rpc → 处理器` + 在对应处理器里写业务。**收包分发器只分发,不写业务逻辑。**
---
## 3. 新旧架构对接边界
平台事件先进旧的受限入口 `Game_Modify.*`,旧入口**只解包、只转交**,把数据委托给新架构:
| 平台入口(受限文件,尽量不改) | 转交到(新架构) | 作用 |
|--------------------------------|------------------|------|
| `Game_Modify.appStart()` | 启动编排 | 启动初始化 |
| `Game_Modify.StartWar(_msg)` | 开局处理 | 开局 |
| `Game_Modify._ReceiveData(_msg)` | 收包分发 | 对局推送分发 |
| `Game_Modify.Reconnect(info)` | 重连处理 | 断线重连/重画 |
### 开局解包要点(StartWar)
服务端 `makewar` 常用**差异化下发**(`sendtype:1` + `seatlist[]`)。`StartWar` 先按本座位取出自己的数据,再交给新架构的开局处理:
```js
Game_Modify.StartWar = function (_msg) {
var raw = _msg.data.deskwar || _msg.data;
var gameData = raw;
if (raw && raw.sendtype === 1 && Array.isArray(raw.seatlist)) {
for (var i = 0; i < raw.seatlist.length; i++) {
if (raw.seatlist[i].seat === C_Player.seat) { gameData = raw.seatlist[i].data; break; }
}
}
// 委托新架构的开局处理,受限入口本身不写业务
};
```
### 重连(Reconnect)= 重画
重连入口拿到服务端 `get_deskinfo` 的完整快照,交由新架构的重连处理**据本地数据重建整个界面**。重连与“切 app 重画”应复用同一条重画路径——**任何时候执行重画函数都能还原正确界面**(如网页刷新)。这与「数据优先、表现延后」一脉相承:界面永远能从数据无歧义地重建。
**红线**:受限文件 `Game_Modify.*` 里**只接不写**,业务逻辑全部在新架构 handler 中。
---
## 4. 成败判定:只认 `data.success`
> 与服务端 [`docs/server/development-guide/03`](../../server/development-guide/03-数据收发与通信协议.md) 同一条协议。
前端 RPC 没有“同步返回”,操作结果由服务端**后续主动推送**告知。判成败的唯一权威是推送 `data` 里的 **`success`**:
```js
// 处理器统一写法
function handleXxx(data) {
if (!data || !data.success) { /* 失败处理 */ return; }
// 成功逻辑
}
```
- **只认 `data.success`**,不用 `status`/`code` 判成败,不写 `status` 兼容兜底。
- 个别综合推送的 success 在**嵌套字段**(如 `gameSettleComplete` 的 `data.gameSettle.success`)——按该推送的契约取对应位置,仍以 `success` 语义为准,不改用 status。
---
## 5. 启动编排
一局前端的启动由一段**启动编排**一次性完成(经 `Game_Modify.appStart` 触发):
```
启动编排
1) 检查依赖 检查 EventBus/SpriteManager/UIManager/各 View 就绪
2) 初始化系统 SpriteManager.init / AnimationManager.init / UIManager.init
3) 注册视图 创建并注册各 View 到 UIManager
4) 标记初始化完成
```
启动后即等待平台事件:`StartWar` 开局、`_ReceiveData` 推送、`Reconnect` 重连,分别转交新架构。
---
## 6. controllers 与 managers 职责
| 类别 | 角色 | 典型成员 |
|------|------|----------|
| **controllers**(处理器) | 接收网络消息 → 改数据/UI → 必要时 `emit` 事件 | 按消息类型分工的处理器(流程、操作、结算、特殊规则等) |
| **managers**(资源/状态) | 管资源与跨模块能力 | 音频、Spine 回调分发、玩法标记、特效管理等 |
> 精灵交互事件**不属于**子游戏 controllers:它由框架 `gameabc-framework/system/SpriteEventController.js` 统一分发。子游戏在组件 `init` 里用 `SpriteEventController.registerMouseDown/registerMouseUp/registerMouseMove/...` 注册按精灵 ID 的回调;需对每个绘制精灵统一处理的能力用 `registerGlobalDraw` 注册全局 draw 钩子。平台入口 `Game_Modify.utlmousedown/mouseup/...` 只把事件转调给它。
调用链示例:
```
平台 _ReceiveData(_msg)
→ 收包分发(_msg)
→ 操作处理器(data)
├ 改数据模型
├ 播动画(经游戏动画封装)
├ 动画回调里刷新静态界面
└ 播音效(经游戏音频管理)
```
**红线**:业务逻辑放 controllers/managers,**不**写进受限的 `Game_Modify.*`;处理器只处理自己那类消息,需要别的能力调对应 manager,不重造。
---
## 7. 本篇 DO / DON'T
| DO ✅ | DON'T ❌ |
|------|---------|
| 发包走语义化发包封装/`RpcHelper` | 业务里直接 `Utl.sendData` 手拼包 |
| 收包统一进收包分发器,一 rpc 一处理器 | 在 `Game_Modify.*` 或散点里直接处理推送 |
| 受限入口只解包转交,业务在新架构 handler | 在受限文件堆业务逻辑 |
| 成败只认 `data.success` | 用 `status`/`code` 判成败、写 status 兜底 |
| 重连/重画复用同一路径,据数据重建界面 | 重连单写一套与正常对局不同的渲染 |
| 业务在 controllers/managers,各司其职 | 处理器交叉重造别人的能力 |
下一篇 [05-开发规范与红线](./05-开发规范与红线.md) 汇总前端所有工程纪律。
</content>