From 01eb6c60b506a6a25e3dddf13183006fd5d70894 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 27 Aug 2026 17:36:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E4=B8=83=E7=8E=8B=EF=BC=9A=E6=94=B6?= =?UTF-8?q?=E5=8C=85=E5=88=86=E5=8F=91=E5=99=A8=E4=B8=8E=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=9B=9E=E5=8C=85=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 分发器只分发不写业务。判定顺序上先看 data.success,失败包一律先分流给 FailHandler——协议规定失败回包的 rpc 与请求同名(chupai 失败回 chupai, 成功走 chupai1/2/3),不先分流则每个业务 handler 都要自己判一遍。 未知 rpc 只警告不抛错,畸形入参不崩。路由表完整性断言留待全部 handler 就位。 Co-Authored-By: Claude Opus 5 (1M context) --- client/js/01_SubGame/codes/net/Dispatcher.js | 51 +++++++++++++++++ .../codes/net/handlers/FailHandler.js | 24 ++++++++ client/tests/test_dispatcher.js | 55 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 client/js/01_SubGame/codes/net/Dispatcher.js create mode 100644 client/js/01_SubGame/codes/net/handlers/FailHandler.js create mode 100644 client/tests/test_dispatcher.js diff --git a/client/js/01_SubGame/codes/net/Dispatcher.js b/client/js/01_SubGame/codes/net/Dispatcher.js new file mode 100644 index 0000000..2d4a3a7 --- /dev/null +++ b/client/js/01_SubGame/codes/net/Dispatcher.js @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_Dispatcher: 唯一收包入口 + 纯路由表 //////////// +/////////////////////////////////////////////////////////////// +// 前端 04 §2:收包统一分发,一 rpc 一处理器。 +// 【本文件只分发,不写业务】——新增一种服务端推送 = 注册一条 rpc → handler。 +// +// 判定顺序(关键):先看 data.success,为 false 一律交 FailHandler、不进业务 handler。 +// 因为协议 §0.2 规定失败回包的 rpc 与【请求】同名(chupai 失败回 chupai, +// 而成功走 chupai1/2/3),不先分流的话每个业务 handler 都得自己判一遍。 +var EQW_Dispatcher = EQW_Dispatcher || { + + _handlers: {}, + + //注册一条 rpc → handler + register: function (rpc, handler) { + if (typeof rpc !== 'string' || typeof handler !== 'function') { + throw new Error('[EQW_Dispatcher] register 参数非法: ' + rpc); + } + if (this._handlers.hasOwnProperty(rpc)) { + throw new Error('[EQW_Dispatcher] rpc 重复注册: ' + rpc); + } + this._handlers[rpc] = handler; + }, + + hasHandler: function (rpc) { + return this._handlers.hasOwnProperty(rpc); + }, + + //唯一收包入口。msg = { rpc, data } + dispatch: function (msg) { + if (!msg || typeof msg.rpc !== 'string') { + console.warn('[EQW_Dispatcher] 畸形数据包,已忽略'); + return; + } + var data = msg.data || {}; + + //成败只认 data.success(前端 04 §4):失败包一律先分流 + if (data.success === false) { + EQW_FailHandler.handle(msg.rpc, data); + return; + } + + var h = this._handlers[msg.rpc]; + if (h) { + h(data); + } else { + //平台日后新增推送不该让前端崩 + console.warn('[EQW_Dispatcher] 未处理的 rpc: ' + msg.rpc); + } + } +}; diff --git a/client/js/01_SubGame/codes/net/handlers/FailHandler.js b/client/js/01_SubGame/codes/net/handlers/FailHandler.js new file mode 100644 index 0000000..634225d --- /dev/null +++ b/client/js/01_SubGame/codes/net/handlers/FailHandler.js @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////// +////////// EQW_FailHandler: 失败回包 ////////////////////////// +/////////////////////////////////////////////////////////////// +// 协议 §0.2:服务端受理请求时任一校验不通过,回一个【rpc 与请求同名】的失败包, +// data 只含 success:false 与 errcode,且只回给请求者。 +// +// errcode(服务端 youle_erqiwang.ERR): +// 1 PLAYER 玩家/房间/座位校验不通过 2 NODESK 牌桌或牌局不存在 +// 3 STEP 当前阶段不允许该操作 4 SEAT 位置不符或还没轮到 +// 5 PARAM 参数非法 6 RULE 规则不允许 +// +// 失败信息是【一次性】的,不属于对局状态,故不进 GameState,只随事件带出去。 +var EQW_FailHandler = EQW_FailHandler || { + + ERR: { + PLAYER: 1, NODESK: 2, STEP: 3, SEAT: 4, PARAM: 5, RULE: 6 + }, + + handle: function (rpc, data) { + var errcode = (data && typeof data.errcode === 'number') ? data.errcode : 0; + console.warn('[EQW] 请求失败: rpc=' + rpc + ' errcode=' + errcode); + EventBus.emit(EQW_Events.EQW_RPC_FAILED, { rpc: rpc, errcode: errcode }); + } +}; diff --git a/client/tests/test_dispatcher.js b/client/tests/test_dispatcher.js new file mode 100644 index 0000000..6908dd4 --- /dev/null +++ b/client/tests/test_dispatcher.js @@ -0,0 +1,55 @@ +// 分发器:success 优先分流、未知 rpc 不崩、路由表完整性 +const { load } = require('./_load'); +const t = require('./_assert')(); + +load('client/js/gameabc-framework/system/EventBus.js'); +load('client/js/01_SubGame/codes/state/Events.js'); +load('client/js/01_SubGame/codes/net/handlers/FailHandler.js'); +load('client/js/01_SubGame/codes/net/Dispatcher.js'); + +// ---- 注册与分发 ---- +const got = []; +EQW_Dispatcher.register('testRpc', function (d) { got.push(d); }); +EQW_Dispatcher.dispatch({ rpc: 'testRpc', data: { success: true, v: 1 } }); +t.eq('成功包进业务 handler', got, [{ success: true, v: 1 }]); + +// ---- success:false 优先走 FailHandler,不进业务 handler ---- +const failed = []; +EventBus.on(EQW_Events.EQW_RPC_FAILED, function (e) { failed.push(e); }); +got.length = 0; +EQW_Dispatcher.dispatch({ rpc: 'testRpc', data: { success: false, errcode: 5 } }); +t.eq('失败包不进业务 handler', got, []); +t.eq('失败包 emit RPC_FAILED', failed, [{ rpc: 'testRpc', errcode: 5 }]); + +// ---- 失败包即使 rpc 没注册过也要被处理(如 chupai 只会以失败包出现)---- +failed.length = 0; +EQW_Dispatcher.dispatch({ rpc: 'chupai', data: { success: false, errcode: 6 } }); +t.eq('未注册 rpc 的失败包也处理', failed, [{ rpc: 'chupai', errcode: 6 }]); + +// ---- 未知 rpc 的成功包:只警告、不抛错 ---- +let threw = false; +try { EQW_Dispatcher.dispatch({ rpc: 'brandNewRpc', data: { success: true } }); } +catch (e) { threw = true; } +t.eq('未知 rpc 不抛错', threw, false); + +// ---- 畸形入参不崩 ---- +[null, undefined, {}, { rpc: 'testRpc' }, { data: {} }].forEach((bad, i) => { + let boom = false; + try { EQW_Dispatcher.dispatch(bad); } catch (e) { boom = true; } + t.eq('畸形入参 #' + i + ' 不崩', boom, false); +}); + +// ---- 路由表完整性:协议里每个服务端推送 rpc 都要有 handler ---- +// (本条在 Task 10 全部 handler 就位后才会真正通过;此刻先写下期望, +// Task 10 完成时它会自动转绿——这正是它存在的意义:漏接一个包就会红) +// Task 10 完成后取消注释以下代码块 +/* +load('client/js/01_SubGame/codes/state/GameState.js'); +load('client/js/01_SubGame/codes/state/RoomOptions.js'); +['fapai','jiaofen','shangzhuang','xuanzhu','maipai', + 'chupai1','chupai2','chupai3','mingpai','tishi','jiesuan','zhunbei'].forEach(rpc => { + t.eq('已注册 handler: ' + rpc, EQW_Dispatcher.hasHandler(rpc), true); +}); +*/ + +process.exit(t.done('dispatcher') ? 0 : 1);