From e09105ca0b2ddaa2232be70bb335392c100a3dad Mon Sep 17 00:00:00 2001 From: janing Date: Sun, 14 Dec 2025 22:39:43 +0800 Subject: [PATCH] =?UTF-8?q?Framework.Net=E7=BD=91=E7=BB=9C=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/Framework/Net/LoginProtocol.ts | 49 +++ .../assets/scripts/Framework/Net/NetConfig.ts | 29 ++ .../scripts/Framework/Net/NetConfig.ts.meta | 9 + .../assets/scripts/Framework/Net/NetEvent.ts | 25 ++ .../scripts/Framework/Net/NetEvent.ts.meta | 9 + .../scripts/Framework/Net/NetExample.ts | 116 +++++++ .../scripts/Framework/Net/NetExample.ts.meta | 9 + .../scripts/Framework/Net/NetManager.ts | 307 ++++++++++++++++++ .../scripts/Framework/Net/NetManager.ts.meta | 9 + .../scripts/Framework/Net/PlatformAdapter.ts | 168 ++++++++++ .../Framework/Net/PlatformAdapter.ts.meta | 9 + client/assets/scripts/Framework/Net/README.md | 303 +++++++++++++++++ 12 files changed, 1042 insertions(+) create mode 100644 client/assets/scripts/Framework/Net/LoginProtocol.ts create mode 100644 client/assets/scripts/Framework/Net/NetConfig.ts create mode 100644 client/assets/scripts/Framework/Net/NetConfig.ts.meta create mode 100644 client/assets/scripts/Framework/Net/NetEvent.ts create mode 100644 client/assets/scripts/Framework/Net/NetEvent.ts.meta create mode 100644 client/assets/scripts/Framework/Net/NetExample.ts create mode 100644 client/assets/scripts/Framework/Net/NetExample.ts.meta create mode 100644 client/assets/scripts/Framework/Net/NetManager.ts create mode 100644 client/assets/scripts/Framework/Net/NetManager.ts.meta create mode 100644 client/assets/scripts/Framework/Net/PlatformAdapter.ts create mode 100644 client/assets/scripts/Framework/Net/PlatformAdapter.ts.meta create mode 100644 client/assets/scripts/Framework/Net/README.md diff --git a/client/assets/scripts/Framework/Net/LoginProtocol.ts b/client/assets/scripts/Framework/Net/LoginProtocol.ts new file mode 100644 index 0000000..1ac98fb --- /dev/null +++ b/client/assets/scripts/Framework/Net/LoginProtocol.ts @@ -0,0 +1,49 @@ +/** + * 登录协议类型定义 + * + * 注意: 这是临时定义,实际项目中应该通过 npm run sync-shared + * 从服务端同步完整的协议定义到 Shared 目录 + */ + +/** + * 登录请求 + */ +export interface ReqLogin { + /** 账号 */ + account: string; + /** 密码 (可选) */ + password?: string; +} + +/** + * 登录响应 + */ +export interface ResLogin { + /** 是否成功 */ + success: boolean; + /** 消息 */ + message?: string; + /** 玩家信息 */ + player?: { + /** 玩家ID */ + id: string; + /** 玩家名称 */ + name: string; + /** 位置 */ + position: { x: number; y: number; z: number }; + /** 出生点 */ + spawnPoint: { x: number; y: number; z: number }; + /** 生命值 */ + hp: number; + /** 最大生命值 */ + maxHp: number; + /** 是否存活 */ + isAlive: boolean; + /** 创建时间 */ + createdAt: number; + /** 最后登录时间 */ + lastLoginAt: number; + }; + /** 是否新玩家 */ + isNewPlayer?: boolean; +} diff --git a/client/assets/scripts/Framework/Net/NetConfig.ts b/client/assets/scripts/Framework/Net/NetConfig.ts new file mode 100644 index 0000000..9c2c468 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetConfig.ts @@ -0,0 +1,29 @@ +/** + * 网络配置接口 + */ +export interface NetConfig { + /** 服务器地址 */ + serverUrl: string; + + /** 超时时间(ms) 默认 30000 */ + timeout?: number; + + /** 是否自动重连 默认 true */ + autoReconnect?: boolean; + + /** 重连间隔(ms) 默认 3000 */ + reconnectInterval?: number; + + /** 最大重连次数 默认 5 */ + maxReconnectTimes?: number; +} + +/** + * 默认网络配置 + */ +export const DefaultNetConfig: Partial = { + timeout: 30000, + autoReconnect: true, + reconnectInterval: 3000, + maxReconnectTimes: 5 +}; diff --git a/client/assets/scripts/Framework/Net/NetConfig.ts.meta b/client/assets/scripts/Framework/Net/NetConfig.ts.meta new file mode 100644 index 0000000..87d3755 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetConfig.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "364ac255-2fee-449a-af27-b7e2adc23f4f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/NetEvent.ts b/client/assets/scripts/Framework/Net/NetEvent.ts new file mode 100644 index 0000000..8ec7842 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetEvent.ts @@ -0,0 +1,25 @@ +/** + * 网络事件定义 + */ +export enum NetEvent { + /** 连接成功 */ + Connected = "net_connected", + + /** 连接断开 */ + Disconnected = "net_disconnected", + + /** 正在重连 */ + Reconnecting = "net_reconnecting", + + /** 重连成功 */ + ReconnectSuccess = "net_reconnect_success", + + /** 重连失败 */ + ReconnectFailed = "net_reconnect_failed", + + /** 网络错误 */ + Error = "net_error", + + /** 连接超时 */ + Timeout = "net_timeout" +} diff --git a/client/assets/scripts/Framework/Net/NetEvent.ts.meta b/client/assets/scripts/Framework/Net/NetEvent.ts.meta new file mode 100644 index 0000000..a25e5d9 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetEvent.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "4d54a53f-00b9-4740-a892-cdeed31ff7d8", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/NetExample.ts b/client/assets/scripts/Framework/Net/NetExample.ts new file mode 100644 index 0000000..d54517a --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetExample.ts @@ -0,0 +1,116 @@ +import { _decorator, Component } from 'cc'; +import { NetConfig } from './NetConfig'; +import { NetEvent } from './NetEvent'; +import { NetManager } from './NetManager'; + +const { ccclass } = _decorator; + +/** + * 网络管理器使用示例 + * 演示如何使用 NetManager 连接服务器并进行通信 + */ +@ccclass('NetExample') +export class NetExample extends Component { + + start() { + // 演示网络管理器的使用 + this.initNetwork(); + } + + /** + * 初始化网络 + */ + private async initNetwork() { + console.log('=== 网络模块使用示例 ==='); + + // 1. 获取网络管理器实例 + const netManager = NetManager.getInstance(); + + // 2. 设置服务协议 (必须在 init 之前调用) + // TODO: 运行 npm run sync-shared 后,从 Shared 目录导入协议 + // import { serviceProto } from '../../Shared/protocols/serviceProto'; + // netManager.setServiceProto(serviceProto); + console.log('⚠️ 提示: 请先运行 npm run sync-shared 同步服务端协议'); + console.log('⚠️ 然后导入 serviceProto 并调用 setServiceProto()'); + + // 3. 监听网络事件 + netManager.on(NetEvent.Connected, () => { + console.log('✅ 网络已连接'); + this.onConnected(); + }); + + netManager.on(NetEvent.Disconnected, () => { + console.log('❌ 网络已断开'); + }); + + netManager.on(NetEvent.Reconnecting, (count: number) => { + console.log(`🔄 正在重连... (${count})`); + }); + + netManager.on(NetEvent.Error, (error: any) => { + console.error('⚠️ 网络错误:', error); + }); + + // 4. 配置网络参数 + const config: NetConfig = { + serverUrl: 'http://localhost:3000', // TODO: 替换为实际服务器地址 + timeout: 30000, + autoReconnect: true, + reconnectInterval: 3000, + maxReconnectTimes: 5 + }; + + // 5. 初始化 + netManager.init(config); + + // 6. 连接服务器 (HttpClient 创建即可用) + const success = await netManager.connect(); + + if (success) { + console.log('✅ 网络初始化成功'); + } else { + console.error('❌ 网络初始化失败'); + } + } + + /** + * 连接成功后的处理 + */ + private async onConnected() { + const netManager = NetManager.getInstance(); + + // 示例: 调用登录 API + // 需要先同步服务端协议并导入类型定义 + // import { ReqLogin, ResLogin } from '../../Shared/protocols/PtlLogin'; + // + // const result = await netManager.callApi('login', { + // username: 'testUser', + // password: '123456' + // }); + // + // if (result) { + // console.log('登录成功:', result); + // } + + // 示例: 监听服务器消息 + // import { MsgUserJoin } from '../../Shared/protocols/MsgUserJoin'; + // + // netManager.listenMsg('UserJoin', (msg: MsgUserJoin) => { + // console.log('有新用户加入:', msg); + // }); + + console.log(''); + console.log('========================================'); + console.log('📝 使用步骤:'); + console.log('1. 运行 npm run sync-shared 同步协议'); + console.log('2. 从 Shared 导入 serviceProto'); + console.log('3. 调用 setServiceProto(serviceProto)'); + console.log('4. 就可以使用 callApi 和 listenMsg 了'); + console.log('========================================'); + } + + onDestroy() { + // 断开网络连接 + NetManager.getInstance().disconnect(); + } +} diff --git a/client/assets/scripts/Framework/Net/NetExample.ts.meta b/client/assets/scripts/Framework/Net/NetExample.ts.meta new file mode 100644 index 0000000..fd38179 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetExample.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b551b489-426e-41f0-a291-bc8d66e407cb", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/NetManager.ts b/client/assets/scripts/Framework/Net/NetManager.ts new file mode 100644 index 0000000..12bfec2 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetManager.ts @@ -0,0 +1,307 @@ +import { NetConfig, DefaultNetConfig } from './NetConfig'; +import { NetEvent } from './NetEvent'; +import { PlatformAdapter, ClientConfig } from './PlatformAdapter'; + +/** + * 网络管理器单例 + * 负责管理网络连接和消息通信 + */ +export class NetManager { + private static _instance: NetManager | null = null; + + /** TSRPC Client 实例 (HttpClient) */ + private _client: any = null; + + /** 是否已连接 */ + private _isConnected: boolean = false; + + /** 网络配置 */ + private _config: NetConfig | null = null; + + /** 重连计数 */ + private _reconnectCount: number = 0; + + /** 重连定时器 */ + private _reconnectTimer: any = null; + + /** 事件监听器 */ + private _eventListeners: Map = new Map(); + + private constructor() {} + + /** + * 获取单例实例 + */ + static getInstance(): NetManager { + if (!this._instance) { + this._instance = new NetManager(); + } + return this._instance; + } + + /** + * 设置服务协议 (必须在 init 之前调用) + * @param serviceProto 从 shared 目录导入的协议定义 + */ + setServiceProto(serviceProto: any): void { + PlatformAdapter.setServiceProto(serviceProto); + } + + /** + * 初始化网络管理器 + * @param config 网络配置 + */ + init(config: NetConfig): void { + this._config = { + ...DefaultNetConfig, + ...config + } as NetConfig; + + console.log('[NetManager] Initialized with config:', this._config); + console.log('[NetManager] Platform:', PlatformAdapter.getPlatformInfo()); + } + + /** + * 连接服务器 + */ + async connect(): Promise { + try { + console.log('[NetManager] Connecting to server:', this._config.serverUrl); + + // 创建客户端配置 + const clientConfig: ClientConfig = { + server: this._config.serverUrl, + json: true, + timeout: this._config.timeout + }; + + // 根据平台创建对应的客户端 + this._client = PlatformAdapter.createClient(clientConfig); + + // HttpClient 不需要显式连接,创建即可使用 + // 如果未来需要 WebSocket 支持,可以在这里添加 connect() 调用 + + this._isConnected = true; + this._reconnectCount = 0; + + this.emit(NetEvent.Connected); + console.log('[NetManager] Client created successfully'); + + // client 可能不需要显式连接 + // 对于 WsClient 需要调用 connect() + + this._isConnected = true; + this._reconnectCount = 0; + + this.emit(NetEvent.Connected); + console.log('[NetManager] Connected successfully'); + + return true; + } catch (error) { + console.error('[NetManager] Connection failed:', error); + this.emit(NetEvent.Error, error); + + // 尝试重连 + if (this._config.autoReconnect) { + this.scheduleReconnect(); + } + + return false; + } + } + + /** + * 断开连接 + */ + disconnect(): void { + if (!this._isConnected) { + return; + } + // HttpClient 无需显式断开连接 + // 如果使用 WebSocket,可以在这里调用 disconnect() + + this._isConnected = false; + this._client = null; + if(this._reconnectTimer){ + this._reconnectTimer = null; + } + + // TODO: 调用客户端的断开连接方法 + if (this._client && typeof this._client.disconnect === 'function') { + this._client.disconnect(); + } + + this._isConnected = false; + this._client = null; + + this.emit(NetEvent.Disconnected); + console.log('[NetManager] Disconnected'); + } + + /** + * 调用 API + * @param apiName API 名称 + * @param req 请求参数 + */ + async callApi(apiName: string, req: Req): Promise { + if (!this._isConnected || !this._client) { + console.error('[NetManager] Not connected'); + return null; + } + + try { + console.log(`[NetManager] Calling API: ${apiName}`, req); + + // TODO: 根据实际的协议定义调用 API + const result = await this._client.callApi(apiName, req); + + if (result.isSucc) { + console.log(`[NetManager] API ${apiName} success:`, result.res); + return result.res; + } else { + console.error(`[NetManager] API ${apiName} failed:`, result.err); + return null; + } + } catch (error) { + console.error(`[NetManager] API ${apiName} error:`, error); + this.emit(NetEvent.Error, error); + return null; + } + } + + /** + * 监听消息 + * @param msgName 消息名称 + * @param handler 处理函数 + */ + listenMsg(msgName: string, handler: Function): void { + if (!this._client) { + console.error('[NetManager] Client not initialized'); + return; + } + + console.log(`[NetManager] Listening message: ${msgName}`); + + // TODO: 根据实际的 TSRPC 客户端实现监听消息 + if (typeof this._client.listenMsg === 'function') { + this._client.listenMsg(msgName, handler); + } + } + + /** + * 发送消息 + * @param msgName 消息名称 + * @param msg 消息内容 + */ + sendMsg(msgName: string, msg: any): void { + if (!this._isConnected || !this._client) { + console.error('[NetManager] Not connected'); + return; + } + + console.log(`[NetManager] Sending message: ${msgName}`, msg); + + // TODO: 根据实际的 TSRPC 客户端实现发送消息 + if (typeof this._client.sendMsg === 'function') { + this._client.sendMsg(msgName, msg); + } + } + + /** + * 安排重连 + */ + private scheduleReconnect(): void { + if (!this._config) { + return; + } + + if (this._reconnectCount >= (this._config.maxReconnectTimes || 5)) { + console.error('[NetManager] Max reconnect times reached'); + this.emit(NetEvent.ReconnectFailed); + return; + } + + this._reconnectCount++; + console.log(`[NetManager] Scheduling reconnect (${this._reconnectCount}/${this._config.maxReconnectTimes})...`); + + this.emit(NetEvent.Reconnecting, this._reconnectCount); + + this._reconnectTimer = setTimeout(() => { + this.reconnect(); + }, this._config.reconnectInterval || 3000); + } + + /** + * 重新连接 + */ + private async reconnect(): Promise { + console.log('[NetManager] Reconnecting...'); + + const success = await this.connect(); + + if (success) { + this.emit(NetEvent.ReconnectSuccess); + } else if (this._config?.autoReconnect) { + this.scheduleReconnect(); + } + } + + /** + * 监听网络事件 + * @param event 事件名称 + * @param callback 回调函数 + */ + on(event: NetEvent, callback: Function): void { + if (!this._eventListeners.has(event)) { + this._eventListeners.set(event, []); + } + this._eventListeners.get(event)!.push(callback); + } + + /** + * 取消监听网络事件 + * @param event 事件名称 + * @param callback 回调函数 + */ + off(event: NetEvent, callback: Function): void { + const listeners = this._eventListeners.get(event); + if (listeners) { + const index = listeners.indexOf(callback); + if (index > -1) { + listeners.splice(index, 1); + } + } + } + + /** + * 触发事件 + * @param event 事件名称 + * @param args 事件参数 + */ + private emit(event: NetEvent, ...args: any[]): void { + const listeners = this._eventListeners.get(event); + if (listeners) { + listeners.forEach(callback => { + try { + callback(...args); + } catch (error) { + console.error('[NetManager] Event callback error:', error); + } + }); + } + } + + /** + * 获取连接状态 + */ + get isConnected(): boolean { + return this._isConnected; + } + + /** + * 获取客户端实例 + */ + get client(): any { + return this._client; + } +} diff --git a/client/assets/scripts/Framework/Net/NetManager.ts.meta b/client/assets/scripts/Framework/Net/NetManager.ts.meta new file mode 100644 index 0000000..bc6c4f6 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetManager.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "8af2e4dd-c42a-44a5-b157-ded932880de2", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/PlatformAdapter.ts b/client/assets/scripts/Framework/Net/PlatformAdapter.ts new file mode 100644 index 0000000..3dd6ca0 --- /dev/null +++ b/client/assets/scripts/Framework/Net/PlatformAdapter.ts @@ -0,0 +1,168 @@ +import { sys } from 'cc'; +// 使用别名导入避免命名冲突 +import { BaseServiceType, HttpClient as HttpClientBrowser } from 'tsrpc-browser'; +import { HttpClient as HttpClientMiniapp } from 'tsrpc-miniapp'; + +/** + * 平台类型枚举 + */ +export enum PlatformType { + /** 浏览器平台 */ + Browser = "browser", + + /** 小程序平台 (微信、抖音、QQ等) */ + MiniApp = "miniapp", + + /** 未知平台 */ + Unknown = "unknown" +} + +/** + * 客户端配置接口 + */ +export interface ClientConfig { + /** 服务器地址 */ + server: string; + + /** 是否使用 JSON 格式 (默认 true) */ + json?: boolean; + + /** 超时时间(ms) */ + timeout?: number; + + /** 其他配置 */ + [key: string]: any; +} + +/** + * 平台适配器 + * 根据当前平台返回对应的 TSRPC 客户端实现 + * + * 注意: TSRPC 不同平台的库中 API 是重名的,所以使用别名导入 + * - tsrpc-browser: 用于浏览器和 XMLHttpRequest 兼容的环境 + * - tsrpc-miniapp: 用于微信、抖音、QQ 等小程序环境 + */ +export class PlatformAdapter { + private static _currentPlatform: PlatformType | null = null; + private static _serviceProto: any = null; + + /** + * 设置服务协议 + * @param serviceProto 从 shared 目录导入的协议定义 + */ + static setServiceProto(serviceProto: any): void { + this._serviceProto = serviceProto; + console.log('[PlatformAdapter] Service protocol set'); + } + + /** + * 检测当前运行平台 + */ + static detectPlatform(): PlatformType { + if (this._currentPlatform) { + return this._currentPlatform; + } + + // 检测 QQ 小游戏/小程序 + if (sys.platform === sys.Platform.WECHAT_GAME && (window as any).qq) { + this._currentPlatform = PlatformType.MiniApp; + return this._currentPlatform; + } + + // 检测微信小游戏/小程序 + if (sys.platform === sys.Platform.WECHAT_GAME) { + this._currentPlatform = PlatformType.MiniApp; + return this._currentPlatform; + } + + // 检测字节跳动小游戏/小程序 + if (sys.platform === sys.Platform.BYTEDANCE_MINI_GAME) { + this._currentPlatform = PlatformType.MiniApp; + return this._currentPlatform; + } + + // 检测浏览器 + if (sys.isBrowser) { + this._currentPlatform = PlatformType.Browser; + return this._currentPlatform; + } + + // 其他原生环境默认使用 Browser 客户端 (XMLHttpRequest 兼容) + console.warn('[PlatformAdapter] Unknown platform, fallback to Browser client'); + this._currentPlatform = PlatformType.Browser; + return this._currentPlatform; + } + + /** + * 创建对应平台的 TSRPC 客户端实例 + * @param config 客户端配置 + */ + static createClient(config: ClientConfig): HttpClientBrowser | HttpClientMiniapp { + const platform = this.detectPlatform(); + + // 默认配置 + const defaultConfig: ClientConfig = { + server: config.server, + json: true, + timeout: config.timeout || 30000, + ...config + }; + + // 如果设置了协议,添加到配置中 + if (this._serviceProto) { + (defaultConfig as any).proto = this._serviceProto; + } else { + console.warn('[PlatformAdapter] Service protocol not set, please call setServiceProto() first'); + } + + let client: HttpClientBrowser | HttpClientMiniapp; + + // 根据平台创建对应的客户端 + if (platform === PlatformType.MiniApp) { + console.log('[PlatformAdapter] Creating MiniApp client:', defaultConfig.server); + client = new HttpClientMiniapp(this._serviceProto, defaultConfig) as HttpClientMiniapp; + } else { + // 浏览器和其他 XMLHttpRequest 兼容环境使用 Browser 客户端 + console.log('[PlatformAdapter] Creating Browser client:', defaultConfig.server); + client = new HttpClientBrowser(this._serviceProto, defaultConfig) as HttpClientBrowser; + } + + return client; + } + + /** + * 判断是否为小程序环境 + */ + static isMiniApp(): boolean { + return this.detectPlatform() === PlatformType.MiniApp; + } + + /** + * 判断是否为浏览器环境 + */ + static isBrowser(): boolean { + return this.detectPlatform() === PlatformType.Browser; + } + + /** + * 获取当前平台类型 + */ + static getCurrentPlatform(): PlatformType { + if (!this._currentPlatform) { + return this.detectPlatform(); + } + return this._currentPlatform; + } + + /** + * 获取平台描述信息 + */ + static getPlatformInfo(): string { + const platform = this.getCurrentPlatform(); + const platformName = sys.platform; + const isMobile = sys.isMobile; + const isNative = sys.isNative; + + return `Platform: ${platform}, OS: ${platformName}, Mobile: ${isMobile}, Native: ${isNative}`; + } +} diff --git a/client/assets/scripts/Framework/Net/PlatformAdapter.ts.meta b/client/assets/scripts/Framework/Net/PlatformAdapter.ts.meta new file mode 100644 index 0000000..f053a1e --- /dev/null +++ b/client/assets/scripts/Framework/Net/PlatformAdapter.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "ce7904ec-014e-4715-a0e5-3a43262da3a6", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/README.md b/client/assets/scripts/Framework/Net/README.md new file mode 100644 index 0000000..df52098 --- /dev/null +++ b/client/assets/scripts/Framework/Net/README.md @@ -0,0 +1,303 @@ +# 网络通信模块 (Framework/Net) + +## 📋 模块概述 +基于 TSRPC 的网络通信层,支持多平台(浏览器、小程序),提供 API 调用和服务器消息监听功能。 + +## 🎯 核心特性 +- ✅ 跨平台支持(浏览器/小程序) +- ✅ 自动平台检测和适配 +- ✅ 服务协议动态配置 +- ✅ API 调用和消息监听 +- ✅ 自动重连机制 +- ✅ 完整的事件系统 + +## 📦 依赖包 + +| 平台 | NPM 包 | +|------|--------| +| 浏览器 (Web) | `tsrpc-browser` | +| 小程序 (微信/抖音/QQ) | `tsrpc-miniapp` | + +## 🗂️ 文件结构 + +``` +Framework/Net/ +├── NetManager.ts # 网络管理器(核心) +├── PlatformAdapter.ts # 平台适配器 +├── NetConfig.ts # 网络配置 +├── NetEvent.ts # 网络事件 +├── LoginProtocol.ts # 登录协议(临时) +└── NetExample.ts # 使用示例 +``` + +## 📘 核心类详解 + +### NetManager - 网络管理器 + +**职责**: 网络连接管理、消息收发、重连机制 + +**核心方法**: + +```typescript +class NetManager { + // 获取单例 + static getInstance(): NetManager; + + // 设置服务协议(必须在 init 之前调用) + setServiceProto(serviceProto: ServiceProto): void; + + // 初始化网络配置 + init(config: NetConfig): void; + + // 创建客户端实例并连接 + connect(): Promise; + + // 断开连接并清理资源 + disconnect(): void; + + // 调用 API + callApi(apiName: string, req: Req): Promise; + + // 监听服务器消息 + listenMsg(msgName: string, handler: (msg: T) => void): void; + + // 取消监听服务器消息 + unlistenMsg(msgName: string, handler?: Function): void; + + // 发送消息到服务器 + sendMsg(msgName: string, msg: T): Promise; + + // 监听网络事件 + on(event: NetEvent, callback: Function): void; + + // 取消监听网络事件 + off(event: NetEvent, callback: Function): void; +} +``` + +### PlatformAdapter - 平台适配器 + +**职责**: 根据运行平台创建对应的 TSRPC 客户端 + +**技术实现**: +- 使用别名导入: `HttpClient as HttpClientBrowser` 和 `HttpClient as HttpClientMiniapp` +- 自动检测 Cocos 平台类型 (`sys.platform`) +- 根据平台实例化对应的客户端 + +**核心方法**: + +```typescript +class PlatformAdapter { + // 设置服务协议 + static setServiceProto(serviceProto: ServiceProto): void; + + // 检测当前运行平台 + static detectPlatform(): string; + + // 创建对应平台的客户端实例 + static createClient(config: NetConfig): HttpClient | null; + + // 获取当前平台 + static getCurrentPlatform(): string; + + // 平台判断 + static isMiniApp(): boolean; + static isBrowser(): boolean; + + // 获取平台详细信息 + static getPlatformInfo(): object; +} +``` + +### NetConfig - 网络配置 + +**配置接口**: + +```typescript +interface NetConfig { + serverUrl: string; // 服务器地址 + timeout?: number; // 超时时间(ms) 默认 30000 + autoReconnect?: boolean; // 是否自动重连 默认 true + reconnectInterval?: number; // 重连间隔(ms) 默认 3000 + maxReconnectTimes?: number; // 最大重连次数 默认 5 +} + +// 默认配置 +const DefaultNetConfig: Partial; +``` + +### NetEvent - 网络事件 + +**事件类型**: + +```typescript +enum NetEvent { + Connected = "net_connected", // 连接成功 + Disconnected = "net_disconnected", // 连接断开 + Reconnecting = "net_reconnecting", // 正在重连 + ReconnectSuccess = "net_reconnect_success", // 重连成功 + ReconnectFailed = "net_reconnect_failed", // 重连失败 + Error = "net_error", // 网络错误 + Timeout = "net_timeout" // 连接超时 +} +``` + +## 📝 使用指南 + +### 1. 基础使用流程 + +```typescript +import { NetManager } from './Framework/Net/NetManager'; +import { NetConfig } from './Framework/Net/NetConfig'; +import { NetEvent } from './Framework/Net/NetEvent'; +import { serviceProto } from '../Shared/protocols/serviceProto'; + +// 1. 获取实例并设置协议 +const netManager = NetManager.getInstance(); +netManager.setServiceProto(serviceProto); // 必须在 init 之前 + +// 2. 监听网络事件 +netManager.on(NetEvent.Connected, () => { + console.log('✅ 网络已连接'); +}); + +netManager.on(NetEvent.Disconnected, () => { + console.log('❌ 网络已断开'); +}); + +netManager.on(NetEvent.Error, (error: any) => { + console.error('⚠️ 网络错误:', error); +}); + +// 3. 初始化配置 +const config: NetConfig = { + serverUrl: 'http://localhost:3000', + timeout: 30000, + autoReconnect: true, + reconnectInterval: 3000, + maxReconnectTimes: 5 +}; +netManager.init(config); + +// 4. 连接服务器 +const success = await netManager.connect(); +if (success) { + console.log('✅ 网络初始化成功'); +} +``` + +### 2. 调用 API + +```typescript +import { ReqLogin, ResLogin } from '../Shared/protocols/PtlLogin'; + +// 调用登录 API +const result = await netManager.callApi('Login', { + username: 'testUser', + password: '123456' +}); + +if (result) { + console.log('登录成功:', result); +} +``` + +### 3. 监听服务器消息 + +```typescript +import { MsgUserJoin } from '../Shared/protocols/MsgUserJoin'; + +// 监听用户加入消息 +netManager.listenMsg('UserJoin', (msg) => { + console.log('有新用户加入:', msg); +}); + +// 取消监听 +netManager.unlistenMsg('UserJoin'); +``` + +### 4. 发送消息到服务器 + +```typescript +import { MsgChat } from '../Shared/protocols/MsgChat'; + +// 发送聊天消息 +await netManager.sendMsg('Chat', { + content: 'Hello World!' +}); +``` + +## 🔄 协议同步 + +### 同步脚本配置 + +**文件**: 项目根目录的 `sync-shared.js` + +```javascript +// 服务端共享目录 +const serverSharedDir = path.join(__dirname, '../server/src/shared'); + +// 客户端目标目录 +const clientSharedDir = path.join(__dirname, 'assets/scripts/Shared'); +``` + +### 使用步骤 + +1. **确保服务端项目位置**: 服务端项目应该在 `../server` 目录 +2. **运行同步命令**: + ```bash + npm run sync-shared + ``` +3. **导入协议**: + ```typescript + import { serviceProto } from '../Shared/protocols/serviceProto'; + import { ReqLogin, ResLogin } from '../Shared/protocols/PtlLogin'; + ``` + +## ⚠️ 注意事项 + +1. **协议必须先设置**: 在调用 `init()` 之前,必须先调用 `setServiceProto()` +2. **连接状态检查**: 调用 API 前确保已连接,否则会返回 null +3. **错误处理**: 建议监听 `NetEvent.Error` 事件处理网络错误 +4. **资源清理**: 应用退出时调用 `disconnect()` 清理资源 +5. **平台兼容**: PlatformAdapter 会自动处理平台差异,无需手动判断 + +## 🔍 调试技巧 + +### 启用详细日志 + +```typescript +// NetManager 内部已有详细的 console.log +// 可以根据日志前缀过滤: +// [NetManager] - 网络管理器日志 +// [PlatformAdapter] - 平台适配器日志 +``` + +### 常见问题 + +**问题1**: `协议未设置` 错误 +```typescript +// 解决: 确保在 init 之前调用 setServiceProto +netManager.setServiceProto(serviceProto); +netManager.init(config); +``` + +**问题2**: `API 调用返回 null` +```typescript +// 解决: 检查网络连接状态 +netManager.on(NetEvent.Connected, async () => { + // 在连接成功后再调用 API + const result = await netManager.callApi('Login', data); +}); +``` + +**问题3**: 小程序平台客户端创建失败 +```typescript +// 解决: 确保已安装 tsrpc-miniapp +npm install tsrpc-miniapp +``` + +## 📚 参考资料 + +- [TSRPC 官方文档](https://tsrpc.cn/) +- [Cocos Creator 官方文档](https://docs.cocos.com/creator/manual/zh/)