Framework.Net网络管理
This commit is contained in:
49
client/assets/scripts/Framework/Net/LoginProtocol.ts
Normal file
49
client/assets/scripts/Framework/Net/LoginProtocol.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
29
client/assets/scripts/Framework/Net/NetConfig.ts
Normal file
29
client/assets/scripts/Framework/Net/NetConfig.ts
Normal file
@@ -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<NetConfig> = {
|
||||||
|
timeout: 30000,
|
||||||
|
autoReconnect: true,
|
||||||
|
reconnectInterval: 3000,
|
||||||
|
maxReconnectTimes: 5
|
||||||
|
};
|
||||||
9
client/assets/scripts/Framework/Net/NetConfig.ts.meta
Normal file
9
client/assets/scripts/Framework/Net/NetConfig.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "364ac255-2fee-449a-af27-b7e2adc23f4f",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
25
client/assets/scripts/Framework/Net/NetEvent.ts
Normal file
25
client/assets/scripts/Framework/Net/NetEvent.ts
Normal file
@@ -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"
|
||||||
|
}
|
||||||
9
client/assets/scripts/Framework/Net/NetEvent.ts.meta
Normal file
9
client/assets/scripts/Framework/Net/NetEvent.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "4d54a53f-00b9-4740-a892-cdeed31ff7d8",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
116
client/assets/scripts/Framework/Net/NetExample.ts
Normal file
116
client/assets/scripts/Framework/Net/NetExample.ts
Normal file
@@ -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<ReqLogin, ResLogin>('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();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
client/assets/scripts/Framework/Net/NetExample.ts.meta
Normal file
9
client/assets/scripts/Framework/Net/NetExample.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "b551b489-426e-41f0-a291-bc8d66e407cb",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
307
client/assets/scripts/Framework/Net/NetManager.ts
Normal file
307
client/assets/scripts/Framework/Net/NetManager.ts
Normal file
@@ -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<string, Function[]> = 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<boolean> {
|
||||||
|
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<Req, Res>(apiName: string, req: Req): Promise<Res | null> {
|
||||||
|
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<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
client/assets/scripts/Framework/Net/NetManager.ts.meta
Normal file
9
client/assets/scripts/Framework/Net/NetManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "8af2e4dd-c42a-44a5-b157-ded932880de2",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
168
client/assets/scripts/Framework/Net/PlatformAdapter.ts
Normal file
168
client/assets/scripts/Framework/Net/PlatformAdapter.ts
Normal file
@@ -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<T extends BaseServiceType>(config: ClientConfig): HttpClientBrowser<T> | HttpClientMiniapp<T> {
|
||||||
|
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<T> | HttpClientMiniapp<T>;
|
||||||
|
|
||||||
|
// 根据平台创建对应的客户端
|
||||||
|
if (platform === PlatformType.MiniApp) {
|
||||||
|
console.log('[PlatformAdapter] Creating MiniApp client:', defaultConfig.server);
|
||||||
|
client = new HttpClientMiniapp(this._serviceProto, defaultConfig) as HttpClientMiniapp<T>;
|
||||||
|
} else {
|
||||||
|
// 浏览器和其他 XMLHttpRequest 兼容环境使用 Browser 客户端
|
||||||
|
console.log('[PlatformAdapter] Creating Browser client:', defaultConfig.server);
|
||||||
|
client = new HttpClientBrowser(this._serviceProto, defaultConfig) as HttpClientBrowser<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "ce7904ec-014e-4715-a0e5-3a43262da3a6",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
303
client/assets/scripts/Framework/Net/README.md
Normal file
303
client/assets/scripts/Framework/Net/README.md
Normal file
@@ -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<boolean>;
|
||||||
|
|
||||||
|
// 断开连接并清理资源
|
||||||
|
disconnect(): void;
|
||||||
|
|
||||||
|
// 调用 API
|
||||||
|
callApi<Req, Res>(apiName: string, req: Req): Promise<Res | null>;
|
||||||
|
|
||||||
|
// 监听服务器消息
|
||||||
|
listenMsg<T>(msgName: string, handler: (msg: T) => void): void;
|
||||||
|
|
||||||
|
// 取消监听服务器消息
|
||||||
|
unlistenMsg(msgName: string, handler?: Function): void;
|
||||||
|
|
||||||
|
// 发送消息到服务器
|
||||||
|
sendMsg<T>(msgName: string, msg: T): Promise<void>;
|
||||||
|
|
||||||
|
// 监听网络事件
|
||||||
|
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<NetConfig>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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<ReqLogin, ResLogin>('Login', {
|
||||||
|
username: 'testUser',
|
||||||
|
password: '123456'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
console.log('登录成功:', result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 监听服务器消息
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { MsgUserJoin } from '../Shared/protocols/MsgUserJoin';
|
||||||
|
|
||||||
|
// 监听用户加入消息
|
||||||
|
netManager.listenMsg<MsgUserJoin>('UserJoin', (msg) => {
|
||||||
|
console.log('有新用户加入:', msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取消监听
|
||||||
|
netManager.unlistenMsg('UserJoin');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 发送消息到服务器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { MsgChat } from '../Shared/protocols/MsgChat';
|
||||||
|
|
||||||
|
// 发送聊天消息
|
||||||
|
await netManager.sendMsg<MsgChat>('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/)
|
||||||
Reference in New Issue
Block a user