From 2dbb1e8d052a39c8d482d2184de5829cb7c5641f Mon Sep 17 00:00:00 2001 From: janing <1175861874@qq.com> Date: Thu, 18 Dec 2025 13:26:28 +0800 Subject: [PATCH] NetWebSocketClinet --- .../scripts/Framework/Net/NetConfigBuilder.ts | 222 ++++++++++++++++ .../Framework/Net/NetConfigBuilder.ts.meta | 9 + .../scripts/Framework/Net/WebSocketClient.ts | 237 ++++++++++++++++++ .../Framework/Net/WebSocketClient.ts.meta | 9 + 4 files changed, 477 insertions(+) create mode 100644 client/assets/scripts/Framework/Net/NetConfigBuilder.ts create mode 100644 client/assets/scripts/Framework/Net/NetConfigBuilder.ts.meta create mode 100644 client/assets/scripts/Framework/Net/WebSocketClient.ts create mode 100644 client/assets/scripts/Framework/Net/WebSocketClient.ts.meta diff --git a/client/assets/scripts/Framework/Net/NetConfigBuilder.ts b/client/assets/scripts/Framework/Net/NetConfigBuilder.ts new file mode 100644 index 0000000..f8ff859 --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetConfigBuilder.ts @@ -0,0 +1,222 @@ +import { NetConfig, NetProtocolType } from './NetConfig'; + +/** + * 网络配置构建器 + * 提供便捷的方法来创建网络配置 + */ +export class NetConfigBuilder { + private _config: Partial = {}; + + /** + * 设置服务器地址 + * @param serverUrl 服务器地址 + */ + setServerUrl(serverUrl: string): NetConfigBuilder { + this._config.serverUrl = serverUrl; + return this; + } + + /** + * 设置协议类型 + * @param protocolType 协议类型 + */ + setProtocolType(protocolType: NetProtocolType): NetConfigBuilder { + this._config.protocolType = protocolType; + return this; + } + + /** + * 设置为 HTTP 协议 + * @param serverUrl HTTP 服务器地址 (如: https://api.example.com/api) + */ + useHttp(serverUrl?: string): NetConfigBuilder { + this._config.protocolType = NetProtocolType.Http; + if (serverUrl) { + this._config.serverUrl = serverUrl; + } + // HTTP 通常不需要重连 + this._config.autoReconnect = false; + return this; + } + + /** + * 设置为 WebSocket 协议 + * @param serverUrl WebSocket 服务器地址 (如: wss://ws.example.com/api) + */ + useWebSocket(serverUrl?: string): NetConfigBuilder { + this._config.protocolType = NetProtocolType.WebSocket; + if (serverUrl) { + this._config.serverUrl = serverUrl; + } + // WebSocket 默认启用重连 + this._config.autoReconnect = true; + return this; + } + + /** + * 设置超时时间 + * @param timeout 超时时间(毫秒) + */ + setTimeout(timeout: number): NetConfigBuilder { + this._config.timeout = timeout; + return this; + } + + /** + * 设置重连配置 + * @param autoReconnect 是否自动重连 + * @param reconnectInterval 重连间隔(毫秒) + * @param maxReconnectTimes 最大重连次数 + */ + setReconnect(autoReconnect: boolean, reconnectInterval?: number, maxReconnectTimes?: number): NetConfigBuilder { + this._config.autoReconnect = autoReconnect; + if (reconnectInterval !== undefined) { + this._config.reconnectInterval = reconnectInterval; + } + if (maxReconnectTimes !== undefined) { + this._config.maxReconnectTimes = maxReconnectTimes; + } + return this; + } + + /** + * 设置是否使用JSON格式 + * @param json 是否使用JSON格式 + */ + setJson(json: boolean): NetConfigBuilder { + this._config.json = json; + return this; + } + + /** + * 构建配置 + */ + build(): NetConfig { + if (!this._config.serverUrl) { + throw new Error('Server URL is required'); + } + + return { + serverUrl: this._config.serverUrl, + protocolType: this._config.protocolType || NetProtocolType.Http, + timeout: this._config.timeout || 30000, + autoReconnect: this._config.autoReconnect ?? true, + reconnectInterval: this._config.reconnectInterval || 3000, + maxReconnectTimes: this._config.maxReconnectTimes || 5, + json: this._config.json ?? true + }; + } + + /** + * 创建新的构建器实例 + */ + static create(): NetConfigBuilder { + return new NetConfigBuilder(); + } +} + +/** + * 预定义的网络配置模板 + */ +export class NetConfigTemplates { + /** + * 开发环境 HTTP 配置 + */ + static developmentHttp(serverUrl: string): NetConfig { + return NetConfigBuilder.create() + .useHttp(serverUrl) + .setTimeout(10000) + .build(); + } + + /** + * 生产环境 HTTP 配置 + */ + static productionHttp(serverUrl: string): NetConfig { + return NetConfigBuilder.create() + .useHttp(serverUrl) + .setTimeout(30000) + .build(); + } + + /** + * 开发环境 WebSocket 配置 + */ + static developmentWebSocket(serverUrl: string): NetConfig { + return NetConfigBuilder.create() + .useWebSocket(serverUrl) + .setTimeout(10000) + .setReconnect(true, 1000, 10) // 更频繁的重连用于开发 + .build(); + } + + /** + * 生产环境 WebSocket 配置 + */ + static productionWebSocket(serverUrl: string): NetConfig { + return NetConfigBuilder.create() + .useWebSocket(serverUrl) + .setTimeout(30000) + .setReconnect(true, 3000, 5) + .build(); + } + + /** + * 游戏实时对战 WebSocket 配置 + */ + static gameRealtimeWebSocket(serverUrl: string): NetConfig { + return NetConfigBuilder.create() + .useWebSocket(serverUrl) + .setTimeout(5000) // 短超时 + .setReconnect(true, 500, 20) // 快速重连 + .build(); + } +} + +/** + * 网络环境检测工具 + */ +export class NetEnvironmentDetector { + /** + * 根据当前环境自动生成配置 + * @param baseUrl 基础URL (不包含协议) + * @param isProduction 是否为生产环境 + */ + static autoDetect(baseUrl: string, isProduction: boolean = false): { + http: NetConfig; + webSocket: NetConfig; + } { + const httpUrl = isProduction ? `https://${baseUrl}/api` : `http://${baseUrl}/api`; + const wsUrl = isProduction ? `wss://${baseUrl}/api` : `ws://${baseUrl}/api`; + + return { + http: isProduction + ? NetConfigTemplates.productionHttp(httpUrl) + : NetConfigTemplates.developmentHttp(httpUrl), + webSocket: isProduction + ? NetConfigTemplates.productionWebSocket(wsUrl) + : NetConfigTemplates.developmentWebSocket(wsUrl) + }; + } + + /** + * 检测URL是否为安全连接 + */ + static isSecureUrl(url: string): boolean { + return url.startsWith('https://') || url.startsWith('wss://'); + } + + /** + * 转换HTTP URL为WebSocket URL + */ + static httpToWebSocket(httpUrl: string): string { + return httpUrl.replace(/^https?:/, httpUrl.startsWith('https:') ? 'wss:' : 'ws:'); + } + + /** + * 转换WebSocket URL为HTTP URL + */ + static webSocketToHttp(wsUrl: string): string { + return wsUrl.replace(/^wss?:/, wsUrl.startsWith('wss:') ? 'https:' : 'http:'); + } +} \ No newline at end of file diff --git a/client/assets/scripts/Framework/Net/NetConfigBuilder.ts.meta b/client/assets/scripts/Framework/Net/NetConfigBuilder.ts.meta new file mode 100644 index 0000000..b8bd2ca --- /dev/null +++ b/client/assets/scripts/Framework/Net/NetConfigBuilder.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "3b215a9a-fd5d-404a-8254-dd6f3cffca6a", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/Net/WebSocketClient.ts b/client/assets/scripts/Framework/Net/WebSocketClient.ts new file mode 100644 index 0000000..5198963 --- /dev/null +++ b/client/assets/scripts/Framework/Net/WebSocketClient.ts @@ -0,0 +1,237 @@ +import { BaseServiceType } from 'tsrpc-browser'; +import { NetEvent } from './NetEvent'; + +/** + * WebSocket 连接状态 + */ +export enum WsConnectionStatus { + /** 已断开 */ + Disconnected = "disconnected", + /** 连接中 */ + Connecting = "connecting", + /** 已连接 */ + Connected = "connected", + /** 连接失败 */ + Failed = "failed" +} + +/** + * WebSocket 客户端接口 + * 统一 HTTP 和 WebSocket 客户端的接口 + */ +export interface INetClient { + /** 调用API */ + callApi(apiName: string, req: Req): Promise<{ isSucc: boolean; res?: Res; err?: any }>; + + /** 监听消息 (仅 WebSocket) */ + listenMsg?(msgName: string, handler: Function): void; + + /** 取消监听消息 (仅 WebSocket) */ + unlistenMsg?(msgName: string, handler?: Function): void; + + /** 发送消息 (仅 WebSocket) */ + sendMsg?(msgName: string, msg: any): void; + + /** 连接 (仅 WebSocket) */ + connect?(): Promise<{ isSucc: boolean; errMsg?: string }>; + + /** 断开连接 (仅 WebSocket) */ + disconnect?(): void; + + /** 获取连接状态 */ + status?: WsConnectionStatus; +} + +/** + * WebSocket 客户端包装器 + * 提供统一的接口和事件处理 + */ +export class WebSocketClientWrapper implements INetClient { + private _wsClient: any = null; + private _status: WsConnectionStatus = WsConnectionStatus.Disconnected; + private _eventCallbacks: Map = new Map(); + + constructor(wsClient: any) { + this._wsClient = wsClient; + this.setupEventHandlers(); + } + + /** + * 设置事件处理器 + */ + private setupEventHandlers(): void { + if (!this._wsClient) return; + + // 监听连接事件 + this._wsClient.flows.preConnectFlow.push((v: any) => { + this._status = WsConnectionStatus.Connecting; + this.emit(NetEvent.Connecting); + return v; + }); + + this._wsClient.flows.postConnectFlow.push((v: any) => { + if (v.isSucc) { + this._status = WsConnectionStatus.Connected; + this.emit(NetEvent.Connected); + } else { + this._status = WsConnectionStatus.Failed; + this.emit(NetEvent.Error, v.errMsg); + } + return v; + }); + + // 监听断开连接事件 + this._wsClient.flows.postDisconnectFlow.push((v: any) => { + this._status = WsConnectionStatus.Disconnected; + this.emit(NetEvent.Disconnected); + return v; + }); + } + + /** + * 连接服务器 + */ + async connect(): Promise<{ isSucc: boolean; errMsg?: string }> { + try { + this._status = WsConnectionStatus.Connecting; + const result = await this._wsClient.connect(); + + if (result.isSucc) { + this._status = WsConnectionStatus.Connected; + } else { + this._status = WsConnectionStatus.Failed; + } + + return result; + } catch (error) { + this._status = WsConnectionStatus.Failed; + return { + isSucc: false, + errMsg: error instanceof Error ? error.message : String(error) + }; + } + } + + /** + * 断开连接 + */ + disconnect(): void { + if (this._wsClient && typeof this._wsClient.disconnect === 'function') { + this._wsClient.disconnect(); + } + this._status = WsConnectionStatus.Disconnected; + } + + /** + * 调用API + */ + async callApi(apiName: string, req: Req): Promise<{ isSucc: boolean; res?: Res; err?: any }> { + if (this._status !== WsConnectionStatus.Connected) { + return { + isSucc: false, + err: 'WebSocket not connected' + }; + } + + try { + return await this._wsClient.callApi(apiName, req); + } catch (error) { + return { + isSucc: false, + err: error + }; + } + } + + /** + * 监听消息 + */ + listenMsg(msgName: string, handler: Function): void { + if (this._wsClient && typeof this._wsClient.listenMsg === 'function') { + this._wsClient.listenMsg(msgName, handler); + } + } + + /** + * 取消监听消息 + */ + unlistenMsg(msgName: string, handler?: Function): void { + if (this._wsClient && typeof this._wsClient.unlistenMsg === 'function') { + this._wsClient.unlistenMsg(msgName, handler); + } + } + + /** + * 发送消息 + */ + sendMsg(msgName: string, msg: any): void { + if (this._status !== WsConnectionStatus.Connected) { + console.warn('[WebSocketClient] Cannot send message: not connected'); + return; + } + + if (this._wsClient && typeof this._wsClient.sendMsg === 'function') { + this._wsClient.sendMsg(msgName, msg); + } + } + + /** + * 注册事件监听器 + */ + on(event: NetEvent, callback: Function): void { + if (!this._eventCallbacks.has(event)) { + this._eventCallbacks.set(event, []); + } + this._eventCallbacks.get(event)!.push(callback); + } + + /** + * 取消事件监听器 + */ + off(event: NetEvent, callback: Function): void { + const callbacks = this._eventCallbacks.get(event); + if (callbacks) { + const index = callbacks.indexOf(callback); + if (index > -1) { + callbacks.splice(index, 1); + } + } + } + + /** + * 触发事件 + */ + private emit(event: NetEvent, ...args: any[]): void { + const callbacks = this._eventCallbacks.get(event); + if (callbacks) { + callbacks.forEach(callback => { + try { + callback(...args); + } catch (error) { + console.error('[WebSocketClient] Event callback error:', error); + } + }); + } + } + + /** + * 获取连接状态 + */ + get status(): WsConnectionStatus { + return this._status; + } + + /** + * 判断是否已连接 + */ + get isConnected(): boolean { + return this._status === WsConnectionStatus.Connected; + } + + /** + * 获取原始客户端实例 + */ + get rawClient(): any { + return this._wsClient; + } +} \ No newline at end of file diff --git a/client/assets/scripts/Framework/Net/WebSocketClient.ts.meta b/client/assets/scripts/Framework/Net/WebSocketClient.ts.meta new file mode 100644 index 0000000..fa0bbae --- /dev/null +++ b/client/assets/scripts/Framework/Net/WebSocketClient.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "4a49d854-9ed8-4344-825f-9439bb287819", + "files": [], + "subMetas": {}, + "userData": {} +}