dzkcc-mflow 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,7 @@ Cocos模块化流程框架(Modular Flow Framework)是一个为Cocos Creator
13
13
  - **事件系统**:强大的事件广播和监听机制
14
14
  - **资源加载系统**:统一的资源加载和释放管理
15
15
  - **HTTP网络请求系统**:简洁易用的HTTP客户端
16
+ - **WebSocket实时通信**:支持自动重连、心跳检测的WebSocket客户端
16
17
  - **开发工具**:配套的Cocos Creator编辑器插件
17
18
 
18
19
  ### 1.3 安装说明
@@ -257,7 +258,361 @@ export class UserManager extends AbstractManager {
257
258
  }
258
259
  ```
259
260
 
260
- ## 7. 开发工具
261
+ ## 7. WebSocket 实时通信系统
262
+
263
+ ### 7.1 WebSocketManager WebSocket管理器
264
+
265
+ WebSocketManager提供了完整的 WebSocket 客户端功能,支持:
266
+
267
+ ```typescript
268
+ // 连接 WebSocket 服务器
269
+ mf.socket.connect('ws://localhost:8080/game');
270
+
271
+ // 或使用安全连接
272
+ mf.socket.connect('wss://game-server.example.com/ws');
273
+
274
+ // 配置自动重连和心跳
275
+ mf.socket.configure({
276
+ reconnect: true, // 启用自动重连
277
+ reconnectInterval: 3000, // 重连间隔 3 秒
278
+ reconnectAttempts: 5, // 最多重连 5 次
279
+ heartbeat: true, // 启用心跳
280
+ heartbeatInterval: 30000, // 心跳间隔 30 秒
281
+ heartbeatMessage: 'ping' // 心跳消息
282
+ });
283
+
284
+ // 监听事件
285
+ mf.socket.on('open', (event) => {
286
+ console.log('连接成功');
287
+ });
288
+
289
+ mf.socket.on('message', (event) => {
290
+ console.log('收到消息:', event.data);
291
+ });
292
+
293
+ mf.socket.on('error', (event) => {
294
+ console.error('连接错误:', event);
295
+ });
296
+
297
+ mf.socket.on('close', (event) => {
298
+ console.log('连接关闭');
299
+ });
300
+
301
+ // 发送消息(支持多种数据类型)
302
+ // 1. 发送对象(自动转换为 JSON)
303
+ mf.socket.send({ type: 'move', x: 100, y: 200 });
304
+
305
+ // 2. 发送字符串
306
+ mf.socket.send('Hello Server');
307
+
308
+ // 检查连接状态
309
+ if (mf.socket.isConnected()) {
310
+ // 已连接
311
+ }
312
+
313
+ // 断开连接
314
+ mf.socket.disconnect();
315
+ ```
316
+
317
+ ### 7.2 发送不同类型的数据
318
+
319
+ WebSocket 支持多种数据类型的发送:
320
+
321
+ ```typescript
322
+ // ==================== 1. 发送 JSON 对象(推荐)====================
323
+ // 自动序列化为 JSON 字符串
324
+ mf.socket.send({
325
+ type: 'player_move',
326
+ position: { x: 100, y: 200 },
327
+ timestamp: Date.now()
328
+ });
329
+
330
+ // ==================== 2. 发送纯文本 ====================
331
+ mf.socket.send('ping');
332
+
333
+ // ==================== 3. 发送二进制数据(ArrayBuffer)====================
334
+ // 适用场景:发送游戏状态快照、地图数据等需要高效传输的场景
335
+ function sendBinaryData() {
336
+ // 创建 ArrayBuffer(8 字节)
337
+ const buffer = new ArrayBuffer(8);
338
+ const view = new DataView(buffer);
339
+
340
+ // 写入玩家 ID(4 字节整数)
341
+ view.setInt32(0, 12345, true);
342
+
343
+ // 写入玩家位置(2 个 2 字节整数)
344
+ view.setInt16(4, 100, true); // x 坐标
345
+ view.setInt16(6, 200, true); // y 坐标
346
+
347
+ // 发送二进制数据
348
+ mf.socket.send(buffer);
349
+ }
350
+
351
+ // 接收二进制数据示例
352
+ mf.socket.on('message', (event: MessageEvent) => {
353
+ if (event.data instanceof ArrayBuffer) {
354
+ const view = new DataView(event.data);
355
+ const playerId = view.getInt32(0, true);
356
+ const x = view.getInt16(4, true);
357
+ const y = view.getInt16(6, true);
358
+ console.log(`玩家 ${playerId} 移动到 (${x}, ${y})`);
359
+ }
360
+ });
361
+
362
+ // ==================== 4. 发送文件(Blob)====================
363
+ // 适用场景:上传截图、录像回放、自定义地图等
364
+ async function sendScreenshot() {
365
+ // 方式 1:从 Canvas 获取 Blob
366
+ const canvas = document.querySelector('canvas') as HTMLCanvasElement;
367
+ canvas.toBlob((blob) => {
368
+ if (blob) {
369
+ mf.socket.send(blob);
370
+ }
371
+ }, 'image/png');
372
+
373
+ // 方式 2:从文件选择器获取
374
+ const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
375
+ const file = fileInput.files?.[0];
376
+ if (file) {
377
+ mf.socket.send(file);
378
+ }
379
+
380
+ // 方式 3:创建自定义 Blob
381
+ const data = new Blob(['自定义数据内容'], { type: 'text/plain' });
382
+ mf.socket.send(data);
383
+ }
384
+
385
+ // 接收 Blob 数据示例
386
+ mf.socket.on('message', async (event: MessageEvent) => {
387
+ if (event.data instanceof Blob) {
388
+ // 读取 Blob 数据
389
+ const text = await event.data.text();
390
+ console.log('收到文件数据:', text);
391
+
392
+ // 或者作为 ArrayBuffer 读取
393
+ const buffer = await event.data.arrayBuffer();
394
+ console.log('文件大小:', buffer.byteLength, '字节');
395
+ }
396
+ });
397
+
398
+ // ==================== 5. 发送 TypedArray(Uint8Array 等)====================
399
+ // 适用场景:发送图像数据、音频流等
400
+ function sendImageData() {
401
+ // 创建一个 256 字节的数据
402
+ const imageData = new Uint8Array(256);
403
+ for (let i = 0; i < imageData.length; i++) {
404
+ imageData[i] = i;
405
+ }
406
+
407
+ // 发送 TypedArray(会自动转换为 ArrayBuffer)
408
+ mf.socket.send(imageData.buffer);
409
+ }
410
+ ```
411
+
412
+
413
+ ### 7.3 功能特性
414
+
415
+ 1. **自动重连**:连接断开后自动尝试重连,可配置重连次数和间隔
416
+ 2. **心跳检测**:定期发送心跳消息保持连接
417
+ 3. **消息队列**:连接断开时缓存消息,重连后自动发送
418
+ 4. **事件管理**:统一的事件监听和触发机制
419
+ 5. **连接状态管理**:实时获取连接状态
420
+ 6. **自动序列化**:对象类型自动转换为 JSON 字符串
421
+ 7. **多数据类型支持**:支持 string、object、ArrayBuffer、Blob 等
422
+
423
+ ### 7.4 实战案例:多人对战游戏
424
+
425
+ ```typescript
426
+ // ==================== 案例 1:实时对战位置同步 ====================
427
+ // 使用二进制数据传输,减少带宽占用
428
+ class BattleNetworkManager {
429
+ // 发送玩家位置(二进制,只需 12 字节)
430
+ sendPlayerPosition(playerId: number, x: number, y: number, rotation: number) {
431
+ const buffer = new ArrayBuffer(12);
432
+ const view = new DataView(buffer);
433
+
434
+ view.setInt32(0, playerId, true); // 玩家 ID(4 字节)
435
+ view.setFloat32(4, x, true); // X 坐标(4 字节)
436
+ view.setFloat32(8, y, true); // Y 坐标(4 字节)
437
+
438
+ mf.socket.send(buffer);
439
+ }
440
+
441
+ // 接收其他玩家位置
442
+ setupPositionReceiver() {
443
+ mf.socket.on('message', (event: MessageEvent) => {
444
+ if (event.data instanceof ArrayBuffer) {
445
+ const view = new DataView(event.data);
446
+ const playerId = view.getInt32(0, true);
447
+ const x = view.getFloat32(4, true);
448
+ const y = view.getFloat32(8, true);
449
+
450
+ // 更新其他玩家位置
451
+ this.updateOtherPlayerPosition(playerId, x, y);
452
+ }
453
+ });
454
+ }
455
+
456
+ private updateOtherPlayerPosition(playerId: number, x: number, y: number) {
457
+ // 更新游戏中其他玩家的位置
458
+ console.log(`玩家 ${playerId} 移动到 (${x}, ${y})`);
459
+ }
460
+ }
461
+
462
+ // ==================== 案例 2:截图分享功能 ====================
463
+ // 使用 Blob 上传游戏截图
464
+ class ScreenshotManager {
465
+ async captureAndSend() {
466
+ // 获取游戏 Canvas
467
+ const canvas = document.querySelector('canvas') as HTMLCanvasElement;
468
+
469
+ // 转换为 Blob
470
+ canvas.toBlob((blob) => {
471
+ if (blob) {
472
+ // 发送截图到服务器
473
+ mf.socket.send(blob);
474
+ console.log(`发送截图,大小: ${blob.size} 字节`);
475
+ }
476
+ }, 'image/jpeg', 0.8); // JPEG 格式,80% 质量
477
+ }
478
+
479
+ // 接收其他玩家的截图
480
+ setupScreenshotReceiver() {
481
+ mf.socket.on('message', async (event: MessageEvent) => {
482
+ if (event.data instanceof Blob) {
483
+ // 创建图片 URL
484
+ const imageUrl = URL.createObjectURL(event.data);
485
+
486
+ // 显示图片
487
+ const img = new Image();
488
+ img.src = imageUrl;
489
+ document.body.appendChild(img);
490
+
491
+ console.log('收到截图');
492
+ }
493
+ });
494
+ }
495
+ }
496
+
497
+ // ==================== 案例 3:混合数据类型 ====================
498
+ // 根据消息类型选择最优传输方式
499
+ class SmartNetworkManager {
500
+ send(messageType: string, data: any) {
501
+ switch (messageType) {
502
+ case 'chat':
503
+ // 聊天消息:使用 JSON
504
+ mf.socket.send({
505
+ type: 'chat',
506
+ message: data.message,
507
+ playerId: data.playerId
508
+ });
509
+ break;
510
+
511
+ case 'position':
512
+ // 位置更新:使用二进制(高频更新)
513
+ this.sendPositionBinary(data);
514
+ break;
515
+
516
+ case 'screenshot':
517
+ // 截图:使用 Blob
518
+ mf.socket.send(data.blob);
519
+ break;
520
+
521
+ case 'skill':
522
+ // 技能释放:使用 JSON
523
+ mf.socket.send({
524
+ type: 'skill',
525
+ skillId: data.skillId,
526
+ targetId: data.targetId,
527
+ timestamp: Date.now()
528
+ });
529
+ break;
530
+ }
531
+ }
532
+
533
+ private sendPositionBinary(data: any) {
534
+ const buffer = new ArrayBuffer(12);
535
+ const view = new DataView(buffer);
536
+ view.setInt32(0, data.playerId, true);
537
+ view.setFloat32(4, data.x, true);
538
+ view.setFloat32(8, data.y, true);
539
+ mf.socket.send(buffer);
540
+ }
541
+ }
542
+ ```
543
+
544
+ ### 7.5 Manager 集成示例
545
+
546
+ ```typescript
547
+ // 在Manager中使用
548
+ @manager()
549
+ export class GameNetworkManager extends AbstractManager {
550
+ initialize() {
551
+ // 配置 WebSocket
552
+ mf.socket.configure({
553
+ reconnect: true,
554
+ reconnectInterval: 3000,
555
+ reconnectAttempts: 10,
556
+ heartbeat: true,
557
+ heartbeatInterval: 30000
558
+ });
559
+
560
+ // 设置事件监听
561
+ mf.socket.on('open', this.onConnected.bind(this));
562
+ mf.socket.on('message', this.onMessage.bind(this));
563
+ mf.socket.on('error', this.onError.bind(this));
564
+ mf.socket.on('close', this.onClose.bind(this));
565
+ }
566
+
567
+ connect(token: string) {
568
+ const wsUrl = `wss://game-server.example.com/ws?token=${token}`;
569
+ mf.socket.connect(wsUrl);
570
+ }
571
+
572
+ private onConnected(event: Event) {
573
+ console.log('连接成功');
574
+ this.sendLogin();
575
+ }
576
+
577
+ private onMessage(event: MessageEvent) {
578
+ const data = JSON.parse(event.data);
579
+ // 处理服务器消息
580
+ this.handleServerMessage(data);
581
+ }
582
+
583
+ private onError(event: Event) {
584
+ console.error('连接错误');
585
+ }
586
+
587
+ private onClose(event: CloseEvent) {
588
+ console.log('连接关闭');
589
+ }
590
+
591
+ sendPlayerMove(x: number, y: number) {
592
+ // 直接发送对象,自动序列化为 JSON
593
+ mf.socket.send({
594
+ type: 'player_move',
595
+ position: { x, y },
596
+ timestamp: Date.now()
597
+ });
598
+ }
599
+
600
+ private sendLogin() {
601
+ // 直接发送对象,自动序列化为 JSON
602
+ mf.socket.send({
603
+ type: 'login',
604
+ userId: this.getUserId()
605
+ });
606
+ }
607
+
608
+ private handleServerMessage(data: any) {
609
+ // 使用事件系统分发消息
610
+ mf.event.dispatch(`server_${data.type}`, data);
611
+ }
612
+ }
613
+ ```
614
+
615
+ ## 8. 开发工具
261
616
 
262
617
  框架配套了Cocos Creator编辑器插件`mflow-tools`,可以:
263
618
 
@@ -265,7 +620,7 @@ export class UserManager extends AbstractManager {
265
620
  2. 自动引用Prefab上需要操作的元素
266
621
  3. 自动挂载脚本组件
267
622
 
268
- ### 7.1 使用方法
623
+ ### 8.1 使用方法
269
624
 
270
625
  1. 在Prefab中,将需要引用的节点重命名为`#属性名#组件类型`格式,例如:
