新规hook

This commit is contained in:
2026-07-06 17:16:05 +08:00
parent 363f97cdbe
commit 047561675b
28 changed files with 510 additions and 1345 deletions
@@ -27,26 +27,25 @@
`route` 决定包被投到**哪个模块**,是前后端必须对齐的字符串。它的权威定义与匹配链如下(均可在代码验证):
- **服务端:`routename` 的唯一定义点**是 `mod.js` 里创建模块的 `cls_mod.new(模块名, 路由名, 所属应用)` 的**第二个参数**:
- **服务端:`routename` 的唯一定义点**是 `mod.js` 里 `cls_mod.new(模块名, 路由名, 所属应用)` 的**第二个参数**:
```js
// server/games2/<你的游戏>/mod.js
// server/games2/<你的游戏>/mod.js —— 第二参 "<你的游戏>" 即 routename(route 要用的值)
var mod_<你的游戏> = global.mod_<你的游戏>
|| cls_mod.new("mod_<你的游戏>", "<你的游戏>", youle_app);
// ▲ 第一参 modname ▲ 第二参 routename(就是 route 要用的值)
```
`cls_mod.new` 把第二参存进 `mod.routename`,并把模块 `push` 进 `app.modlist`(`server/class/class.mod.js`)。
- **它是你自定义的字符串**:由你起名,只要**在应用内唯一**即可;与目录名、与模块名 `modname`(第一参,用于全局暴露 `global[modname]`、`app[modname]`)都**无强制绑定**——本项目三者恰好都叫 `jinxianmahjong` 只是约定(见 02 §2.1、01 §5)。
- **平台按它匹配模块**:收包时 `cls_app.ReceivePack` 用 `pack.route == modlist[i].routename` 找到模块,再 `DoPack` 进第三层按 `rpc` 调方法(`server/class/class.app.js`,见 01 §4)。
- **前端发包的 `route` 必须与它逐字一致**:前端把该值固化为常量(本项目 `codes/game/network/RpcSender.js` 里 `var ROUTE_NAME = 'jinxianmahjong'`,经 `Utl.sendData(app, route, rpc, data)` 发出)。**两端字符串不一致 → 平台匹配不到模块,包被静默丢弃**(前端也收不到任何响应)。
- **与平台房间模块区分**:平台自带的房间模块 `routename` 是 `"room"`,处理创建/加入/开战(`createRoom`、`self_join_room`、`self_makewar` 等);**你的子游戏自定义 RPC**(`playCard` 等)走**你自己的 `routename`**。两者不要混用:平台流程发 `route:"room"`,玩法操作发 `route:"<你的游戏>"`。
- **与平台房间模块区分**:平台自带房间模块 `routename` 是 `"room"`,处理创建/加入/开战(`createRoom`、`self_join_room`、`self_makewar` 等);**你的子游戏自定义 RPC**(`playCard` 等)走**你自己的 `routename`**。两者不要混用:平台流程发 `route:"room"`,玩法操作发 `route:"<你的游戏>"`。
> 一句话:**`routename` 在服务端 `cls_mod.new` 第二参定义(自定、应用内唯一),前端发包 `route` 必须与它逐字相同,平台据此把包投到你的模块**;改名要两端同步改。
### 1.2 发包必须自带前端界面所需的全部核心数据
**前端以服务端数据为权威——只做界面展示与交互、不做权威计算**(见 [`client 05`](../../client/development-guide/05-开发规范与红线.md))。推论落到服务端:**每个下发包都必须携带前端渲染/刷新该界面所需的全部核心数据**,让前端「据包写入数据 → 直接刷新出界面」,而**不需要前端自行推算、补全或兜底**。
**前端以服务端数据为权威——只做界面展示与交互、不做权威计算**(见 [前端 05 开发规范与红线](../../client/development-guide/05-开发规范与红线.md))。推论落到服务端:**每个下发包都必须携带前端渲染/刷新该界面所需的全部核心数据**,让前端「据包写入数据 → 直接刷新出界面」,而**不需要前端自行推算、补全或兜底**。
- **界面要用的字段都要发全**:凡界面要显示、或前端 set/refresh 要用到的核心字段(出了什么牌、轮到谁、各家剩余张数、手牌/副露、分数/比分、倒计时锚点、庄家/座位、各类状态标志……)都要放进 `data`,不能让前端"猜"或本地推算权威结果。
- **漏发是服务端的缺陷,不许前端补**:前端遵循数据权威原则——权威字段缺失应显式报错/留空、不用 `|| 0`/`|| []` 兜造(见 client 05)。所以服务端漏发 = 前端界面缺数据,**修在服务端发包处,不在前端补洞**。
@@ -65,9 +64,7 @@
```js
XxxHandler.handlePlayCard = function(pack) { // 由 mod_<游戏>.playCard 委托进来
try {
// 1) 提取并校验参数:列出必填字段 + 数值型字段的类型要求
// (本项目用统一工具 ValidationHelper.extractAndValidateParams 做这件事)
// 等价于:agentid/playerid/gameid/roomcode/seat 必填,playerid/roomcode/seat 需为数值
// 1) 提取并校验参数:必填字段 + 数值型字段类型(本项目用 ValidationHelper.extractAndValidateParams)
var params = extractAndValidateParams(
pack,
['agentid', 'playerid', 'gameid', 'roomcode', 'seat', 'cardUniqueId'],
@@ -85,15 +82,10 @@ XxxHandler.handlePlayCard = function(pack) { // 由 mod_<游戏>.playCard 委
var o_desk = o_room.o_desk;
if (!o_desk) return { success: false, error: '游戏桌不存在' };
// 4) 调试记录(若框架提供)——便于复盘
if (o_desk.debug && o_desk.debug.save_receivepack) {
o_desk.debug.save_receivepack(pack, p.seat, p.playerid);
}
// 4) 调试记录(若框架提供 o_desk.debug.save_receivepack)——便于复盘
// 5) 业务校验 + 执行业务:委托权威模块(handler 不内联规则,见 04)
// 如:OperationExecutor.executePlayCard(o_room, {...})
// 6) 构建响应 + 【主动推送】:ResponseBuilder 组包 → BroadcastManager 逐座位 sendpack_toseat
// 推送 data 自带 success(见 §5);return 不是下发通道(见 §4)
// 6) 构建响应 + 【主动推送】:组包 → 逐座位 sendpack_toseat;推送 data 自带 success(见 §5);
// return 不是下发通道(见 §4)
} catch (e) { /* 记录日志;必要时给该座推送失败包 */ }
};
```
@@ -126,11 +118,8 @@ for (var seat = 0; seat < o_room.seatlist.length; seat++) {
app: "youle", route: "<游戏>", rpc: "playCard",
data: deepCopy(baseData) // 公共信息
};
if (seat === actionSeat) {
msg.data.handCards = hands[seat]; // 仅本人可见手牌
} else {
msg.data.handCards = []; // 他人看不到
}
// 敏感信息只发本人:本人给真实手牌,他人给 []
msg.data.handCards = (seat === actionSeat) ? hands[seat] : [];
o_room.method.sendpack_toseat(msg, seat);
}
```
@@ -163,23 +152,13 @@ for (var seat = 0; seat < o_room.seatlist.length; seat++) {
### 正反例
```js
// ❌ 错误:推送只带 status,前端读 data.success 恒 undefined → 误判失败
o_room.method.sendpack_toseat({
app:"youle", route:"<游戏>", rpc:"setHostingState",
data: { status: 200, hosting: true } // 少了 success
}, seat);
// ❌ 推送只带 status、少了 success → 前端读 data.success 恒 undefined,误判失败
data: { status: 200, hosting: true }
// ✅ 成败语义放 success,status 仅作细分
data: { success: true, status: 200, hosting: true }
// ✅ 正确:成败语义放 success,status 仅作细分
o_room.method.sendpack_toseat({
app:"youle", route:"<游戏>", rpc:"setHostingState",
data: { success: true, status: 200, hosting: true }
}, seat);
```
```js
// 前端:只认 success
// 前端:只认 success,status/code 仅用于展示或日志
if (!data.success) { /* 失败处理 */ return; }
// data.status / data.code 仅用于展示或日志细分
```
> 典型事故:托管状态推送 `{ status: 200, ... }` 不带 `success`,前端读 `data.success` 恒为 `undefined`,于是"已进入托管却报失败"。根因就是违反了本协议第 3 条。
@@ -253,7 +232,7 @@ if (!data.success) { /* 失败处理 */ return; }
| 重连/中途加入 | `export.get_deskinfo` 的返回 | `Game_Modify.Reconnect(_deskinfo)` → 重画 |
| 对局/推送 | RPC handler 或主动推送(按 `rpc`) | 收包分发表里同名 `rpc` 的处理器 |
**改服务端下发结构 = 同步核对前端对应 `rpc` 的解析**;**新增一种推送 = 服务端选定 `rpc` + 前端在分发表加同名处理器**。任一端单方面改,另一端必按旧结构解析出错。前端侧的收发细节以 [`docs/client/development-guide/04-网络对接与启动编排`](../../client/development-guide/04-网络对接与启动编排.md) 为权威。
**改服务端下发结构 = 同步核对前端对应 `rpc` 的解析**;**新增一种推送 = 服务端选定 `rpc` + 前端在分发表加同名处理器**。任一端单方面改,另一端必按旧结构解析出错。前端侧的收发细节以 [前端 04 网络对接与启动编排](../../client/development-guide/04-网络对接与启动编排.md) 为权威。
---
@@ -274,4 +253,3 @@ if (!data.success) { /* 失败处理 */ return; }
- 改下发结构必同步核对前端 `StartWar`/`Reconnect`/对应 `rpc` 解析。
下一篇 [04-开发规范与红线](./04-开发规范与红线.md) 汇总所有必须遵守的工程纪律。
</content>