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 +369 -11
- package/dist/App.d.ts +2 -2
- package/dist/App.js +3 -1
- package/dist/core/Api.d.ts +20 -0
- package/dist/core/Core.d.ts +2 -1
- package/dist/core/Core.js +4 -0
- package/dist/libs/CocosCore.js +2 -0
- package/dist/libs/WebSocketManager.d.ts +101 -0
- package/dist/libs/WebSocketManager.js +273 -0
- package/dist/libs/index.d.ts +1 -0
- package/dist/libs/index.js +1 -0
- package/dist/mflow-tools.zip +0 -0
- package/package.json +1 -1
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
|
-
###
|
|
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
|
-
##
|
|
634
|
+
## 9. 完整示例
|
|
280
635
|
|
|
281
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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.
|
|
758
|
+
7. **实时通信**:使用WebSocketManager处理实时消息,配合事件系统分发
|
|
759
|
+
8. **工具辅助**:使用mflow-tools提高开发效率
|
|
404
760
|
|
|
405
|
-
##
|
|
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
|
|
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;
|
package/dist/core/Api.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/Core.d.ts
CHANGED
|
@@ -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) {
|
package/dist/libs/CocosCore.js
CHANGED
|
@@ -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 };
|
package/dist/libs/index.d.ts
CHANGED
package/dist/libs/index.js
CHANGED
package/dist/mflow-tools.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED