diff --git a/cocoscreator_projects/YouleNexus/assets/framework/protocol/routes.ts b/cocoscreator_projects/YouleNexus/assets/framework/protocol/routes.ts index bb9452a..e91c9b0 100644 --- a/cocoscreator_projects/YouleNexus/assets/framework/protocol/routes.ts +++ b/cocoscreator_projects/YouleNexus/assets/framework/protocol/routes.ts @@ -1,9 +1,133 @@ import { Route } from '../core/constants.ts'; export { Route }; -/** 平台层会用到的 rpc 名(最小集)。完整清单见 docs/protocol/02、03。 */ + +/** + * 协议层 rpc 名(与 docs/protocol/02、03 及 projects/Game_Surface_3/js/00_Surface/02_Const.js 逐字对齐)。 + * + * 字面量联合类型:业务侧发包(NetClient.send)/收包 handler(platformHandlers + roomHandlers + sdk onReceive)入参必须在此集合内, + * 拼错编译期就发现;未注册的 rpc 运行时显式抛错(第二准则:不静默兜底)。 + */ export const Rpc = { + // ---- 登录与连接 ---- player_login: 'player_login', kick_server: 'kick_server', connect_roomserver: 'connect_roomserver', connect_agentserver: 'connect_agentserver', + agentserver_game: 'agentserver_game', + + // ---- 房间生命周期(room route)---- + create_room: 'create_room', + self_join_room: 'self_join_room', + other_join_room: 'other_join_room', + self_exit_room: 'self_exit_room', + other_exit_room: 'other_exit_room', + self_break_room: 'self_break_room', + other_break_room: 'other_break_room', + player_prepare: 'player_prepare', + change_seat: 'change_seat', + change_room: 'change_room', + switchRoomList: 'switchRoomList', + + // ---- 对局(room route)---- + self_makewar: 'self_makewar', + other_makewar: 'other_makewar', + over_game: 'over_game', + get_player_grade1: 'get_player_grade1', + get_player_grade2: 'get_player_grade2', + send_gift: 'send_gift', + other_send_gift: 'other_send_gift', + beanroom_surrender: 'beanroom_surrender', + send_phiz: 'send_phiz', + broadcast: 'broadcast', + other_offline: 'other_offline', + other_online: 'other_online', + + // ---- 解散投票(room route)---- + self_apply_free_room: 'self_apply_free_room', + other_apply_free_room: 'other_apply_free_room', + self_agree_free_room: 'self_agree_free_room', + other_agree_free_room: 'other_agree_free_room', + self_refuse_free_room: 'self_refuse_free_room', + other_refuse_free_room: 'other_refuse_free_room', + free_room: 'free_room', + + // ---- 聊天 / 语音 / 电话(room route)---- + send_voice: 'send_voice', + play_voice: 'play_voice', + send_text: 'send_text', + receive_chat: 'receive_chat', + call_phone: 'call_phone', + other_callphone: 'other_callphone', + hangup_phone: 'hangup_phone', + other_hangup: 'other_hangup', + kick_offline: 'kick_offline', + + // ---- 资产 / 经济(agent/room route)---- + update_roomcard: 'update_roomcard', + update_bean: 'update_bean', + update_charm: 'update_charm', + giveCoin: 'giveCoin', + change_star: 'change_star', + topup_card: 'topup_card', + get_paylist: 'get_paylist', + pay_succ: 'pay_succ', + get_treasurelist: 'get_treasurelist', + + // ---- 任务(agent route)---- + get_player_task: 'get_player_task', + player_finish_task: 'player_finish_task', + get_task_award: 'get_task_award', + can_award: 'can_award', + + // ---- 社交 / 邀请 / 分享(agent route)---- + binding_invitecode: 'binding_invitecode', + get_player_invitecode: 'get_player_invitecode', + share_room: 'share_room', + get_share_room: 'get_share_room', + quick_enter_share_room: 'quick_enter_share_room', + + // ---- 房间列表 / 高级(agent route)---- + advanced_roomlist: 'advanced_roomlist', + advanced_createroom: 'advanced_createroom', + query_player2: 'query_player2', + + // ---- 银行 / 资料(agent route)---- + set_bankpwd: 'set_bankpwd', + + // ---- 手机绑定 / 验证码(agent route)---- + binding_phone: 'binding_phone', + send_phone_checkcode: 'send_phone_checkcode', + send_phone_code_wechat: 'send_phone_code_wechat', + submit_phoneinfo: 'submit_phoneinfo', + + // ---- 位置 / 反馈(agent route)---- + submit_location: 'submit_location', + submit_opinion: 'submit_opinion', + + // ---- 签名 / 短码房间(agent route)---- + setSign: 'setSign', + getInfoByShortCode: 'getInfoByShortCode', + optBanList: 'optBanList', + getShortCodeRankList: 'getShortCodeRankList', + + // ---- VIP / 黑名单 / 白名单(agent route)---- + setAllCharm: 'setAllCharm', + getVipRankList: 'getVipRankList', + playerBehavior: 'playerBehavior', + setVipForbidSelect: 'setVipForbidSelect', + getPlayerWhiteList: 'getPlayerWhiteList', + optWhiteList: 'optWhiteList', + + // ---- H5 专用(agent route,由 joinRoom 入口直达)---- + joinRoom: 'joinRoom', } as const; + +/** 全部 rpc 名的字面量联合类型(编译期校验入参) */ +export type RpcName = (typeof Rpc)[keyof typeof Rpc]; + +/** Route + Rpc 二元组的契约(用于 typed send / typed receive) */ +export interface Envelope { + route: typeof Route[keyof typeof Route]; + rpc: RpcName; + data: T; +} \ No newline at end of file diff --git a/cocoscreator_projects/framework-tests/protocol/routes.test.ts b/cocoscreator_projects/framework-tests/protocol/routes.test.ts new file mode 100644 index 0000000..e388b1b --- /dev/null +++ b/cocoscreator_projects/framework-tests/protocol/routes.test.ts @@ -0,0 +1,48 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { Rpc, type RpcName } from '../../YouleNexus/assets/framework/protocol/routes.ts'; + +test('Rpc 表: 每个值等于 key(自映射契约,避免错拼)', () => { + // 每个 Rpc.X 必须 === 'X'(自映射契约;改任一即不一致) + for (const [k, v] of Object.entries(Rpc)) { + assert.equal(v, k, `Rpc.${k} 应等于 "${k}",实际 "${v}"`); + } +}); + +test('Rpc 表: 至少覆盖核心业务场景', () => { + const required: RpcName[] = [ + 'player_login', 'kick_server', 'connect_roomserver', 'connect_agentserver', + 'create_room', 'self_join_room', 'other_join_room', + 'self_exit_room', 'other_exit_room', 'player_prepare', 'change_seat', + 'self_makewar', 'other_makewar', + 'self_apply_free_room', 'free_room', + 'send_text', 'receive_chat', + 'update_bean', 'update_roomcard', + 'joinRoom', + ]; + for (const rpc of required) { + assert.ok(typeof Rpc[rpc] === 'string', `Rpc.${rpc} 必须是字符串`); + } +}); + +test('RpcName 联合类型: 字面量类型正确推导', () => { + // 编译期类型测试:这里的赋值如果类型不对,tsc 会报错 + const sample: RpcName = 'player_login'; + assert.equal(sample, 'player_login'); +}); + +test('Rpc 表: 房间 handler 已覆盖的关键 rpc 都在白名单', () => { + // 验证 room-handlers.ts 用的 14 个 rpc 都在 Rpc 表里 + const roomRpcs: RpcName[] = [ + 'self_join_room', 'other_join_room', + 'self_exit_room', 'other_exit_room', + 'self_apply_free_room', 'other_apply_free_room', + 'self_agree_free_room', 'other_agree_free_room', + 'self_refuse_free_room', 'other_refuse_free_room', + 'other_offline', 'other_online', + 'change_seat', 'free_room', + ]; + for (const rpc of roomRpcs) { + assert.ok(Rpc[rpc] === rpc, `Rpc.${rpc} 必须等于自身字符串`); + } +}); \ No newline at end of file