271
626
  - `#titleLabel#Label` 表示引用Label组件
@@ -276,9 +631,9 @@ export class UserManager extends AbstractManager {
276
631
 
277
632
  3. 插件会自动生成基础脚本和业务脚本,并自动设置引用关系
278
633
 
279
- ## 8. 完整示例
634
+ ## 9. 完整示例
280
635
 
281
- ### 8.1 创建Manager
636
+ ### 9.1 创建Manager
282
637
 
283
638
  ```typescript
284
639
  @manager()
@@ -301,7 +656,7 @@ export class GameManager extends AbstractManager {
301
656
  }
302
657
  ```
303
658
 
304
- ### 8.2 创建Model
659
+ ### 9.2 创建Model
305
660
 
306
661
  ```typescript
307
662
  @model()
@@ -322,7 +677,7 @@ export class GameModel implements IModel {
322
677
  }
323
678
  ```
324
679
 
325
- ### 8.3 创建UI界面
680
+ ### 9.3 创建UI界面
326
681
 
327
682
  ```typescript
328
683
  // BaseHomeView.ts (由工具自动生成)
@@ -380,7 +735,7 @@ export class HomeView extends BaseHomeView {
380
735
  }
381
736
  ```
382
737
 
383
- ### 8.4 在场景中使用
738
+ ### 9.4 在场景中使用
384
739
 
385
740
  ```typescript
386
741
  // 在游戏启动时
@@ -392,7 +747,7 @@ export class GameApp extends Component {
392
747
  }
393
748
  ```
394
749
 
395
- ## 9. 最佳实践
750
+ ## 10. 最佳实践
396
751
 
397
752
  1. **模块化设计**:将相关的业务逻辑封装在对应的Manager中
398
753
  2. **数据驱动**:使用Model管理数据状态
@@ -400,12 +755,15 @@ export class GameApp extends Component {
400
755
  4. **资源管理**:使用BaseView自动管理资源加载和释放
401
756
  5. **依赖注入**:使用装饰器简化依赖管理
402
757
  6. **网络请求**:使用HttpManager统一管理网络请求
403
- 7. **工具辅助**:使用mflow-tools提高开发效率
758
+ 7. **实时通信**:使用WebSocketManager处理实时消息,配合事件系统分发
759
+ 8. **工具辅助**:使用mflow-tools提高开发效率
404
760
 
405
- ## 10. 注意事项
761
+ ## 11. 注意事项
406
762
 
407
763
  1. 确保在使用框架功能前Core已经初始化
408
764
  2. 注意资源的正确加载和释放,避免内存泄漏
409
765
  3. 合理使用事件系统,避免事件监听过多影响性能
410
766
  4. 使用BaseView的子类时,确保正确实现所有抽象方法
411
- 5. 网络请求时注意错误处理和超时设置
767
+ 5. 网络请求时注意错误处理和超时设置
768
+ 6. WebSocket 连接时记得在场景切换时断开连接,避免内存泄漏
769
+ 7. 合理配置心跳和重连参数,平衡连接稳定性和服务器压力
package/dist/App.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ICore, IUIManager, IEventManager, ICocosResManager, IHttpManager } from "./core";
1
+ import { ICore, IUIManager, IEventManager, ICocosResManager, IHttpManager, IWebSocketManager } from "./core";
2
2
  /**
3
3
  * 对外暴露的全局app对像,用于访问基础能力,为上层业务提供了简洁的访问方式
4
4
  *
@@ -10,7 +10,7 @@ export declare class App {
10
10
  static readonly config: any;
11
11
  static get gui(): IUIManager;
12
12
  static get http(): IHttpManager;
13
- static readonly socket: any;
13
+ static get socket(): IWebSocketManager;
14
14
  static get res(): ICocosResManager;
15
15
  static get event(): IEventManager;
16
16
  static readonly storage: any;
package/dist/App.js CHANGED
@@ -16,6 +16,9 @@ class App {
16
16
  static get http() {
17
17
  return ServiceLocator.getService('HttpManager');
18
18
  }
19
+ static get socket() {
20
+ return ServiceLocator.getService('WebSocketManager');
21
+ }
19
22
  static get res() {
20
23
  return ServiceLocator.getService('ResLoader');
21
24
  }
@@ -25,7 +28,6 @@ class App {
25
28
  }
26
29
  App.log = null;
27
30
  App.config = null;
28
- App.socket = null;
29
31
  App.storage = null;
30
32
  App.audio = null;
31
33
  App.timer = null;
@@ -43,6 +43,26 @@ export interface HttpRequestOptions {
43
43
  data?: any;
44
44
  timeout?: number;
45
45
  }
46
+ export interface IWebSocketManager extends IManager {
47
+ connect(url: string, protocols?: string | string[]): void;
48
+ disconnect(code?: number, reason?: string): void;
49
+ send(data: string | ArrayBuffer | Blob | object): void;
50
+ isConnected(): boolean;
51
+ on(event: 'open' | 'message' | 'error' | 'close', handler: Function): void;
52
+ off(event: 'open' | 'message' | 'error' | 'close', handler?: Function): void;
53
+ getReadyState(): number;
54
+ configure(config: Partial<WebSocketConfig>): void;
55
+ }
56
+ export interface WebSocketConfig {
57
+ url: string;
58
+ protocols?: string | string[];
59
+ reconnect?: boolean;
60
+ reconnectInterval?: number;
61
+ reconnectAttempts?: number;
62
+ heartbeat?: boolean;
63
+ heartbeatInterval?: number;
64
+ heartbeatMessage?: string;
65
+ }
46
66
  export interface IEventMsgKey {
47
67
  }
48
68
  export type ToAnyIndexKey<IndexKey, AnyType> = IndexKey extends keyof AnyType ? IndexKey : keyof AnyType;
@@ -1,4 +1,4 @@
1
- import { ICore, IEventManager, IManager, IModel, IHttpManager } from "./Api";
1
+ import { ICore, IEventManager, IManager, IModel, IHttpManager, IWebSocketManager } from "./Api";
2
2
  export declare abstract class AbstractCore<T extends AbstractCore<T>> implements ICore {
3
3
  private readonly container;
4
4
  constructor();
@@ -15,5 +15,6 @@ export declare abstract class AbstractManager implements IManager {
15
15
  protected getModel<T extends IModel>(ctor: new () => T): T;
16
16
  protected getEventManager(): IEventManager;
17
17
  protected getHttpManager(): IHttpManager;
18
+ protected getWebSocketManager(): IWebSocketManager;
18
19
  private releaseEventManager;
19
20
  }
package/dist/core/Core.js CHANGED
@@ -75,6 +75,10 @@ class AbstractManager {
75
75
  getHttpManager() {
76
76
  return ServiceLocator.getService('HttpManager');
77
77
  }
78
+ // WebSocket 管理器获取
79
+ getWebSocketManager() {
80
+ return ServiceLocator.getService('WebSocketManager');
81
+ }
78
82
  releaseEventManager() {
79
83
  var _a, _b;
80
84
  if (this.eventManager) {
@@ -6,6 +6,7 @@ import { UIManager } from './UIManager.js';
6
6
  import { ResLoader } from './ResLoader.js';
7
7
  import { Broadcaster } from './Broadcaster.js';
8
8
  import { HttpManager } from './HttpManager.js';
9
+ import { WebSocketManager } from './WebSocketManager.js';
9
10
  import '../App.js';
10
11
 
11
12
  class Core extends AbstractCore {
@@ -16,6 +17,7 @@ class Core extends AbstractCore {
16
17
  ServiceLocator.regService('ResLoader', new ResLoader());
17
18
  ServiceLocator.regService('UIManager', new UIManager());
18
19
  ServiceLocator.regService('HttpManager', new HttpManager());
20
+ ServiceLocator.regService('WebSocketManager', new WebSocketManager());
19
21
  // 注册业务模块(通过装饰器自动注册)
20
22
  // 推迟到构造函数执行完毕
21
23
  queueMicrotask(() => autoRegister(this));
@@ -0,0 +1,101 @@
1
+ import { IWebSocketManager, WebSocketConfig } from "../core";
2
+ /**
3
+ * WebSocket 管理器实现类
4
+ *
5
+ * 功能特性:
6
+ * 1. 自动重连:连接断开后自动尝试重连
7
+ * 2. 心跳检测:定期发送心跳保持连接
8
+ * 3. 事件管理:统一的事件监听和触发
9
+ * 4. 消息队列:连接断开时缓存消息,重连后自动发送
10
+ */
11
+ export declare class WebSocketManager implements IWebSocketManager {
12
+ private ws;
13
+ private url;
14
+ private protocols?;
15
+ private reconnect;
16
+ private reconnectInterval;
17
+ private reconnectAttempts;
18
+ private currentReconnectAttempts;
19
+ private heartbeat;
20
+ private heartbeatInterval;
21
+ private heartbeatMessage;
22
+ private heartbeatTimer;
23
+ private eventHandlers;
24
+ private messageQueue;
25
+ private reconnectTimer;
26
+ initialize(): void;
27
+ dispose(): void;
28
+ /**
29
+ * 连接 WebSocket
30
+ */
31
+ connect(url: string, protocols?: string | string[]): void;
32
+ /**
33
+ * 断开连接
34
+ */
35
+ disconnect(code?: number, reason?: string): void;
36
+ /**
37
+ * 发送消息
38
+ * 支持多种数据类型:
39
+ * - string: 直接发送
40
+ * - object: 自动转换为 JSON 字符串
41
+ * - ArrayBuffer: 发送二进制数据
42
+ * - Blob: 发送文件数据
43
+ */
44
+ send(data: string | ArrayBuffer | Blob | object): void;
45
+ /**
46
+ * 检查是否已连接
47
+ */
48
+ isConnected(): boolean;
49
+ /**
50
+ * 获取连接状态
51
+ */
52
+ getReadyState(): number;
53
+ /**
54
+ * 注册事件监听
55
+ */
56
+ on(event: 'open' | 'message' | 'error' | 'close', handler: Function): void;
57
+ /**
58
+ * 移除事件监听
59
+ */
60
+ off(event: 'open' | 'message' | 'error' | 'close', handler?: Function): void;
61
+ /**
62
+ * 配置 WebSocket
63
+ */
64
+ configure(config: Partial<WebSocketConfig>): void;
65
+ /**
66
+ * 创建 WebSocket 连接
67
+ */
68
+ private _createWebSocket;
69
+ /**
70
+ * 连接成功
71
+ */
72
+ private _onOpen;
73
+ /**
74
+ * 收到消息
75
+ */
76
+ private _onMessage;
77
+ /**
78
+ * 连接错误
79
+ */
80
+ private _onError;
81
+ /**
82
+ * 连接关闭
83
+ */
84
+ private _onClose;
85
+ /**
86
+ * 触发事件
87
+ */
88
+ private _emit;
89
+ /**
90
+ * 启动心跳
91
+ */
92
+ private _startHeartbeat;
93
+ /**
94
+ * 停止心跳
95
+ */
96
+ private _stopHeartbeat;
97
+ /**
98
+ * 发送队列中的消息
99
+ */
100
+ private _sendQueuedMessages;
101
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * WebSocket 管理器实现类
3
+ *
4
+ * 功能特性:
5
+ * 1. 自动重连:连接断开后自动尝试重连
6
+ * 2. 心跳检测:定期发送心跳保持连接
7
+ * 3. 事件管理:统一的事件监听和触发
8
+ * 4. 消息队列:连接断开时缓存消息,重连后自动发送
9
+ */
10
+ class WebSocketManager {
11
+ constructor() {
12
+ this.ws = null;
13
+ this.url = '';
14
+ // 配置项
15
+ this.reconnect = true; // 是否自动重连
16
+ this.reconnectInterval = 3000; // 重连间隔(毫秒)
17
+ this.reconnectAttempts = 5; // 最大重连次数
18
+ this.currentReconnectAttempts = 0; // 当前重连次数
19
+ this.heartbeat = true; // 是否启用心跳
20
+ this.heartbeatInterval = 30000; // 心跳间隔(毫秒)
21
+ this.heartbeatMessage = 'ping'; // 心跳消息
22
+ this.heartbeatTimer = null; // 心跳定时器
23
+ // 事件监听器
24
+ this.eventHandlers = new Map();
25
+ // 消息队列(连接断开时缓存消息)
26
+ this.messageQueue = [];
27
+ // 重连定时器
28
+ this.reconnectTimer = null;
29
+ }
30
+ initialize() {
31
+ console.log('WebSocketManager 初始化');
32
+ }
33
+ dispose() {
34
+ this.disconnect();
35
+ this.eventHandlers.clear();
36
+ this.messageQueue = [];
37
+ console.log('WebSocketManager 已清理');
38
+ }
39
+ /**
40
+ * 连接 WebSocket
41
+ */
42
+ connect(url, protocols) {
43
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
44
+ console.warn('WebSocket 已连接');
45
+ return;
46
+ }
47
+ this.url = url;
48
+ this.protocols = protocols;
49
+ this.currentReconnectAttempts = 0;
50
+ this._createWebSocket();
51
+ }
52
+ /**
53
+ * 断开连接
54
+ */
55
+ disconnect(code, reason) {
56
+ this.reconnect = false; // 禁止自动重连
57
+ if (this.reconnectTimer) {
58
+ clearTimeout(this.reconnectTimer);
59
+ this.reconnectTimer = null;
60
+ }
61
+ this._stopHeartbeat();
62
+ if (this.ws) {
63
+ this.ws.close(code || 1000, reason || 'Normal closure');
64
+ this.ws = null;
65
+ }
66
+ }
67
+ /**
68
+ * 发送消息
69
+ * 支持多种数据类型:
70
+ * - string: 直接发送
71
+ * - object: 自动转换为 JSON 字符串
72
+ * - ArrayBuffer: 发送二进制数据
73
+ * - Blob: 发送文件数据
74
+ */
75
+ send(data) {
76
+ let sendData;
77
+ // 自动处理对象类型,转换为 JSON 字符串
78
+ if (typeof data === 'object' && !(data instanceof ArrayBuffer) && !(data instanceof Blob)) {
79
+ sendData = JSON.stringify(data);
80
+ }
81
+ else {
82
+ sendData = data;
83
+ }
84
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
85
+ this.ws.send(sendData);
86
+ }
87
+ else {
88
+ console.warn('WebSocket 未连接,消息已加入队列');
89
+ this.messageQueue.push(sendData);
90
+ }
91
+ }
92
+ /**
93
+ * 检查是否已连接
94
+ */
95
+ isConnected() {
96
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
97
+ }
98
+ /**
99
+ * 获取连接状态
100
+ */
101
+ getReadyState() {
102
+ return this.ws ? this.ws.readyState : WebSocket.CLOSED;
103
+ }
104
+ /**
105
+ * 注册事件监听
106
+ */
107
+ on(event, handler) {
108
+ if (!this.eventHandlers.has(event)) {
109
+ this.eventHandlers.set(event, new Set());
110
+ }
111
+ this.eventHandlers.get(event).add(handler);
112
+ }
113
+ /**
114
+ * 移除事件监听
115
+ */
116
+ off(event, handler) {
117
+ if (!handler) {
118
+ // 移除该事件的所有监听器
119
+ this.eventHandlers.delete(event);
120
+ }
121
+ else {
122
+ // 移除特定监听器
123
+ const handlers = this.eventHandlers.get(event);
124
+ if (handlers) {
125
+ handlers.delete(handler);
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * 配置 WebSocket
131
+ */
132
+ configure(config) {
133
+ if (config.reconnect !== undefined) {
134
+ this.reconnect = config.reconnect;
135
+ }
136
+ if (config.reconnectInterval !== undefined) {
137
+ this.reconnectInterval = config.reconnectInterval;
138
+ }
139
+ if (config.reconnectAttempts !== undefined) {
140
+ this.reconnectAttempts = config.reconnectAttempts;
141
+ }
142
+ if (config.heartbeat !== undefined) {
143
+ this.heartbeat = config.heartbeat;
144
+ }
145
+ if (config.heartbeatInterval !== undefined) {
146
+ this.heartbeatInterval = config.heartbeatInterval;
147
+ }
148
+ if (config.heartbeatMessage !== undefined) {
149
+ this.heartbeatMessage = config.heartbeatMessage;
150
+ }
151
+ }
152
+ /**
153
+ * 创建 WebSocket 连接
154
+ */
155
+ _createWebSocket() {
156
+ try {
157
+ this.ws = this.protocols
158
+ ? new WebSocket(this.url, this.protocols)
159
+ : new WebSocket(this.url);
160
+ this.ws.onopen = this._onOpen.bind(this);
161
+ this.ws.onmessage = this._onMessage.bind(this);
162
+ this.ws.onerror = this._onError.bind(this);
163
+ this.ws.onclose = this._onClose.bind(this);
164
+ console.log(`🔌 正在连接 WebSocket: ${this.url}`);
165
+ }
166
+ catch (error) {
167
+ console.error('创建 WebSocket 失败:', error);
168
+ this._emit('error', error);
169
+ }
170
+ }
171
+ /**
172
+ * 连接成功
173
+ */
174
+ _onOpen(event) {
175
+ console.log('✅ WebSocket 连接成功');
176
+ this.currentReconnectAttempts = 0;
177
+ // 启动心跳
178
+ if (this.heartbeat) {
179
+ this._startHeartbeat();
180
+ }
181
+ // 发送队列中的消息
182
+ this._sendQueuedMessages();
183
+ // 触发 open 事件
184
+ this._emit('open', event);
185
+ }
186
+ /**
187
+ * 收到消息
188
+ */
189
+ _onMessage(event) {
190
+ console.log('📨 收到消息:', event.data);
191
+ this._emit('message', event);
192
+ }
193
+ /**
194
+ * 连接错误
195
+ */
196
+ _onError(event) {
197
+ console.error('❌ WebSocket 错误:', event);
198
+ this._emit('error', event);
199
+ }
200
+ /**
201
+ * 连接关闭
202
+ */
203
+ _onClose(event) {
204
+ console.log(`🔌 WebSocket 连接关闭: code=${event.code}, reason=${event.reason}`);
205
+ this._stopHeartbeat();
206
+ // 触发 close 事件
207
+ this._emit('close', event);
208
+ // 尝试重连
209
+ if (this.reconnect && this.currentReconnectAttempts < this.reconnectAttempts) {
210
+ this.currentReconnectAttempts++;
211
+ console.log(`🔄 尝试重连 (${this.currentReconnectAttempts}/${this.reconnectAttempts})...`);
212
+ this.reconnectTimer = setTimeout(() => {
213
+ this._createWebSocket();
214
+ }, this.reconnectInterval);
215
+ }
216
+ else if (this.currentReconnectAttempts >= this.reconnectAttempts) {
217
+ console.error('❌ 达到最大重连次数,停止重连');
218
+ }
219
+ }
220
+ /**
221
+ * 触发事件
222
+ */
223
+ _emit(event, data) {
224
+ const handlers = this.eventHandlers.get(event);
225
+ if (handlers) {
226
+ handlers.forEach(handler => {
227
+ try {
228
+ handler(data);
229
+ }
230
+ catch (error) {
231
+ console.error(`事件处理器错误 [${event}]:`, error);
232
+ }
233
+ });
234
+ }
235
+ }
236
+ /**
237
+ * 启动心跳
238
+ */
239
+ _startHeartbeat() {
240
+ this._stopHeartbeat();
241
+ this.heartbeatTimer = setInterval(() => {
242
+ if (this.isConnected()) {
243
+ console.log('💓 发送心跳');
244
+ this.send(this.heartbeatMessage);
245
+ }
246
+ }, this.heartbeatInterval);
247
+ }
248
+ /**
249
+ * 停止心跳
250
+ */
251
+ _stopHeartbeat() {
252
+ if (this.heartbeatTimer) {
253
+ clearInterval(this.heartbeatTimer);
254
+ this.heartbeatTimer = null;
255
+ }
256
+ }
257
+ /**
258
+ * 发送队列中的消息
259
+ */
260
+ _sendQueuedMessages() {
261
+ if (this.messageQueue.length > 0) {
262
+ console.log(`📤 发送队列中的 ${this.messageQueue.length} 条消息`);
263
+ while (this.messageQueue.length > 0) {
264
+ const message = this.messageQueue.shift();
265
+ if (message) {
266
+ this.send(message);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ export { WebSocketManager };
@@ -5,3 +5,4 @@ export * from './ResLoader';
5
5
  export * from './UIManager';
6
6
  export * from './UIRoot';
7
7
  export * from './HttpManager';
8
+ export * from './WebSocketManager';
@@ -5,3 +5,4 @@ export { ResLoader } from './ResLoader.js';
5
5
  export { UIManager } from './UIManager.js';
6
6
  export { UIRoot } from './UIRoot.js';
7
7
  export { HttpManager } from './HttpManager.js';
8
+ export { WebSocketManager } from './WebSocketManager.js';
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dzkcc-mflow",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "A modular design and process management framework developed for the cocos engine, suitable for decoupling and dependency injection.",
5
5
  "author": "duanzhk",
6
6
  "license": "MIT",