@roomkit/gateway 1.0.0
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 +93 -0
- package/dist/src/admin/admin.controller.d.ts +187 -0
- package/dist/src/admin/admin.controller.d.ts.map +1 -0
- package/dist/src/admin/admin.controller.js +187 -0
- package/dist/src/admin/admin.controller.js.map +1 -0
- package/dist/src/admin/admin.module.d.ts +3 -0
- package/dist/src/admin/admin.module.d.ts.map +1 -0
- package/dist/src/admin/admin.module.js +28 -0
- package/dist/src/admin/admin.module.js.map +1 -0
- package/dist/src/admin/admin.service.d.ts +196 -0
- package/dist/src/admin/admin.service.d.ts.map +1 -0
- package/dist/src/admin/admin.service.js +322 -0
- package/dist/src/admin/admin.service.js.map +1 -0
- package/dist/src/config/config.module.d.ts +3 -0
- package/dist/src/config/config.module.d.ts.map +1 -0
- package/dist/src/config/config.module.js +22 -0
- package/dist/src/config/config.module.js.map +1 -0
- package/dist/src/config/config.service.d.ts +19 -0
- package/dist/src/config/config.service.d.ts.map +1 -0
- package/dist/src/config/config.service.js +55 -0
- package/dist/src/config/config.service.js.map +1 -0
- package/dist/src/config/gateway.config.d.ts +64 -0
- package/dist/src/config/gateway.config.d.ts.map +1 -0
- package/dist/src/config/gateway.config.js +43 -0
- package/dist/src/config/gateway.config.js.map +1 -0
- package/dist/src/connection/connection.module.d.ts +3 -0
- package/dist/src/connection/connection.module.d.ts.map +1 -0
- package/dist/src/connection/connection.module.js +22 -0
- package/dist/src/connection/connection.module.js.map +1 -0
- package/dist/src/connection/connection.service.d.ts +93 -0
- package/dist/src/connection/connection.service.d.ts.map +1 -0
- package/dist/src/connection/connection.service.js +257 -0
- package/dist/src/connection/connection.service.js.map +1 -0
- package/dist/src/gateway.module.d.ts +8 -0
- package/dist/src/gateway.module.d.ts.map +1 -0
- package/dist/src/gateway.module.js +44 -0
- package/dist/src/gateway.module.js.map +1 -0
- package/dist/src/index.d.ts +38 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +97 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lifecycle/graceful-shutdown.service.d.ts +65 -0
- package/dist/src/lifecycle/graceful-shutdown.service.d.ts.map +1 -0
- package/dist/src/lifecycle/graceful-shutdown.service.js +187 -0
- package/dist/src/lifecycle/graceful-shutdown.service.js.map +1 -0
- package/dist/src/lifecycle/index.d.ts +2 -0
- package/dist/src/lifecycle/index.d.ts.map +1 -0
- package/dist/src/lifecycle/index.js +18 -0
- package/dist/src/lifecycle/index.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/main.js +56 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/metrics/metrics.controller.d.ts +13 -0
- package/dist/src/metrics/metrics.controller.d.ts.map +1 -0
- package/dist/src/metrics/metrics.controller.js +42 -0
- package/dist/src/metrics/metrics.controller.js.map +1 -0
- package/dist/src/metrics/metrics.module.d.ts +3 -0
- package/dist/src/metrics/metrics.module.d.ts.map +1 -0
- package/dist/src/metrics/metrics.module.js +26 -0
- package/dist/src/metrics/metrics.module.js.map +1 -0
- package/dist/src/metrics/metrics.service.d.ts +81 -0
- package/dist/src/metrics/metrics.service.d.ts.map +1 -0
- package/dist/src/metrics/metrics.service.js +255 -0
- package/dist/src/metrics/metrics.service.js.map +1 -0
- package/dist/src/ratelimit/rate-limit.module.d.ts +3 -0
- package/dist/src/ratelimit/rate-limit.module.d.ts.map +1 -0
- package/dist/src/ratelimit/rate-limit.module.js +23 -0
- package/dist/src/ratelimit/rate-limit.module.js.map +1 -0
- package/dist/src/ratelimit/rate-limiter.service.d.ts +68 -0
- package/dist/src/ratelimit/rate-limiter.service.d.ts.map +1 -0
- package/dist/src/ratelimit/rate-limiter.service.js +201 -0
- package/dist/src/ratelimit/rate-limiter.service.js.map +1 -0
- package/dist/src/redis/redis.module.d.ts +3 -0
- package/dist/src/redis/redis.module.d.ts.map +1 -0
- package/dist/src/redis/redis.module.js +22 -0
- package/dist/src/redis/redis.module.js.map +1 -0
- package/dist/src/redis/redis.service.d.ts +68 -0
- package/dist/src/redis/redis.service.d.ts.map +1 -0
- package/dist/src/redis/redis.service.js +281 -0
- package/dist/src/redis/redis.service.js.map +1 -0
- package/dist/src/session/session.module.d.ts +3 -0
- package/dist/src/session/session.module.d.ts.map +1 -0
- package/dist/src/session/session.module.js +21 -0
- package/dist/src/session/session.module.js.map +1 -0
- package/dist/src/session/session.service.d.ts +64 -0
- package/dist/src/session/session.service.d.ts.map +1 -0
- package/dist/src/session/session.service.js +206 -0
- package/dist/src/session/session.service.js.map +1 -0
- package/dist/src/ws/message-router.service.d.ts +133 -0
- package/dist/src/ws/message-router.service.d.ts.map +1 -0
- package/dist/src/ws/message-router.service.js +715 -0
- package/dist/src/ws/message-router.service.js.map +1 -0
- package/dist/src/ws/response-handler.service.d.ts +53 -0
- package/dist/src/ws/response-handler.service.d.ts.map +1 -0
- package/dist/src/ws/response-handler.service.js +282 -0
- package/dist/src/ws/response-handler.service.js.map +1 -0
- package/dist/src/ws/ws.gateway.d.ts +46 -0
- package/dist/src/ws/ws.gateway.d.ts.map +1 -0
- package/dist/src/ws/ws.gateway.js +244 -0
- package/dist/src/ws/ws.gateway.js.map +1 -0
- package/dist/src/ws/ws.module.d.ts +8 -0
- package/dist/src/ws/ws.module.d.ts.map +1 -0
- package/dist/src/ws/ws.module.js +39 -0
- package/dist/src/ws/ws.module.js.map +1 -0
- package/dist/test/connection/connection.service.spec.d.ts +2 -0
- package/dist/test/connection/connection.service.spec.d.ts.map +1 -0
- package/dist/test/connection/connection.service.spec.js +204 -0
- package/dist/test/connection/connection.service.spec.js.map +1 -0
- package/dist/test/e2e/gateway-worker.e2e.spec.d.ts +2 -0
- package/dist/test/e2e/gateway-worker.e2e.spec.d.ts.map +1 -0
- package/dist/test/e2e/gateway-worker.e2e.spec.js +412 -0
- package/dist/test/e2e/gateway-worker.e2e.spec.js.map +1 -0
- package/dist/test/integration/admin-api.spec.d.ts +2 -0
- package/dist/test/integration/admin-api.spec.d.ts.map +1 -0
- package/dist/test/integration/admin-api.spec.js +218 -0
- package/dist/test/integration/admin-api.spec.js.map +1 -0
- package/dist/test/ratelimit/rate-limiter.service.spec.d.ts +2 -0
- package/dist/test/ratelimit/rate-limiter.service.spec.d.ts.map +1 -0
- package/dist/test/ratelimit/rate-limiter.service.spec.js +139 -0
- package/dist/test/ratelimit/rate-limiter.service.spec.js.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +56 -0
- package/dist/test/setup.js.map +1 -0
- package/dist/test/ws/message-router.service.spec.d.ts +2 -0
- package/dist/test/ws/message-router.service.spec.d.ts.map +1 -0
- package/dist/test/ws/message-router.service.spec.js +403 -0
- package/dist/test/ws/message-router.service.spec.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var MessageRouterService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MessageRouterService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const uuid_1 = require("uuid");
|
|
16
|
+
const core_1 = require("@roomkit/core");
|
|
17
|
+
const config_service_1 = require("../config/config.service");
|
|
18
|
+
const redis_service_1 = require("../redis/redis.service");
|
|
19
|
+
const connection_service_1 = require("../connection/connection.service");
|
|
20
|
+
const session_service_1 = require("../session/session.service");
|
|
21
|
+
let MessageRouterService = MessageRouterService_1 = class MessageRouterService {
|
|
22
|
+
configService;
|
|
23
|
+
redisService;
|
|
24
|
+
connectionService;
|
|
25
|
+
sessionService;
|
|
26
|
+
logger = new common_1.Logger(MessageRouterService_1.name);
|
|
27
|
+
// 房间路由缓存 (roomId -> workerId)
|
|
28
|
+
roomRoutes = new Map();
|
|
29
|
+
constructor(configService, redisService, connectionService, sessionService) {
|
|
30
|
+
this.configService = configService;
|
|
31
|
+
this.redisService = redisService;
|
|
32
|
+
this.connectionService = connectionService;
|
|
33
|
+
this.sessionService = sessionService;
|
|
34
|
+
// 监听路由更新
|
|
35
|
+
this.initializeRouteUpdateListener();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 初始化路由更新监听
|
|
39
|
+
*/
|
|
40
|
+
initializeRouteUpdateListener() {
|
|
41
|
+
this.redisService.onMessage(core_1.RedisChannels.routeUpdate(), (message) => {
|
|
42
|
+
try {
|
|
43
|
+
const update = JSON.parse(message);
|
|
44
|
+
this.handleRouteUpdate(update);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
this.logger.error('Failed to parse route update:', error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 处理路由更新
|
|
53
|
+
*/
|
|
54
|
+
handleRouteUpdate(update) {
|
|
55
|
+
if (update.type === 'room-migrated') {
|
|
56
|
+
const { roomId, oldWorkerId, newWorkerId } = update;
|
|
57
|
+
this.logger.log(`Route updated: room ${roomId} migrated from ${oldWorkerId} to ${newWorkerId}`);
|
|
58
|
+
// 更新本地缓存
|
|
59
|
+
this.roomRoutes.set(roomId, newWorkerId);
|
|
60
|
+
// 通知房间内的玩家(可选)
|
|
61
|
+
// 可以发送一个迁移通知,让客户端知道房间已迁移
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 处理客户端消息
|
|
66
|
+
* 使用字符串消息类型格式:{ type: 'gw:auth', payload: {...} }
|
|
67
|
+
*/
|
|
68
|
+
async handleMessage(connection, message) {
|
|
69
|
+
// 提取消息类型
|
|
70
|
+
const msgType = message.msgType;
|
|
71
|
+
const payload = message.payload;
|
|
72
|
+
// 更新活动时间
|
|
73
|
+
this.connectionService.touch(connection.id);
|
|
74
|
+
// 智能刷新session TTL(如果用户已认证)
|
|
75
|
+
// 只在TTL剩余不足2分钟时才刷新,避免频繁写入
|
|
76
|
+
if (connection.userId) {
|
|
77
|
+
await this.sessionService.refreshSession(connection.userId, false);
|
|
78
|
+
}
|
|
79
|
+
// 必须有消息类型
|
|
80
|
+
if (!msgType) {
|
|
81
|
+
this.sendErrorByType(connection.id, 'INVALID_MESSAGE_FORMAT');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 认证消息
|
|
85
|
+
if (msgType === core_1.MessageType.AUTH) {
|
|
86
|
+
await this.handleAuthRequest(connection, payload);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// 心跳消息
|
|
90
|
+
if (msgType === core_1.MessageType.HEARTBEAT) {
|
|
91
|
+
const heartbeatPayload = payload;
|
|
92
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.HEARTBEAT_ACK, {
|
|
93
|
+
timestamp: heartbeatPayload?.timestamp,
|
|
94
|
+
serverTime: Date.now(),
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// ========== 连接层认证必须先完成 ==========
|
|
99
|
+
// 所有非 auth/heartbeat 消息都要求用户已认证
|
|
100
|
+
// 这确保了:
|
|
101
|
+
// 1. userId 在 Gateway 层确定,不可更改
|
|
102
|
+
// 2. 同一连接只有一个 userId
|
|
103
|
+
// 3. Session 正确写入
|
|
104
|
+
if (connection.status !== 'identified' && connection.status !== 'in-room') {
|
|
105
|
+
this.sendErrorByType(connection.id, 'NOT_AUTHENTICATED');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// ========== 房间管理消息 ==========
|
|
109
|
+
if (msgType === core_1.MessageType.MSG_ROOM_JOIN) {
|
|
110
|
+
await this.handleJoinRoom(connection, payload);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (msgType === core_1.MessageType.MSG_ROOM_JOIN_OR_CREATE) {
|
|
114
|
+
await this.handleJoinOrCreate(connection, payload, message.token, message.reqId);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (msgType === core_1.MessageType.ROOM_CREATE) {
|
|
118
|
+
await this.handleCreateRoom(connection, payload);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (msgType === core_1.MessageType.ROOM_LEAVE) {
|
|
122
|
+
await this.handleLeaveRoom(connection, payload, message.roomId);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (msgType === core_1.MessageType.ROOM_LIST) {
|
|
126
|
+
await this.handleRoomList(connection, payload);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (msgType === core_1.MessageType.RECONNECT) {
|
|
130
|
+
await this.handleReconnect(connection, payload);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// 游戏逻辑消息:转发到Worker
|
|
134
|
+
const targetRoomId = message.roomId || (connection.rooms?.size === 1 ? [...connection.rooms][0] : undefined);
|
|
135
|
+
if (!targetRoomId) {
|
|
136
|
+
this.sendErrorByType(connection.id, 'ROOM_ID_REQUIRED');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!connection.rooms?.has(targetRoomId)) {
|
|
140
|
+
this.sendErrorByType(connection.id, 'NOT_IN_ROOM');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await this.forwardToWorkerByType(connection, msgType, payload, targetRoomId, message.token);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 发送错误消息
|
|
147
|
+
*/
|
|
148
|
+
sendErrorByType(connectionId, error) {
|
|
149
|
+
this.connectionService.sendByType(connectionId, core_1.MessageType.ERROR, {
|
|
150
|
+
error,
|
|
151
|
+
serverTime: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 转发消息到 Worker
|
|
156
|
+
*/
|
|
157
|
+
async forwardToWorkerByType(connection, msgType, payload, roomId, token) {
|
|
158
|
+
// 优先使用缓存的路由
|
|
159
|
+
let workerId = this.roomRoutes.get(roomId);
|
|
160
|
+
// 如果缓存中没有,从 Redis 查询
|
|
161
|
+
if (!workerId) {
|
|
162
|
+
const result = await this.redisService.client.get(core_1.RedisKeys.roomWorker(roomId));
|
|
163
|
+
workerId = result || undefined;
|
|
164
|
+
if (workerId) {
|
|
165
|
+
// 更新缓存
|
|
166
|
+
this.roomRoutes.set(roomId, workerId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!workerId) {
|
|
170
|
+
this.sendErrorByType(connection.id, 'ROOM_NOT_FOUND');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const message = {
|
|
174
|
+
type: 'client-message',
|
|
175
|
+
requestId: (0, uuid_1.v4)(),
|
|
176
|
+
gatewayId: this.configService.gatewayId,
|
|
177
|
+
connectionId: connection.id,
|
|
178
|
+
userId: connection.userId,
|
|
179
|
+
roomId,
|
|
180
|
+
msgType,
|
|
181
|
+
payload,
|
|
182
|
+
timestamp: Date.now(),
|
|
183
|
+
token,
|
|
184
|
+
};
|
|
185
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 处理鉴权请求
|
|
189
|
+
*/
|
|
190
|
+
async handleAuthRequest(connection, payload) {
|
|
191
|
+
if (connection.status !== 'connected') {
|
|
192
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.AUTH_FAIL, {
|
|
193
|
+
success: false,
|
|
194
|
+
error: 'INVALID_STATE',
|
|
195
|
+
serverTime: Date.now(),
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// 验证请求格式
|
|
200
|
+
const parseResult = core_1.AuthRequestSchema.safeParse(payload);
|
|
201
|
+
if (!parseResult.success) {
|
|
202
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.AUTH_FAIL, {
|
|
203
|
+
success: false,
|
|
204
|
+
error: 'INVALID_PAYLOAD',
|
|
205
|
+
serverTime: Date.now(),
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.connectionService.updateStatus(connection.id, 'authenticating');
|
|
210
|
+
// 选择一个Worker处理鉴权
|
|
211
|
+
const workerId = await this.selectAuthWorker();
|
|
212
|
+
if (!workerId) {
|
|
213
|
+
this.connectionService.updateStatus(connection.id, 'connected');
|
|
214
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.AUTH_FAIL, {
|
|
215
|
+
success: false,
|
|
216
|
+
error: 'NO_WORKER_AVAILABLE',
|
|
217
|
+
serverTime: Date.now(),
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
// 转发鉴权请求到Worker
|
|
222
|
+
const message = {
|
|
223
|
+
type: 'auth', // NOTICE: 鉴权消息类型是否有必要和MessageType.AUTH一致?
|
|
224
|
+
requestId: (0, uuid_1.v4)(),
|
|
225
|
+
gatewayId: this.configService.gatewayId,
|
|
226
|
+
connectionId: connection.id,
|
|
227
|
+
msgType: core_1.MessageType.AUTH,
|
|
228
|
+
payload: parseResult.data,
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
};
|
|
231
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 处理房间列表请求
|
|
235
|
+
*/
|
|
236
|
+
async handleRoomList(connection, payload) {
|
|
237
|
+
// TODO: 实现房间列表查询
|
|
238
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_ROOM_LIST_OK, {
|
|
239
|
+
rooms: [],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 处理重连请求
|
|
244
|
+
*
|
|
245
|
+
* 统一的重连处理策略:
|
|
246
|
+
*
|
|
247
|
+
* **客户端视角**:
|
|
248
|
+
* - 客户端只知道 WebSocket 断开了
|
|
249
|
+
* - 无法区分是自己网络问题还是 Gateway 重启
|
|
250
|
+
* - 只需发送 sessionToken,不需要指定重连类型
|
|
251
|
+
*
|
|
252
|
+
* **服务端处理**:
|
|
253
|
+
* - Gateway 收到重连请求时,内存中已无旧 Connection 对象
|
|
254
|
+
* - 无论是客户端断连还是 Gateway 重启,处理流程相同:
|
|
255
|
+
* 1. 恢复 Gateway 本地连接映射
|
|
256
|
+
* 2. 通知 Worker 更新连接信息(Gateway ID + Connection ID)
|
|
257
|
+
* 3. 不发送房间状态(客户端已有状态)
|
|
258
|
+
*
|
|
259
|
+
* **Worker 重启场景**:
|
|
260
|
+
* - 不通过重连机制处理
|
|
261
|
+
* - Worker 应该有独立的容错恢复机制:
|
|
262
|
+
* - 启动时从 Redis/持久化恢复房间状态
|
|
263
|
+
* - 或延迟加载:收到消息时发现房间不存在,再恢复
|
|
264
|
+
* - 对客户端和 Gateway 透明
|
|
265
|
+
*/
|
|
266
|
+
async handleReconnect(connection, payload) {
|
|
267
|
+
// 只允许未认证的连接进行重连
|
|
268
|
+
if (connection.status !== 'connected') {
|
|
269
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_RECONNECT_FAIL, {
|
|
270
|
+
success: false,
|
|
271
|
+
error: 'INVALID_STATE',
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const request = payload;
|
|
276
|
+
if (!request?.sessionToken) {
|
|
277
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_RECONNECT_FAIL, {
|
|
278
|
+
success: false,
|
|
279
|
+
error: 'INVALID_PAYLOAD',
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// 从 Redis 获取会话信息
|
|
284
|
+
const session = await this.sessionService.getSessionByToken(request.sessionToken);
|
|
285
|
+
if (!session) {
|
|
286
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_RECONNECT_FAIL, {
|
|
287
|
+
success: false,
|
|
288
|
+
error: 'SESSION_NOT_FOUND',
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const { userId, rooms } = session;
|
|
293
|
+
// 踢掉旧连接(如果存在且还活着)
|
|
294
|
+
if (session.connectionId && session.gatewayId) {
|
|
295
|
+
if (session.gatewayId === this.configService.gatewayId) {
|
|
296
|
+
// 本地 Gateway,检查连接是否还存在
|
|
297
|
+
const oldConnection = this.connectionService.getConnection(session.connectionId);
|
|
298
|
+
if (oldConnection) {
|
|
299
|
+
this.logger.log(`Kicking old connection ${session.connectionId} during reconnect for user ${userId}`);
|
|
300
|
+
this.connectionService.removeConnection(session.connectionId, 'reconnect-elsewhere');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// 其他 Gateway,发送踢出消息(可能 Gateway 已重启,消息会丢失,无所谓)
|
|
305
|
+
const kickMessage = {
|
|
306
|
+
type: 'kick-connection',
|
|
307
|
+
requestId: (0, uuid_1.v4)(),
|
|
308
|
+
gatewayId: this.configService.gatewayId,
|
|
309
|
+
connectionId: session.connectionId,
|
|
310
|
+
payload: { reason: 'reconnect-elsewhere' },
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
};
|
|
313
|
+
await this.redisService.publish(core_1.RedisChannels.rpcGateway(session.gatewayId), kickMessage);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// 更新连接状态
|
|
317
|
+
this.connectionService.setUserId(connection.id, userId);
|
|
318
|
+
this.connectionService.updateStatus(connection.id, 'identified');
|
|
319
|
+
// 更新会话信息
|
|
320
|
+
await this.sessionService.updateConnectionInfo(userId, this.configService.gatewayId, connection.id);
|
|
321
|
+
// 恢复房间连接
|
|
322
|
+
const roomsInfo = [];
|
|
323
|
+
for (const roomInfo of rooms) {
|
|
324
|
+
const { roomId, gameType } = roomInfo;
|
|
325
|
+
// 验证房间是否还存在
|
|
326
|
+
const workerId = await this.redisService.client.get(core_1.RedisKeys.roomWorker(roomId));
|
|
327
|
+
if (!workerId) {
|
|
328
|
+
this.logger.warn(`Room ${roomId} not found during reconnect for user ${userId}`);
|
|
329
|
+
// 房间已不存在,从会话中移除
|
|
330
|
+
await this.sessionService.removeRoomFromSession(userId, roomId);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
// 更新本地连接的房间信息
|
|
334
|
+
this.connectionService.addRoom(connection.id, roomId);
|
|
335
|
+
// 通知 Worker 更新房间成员的连接信息(轻量级通知,不发送状态)
|
|
336
|
+
const updateMessage = {
|
|
337
|
+
type: 'reconnect-room',
|
|
338
|
+
requestId: (0, uuid_1.v4)(),
|
|
339
|
+
gatewayId: this.configService.gatewayId,
|
|
340
|
+
connectionId: connection.id,
|
|
341
|
+
userId,
|
|
342
|
+
roomId,
|
|
343
|
+
payload: {
|
|
344
|
+
sendState: false, // 明确告知不需要发送状态
|
|
345
|
+
},
|
|
346
|
+
timestamp: Date.now(),
|
|
347
|
+
};
|
|
348
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), updateMessage);
|
|
349
|
+
roomsInfo.push({ roomId, gameType });
|
|
350
|
+
}
|
|
351
|
+
// 发送重连成功响应
|
|
352
|
+
const response = {
|
|
353
|
+
userId,
|
|
354
|
+
rooms: roomsInfo,
|
|
355
|
+
};
|
|
356
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_RECONNECT_OK, response);
|
|
357
|
+
// 强制刷新 session TTL(重连后重置过期时间)
|
|
358
|
+
await this.sessionService.refreshSession(userId, true);
|
|
359
|
+
this.logger.log(`User ${userId} reconnected successfully, rejoined ${roomsInfo.length} rooms`);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 创建房间
|
|
363
|
+
*/
|
|
364
|
+
async handleCreateRoom(connection, payload) {
|
|
365
|
+
const parseResult = core_1.CreateRoomRequestSchema.safeParse(payload);
|
|
366
|
+
if (!parseResult.success) {
|
|
367
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_CREATE_FAIL, {
|
|
368
|
+
success: false,
|
|
369
|
+
error: 'INVALID_PAYLOAD',
|
|
370
|
+
});
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const { roomId: customRoomId, gameType, maxPlayers, config } = parseResult.data;
|
|
374
|
+
// 使用客户端指定的房间号,或自动生成
|
|
375
|
+
const roomId = customRoomId || `room_${(0, uuid_1.v4)().substring(0, 8)}`;
|
|
376
|
+
// 选择合适的Worker
|
|
377
|
+
const workerId = await this.selectWorker(gameType);
|
|
378
|
+
if (!workerId) {
|
|
379
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_CREATE_FAIL, {
|
|
380
|
+
success: false,
|
|
381
|
+
error: 'NO_WORKER_AVAILABLE',
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
// 使用 SETNX 原子操作:只有当 key 不存在时才设置成功
|
|
386
|
+
// 这样可以防止并发创建相同房间号的竞态条件
|
|
387
|
+
const setResult = await this.redisService.client.setnx(core_1.RedisKeys.roomWorker(roomId), workerId);
|
|
388
|
+
if (setResult === 0) {
|
|
389
|
+
// key 已存在,说明房间已被创建
|
|
390
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_CREATE_FAIL, {
|
|
391
|
+
success: false,
|
|
392
|
+
error: 'ROOM_ALREADY_EXISTS',
|
|
393
|
+
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// 转发创建请求到Worker
|
|
397
|
+
const message = {
|
|
398
|
+
type: 'client-message',
|
|
399
|
+
requestId: (0, uuid_1.v4)(),
|
|
400
|
+
gatewayId: this.configService.gatewayId,
|
|
401
|
+
connectionId: connection.id,
|
|
402
|
+
userId: connection.userId,
|
|
403
|
+
roomId,
|
|
404
|
+
msgType: core_1.MessageType.ROOM_CREATE,
|
|
405
|
+
payload: { roomId, gameType, maxPlayers, config },
|
|
406
|
+
timestamp: Date.now(),
|
|
407
|
+
};
|
|
408
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 加入房间
|
|
412
|
+
*/
|
|
413
|
+
async handleJoinRoom(connection, payload) {
|
|
414
|
+
const parseResult = core_1.JoinRoomRequestSchema.safeParse(payload);
|
|
415
|
+
if (!parseResult.success) {
|
|
416
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_JOIN_FAIL, {
|
|
417
|
+
success: false,
|
|
418
|
+
error: 'INVALID_PAYLOAD',
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const { roomId } = parseResult.data;
|
|
423
|
+
// 查询房间所在Worker
|
|
424
|
+
const workerId = await this.redisService.client.get(core_1.RedisKeys.roomWorker(roomId));
|
|
425
|
+
if (!workerId) {
|
|
426
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_JOIN_FAIL, {
|
|
427
|
+
success: false,
|
|
428
|
+
error: 'ROOM_NOT_FOUND',
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// 转发加入请求
|
|
433
|
+
const message = {
|
|
434
|
+
type: 'client-message',
|
|
435
|
+
requestId: (0, uuid_1.v4)(),
|
|
436
|
+
gatewayId: this.configService.gatewayId,
|
|
437
|
+
connectionId: connection.id,
|
|
438
|
+
userId: connection.userId,
|
|
439
|
+
roomId,
|
|
440
|
+
msgType: core_1.MessageType.ROOM_JOIN,
|
|
441
|
+
payload: parseResult.data,
|
|
442
|
+
timestamp: Date.now(),
|
|
443
|
+
};
|
|
444
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 离开房间
|
|
448
|
+
*
|
|
449
|
+
* 多房间模式:需要指定要离开的 roomId
|
|
450
|
+
* 单房间兼容:如果只在一个房间,可以不指定
|
|
451
|
+
*/
|
|
452
|
+
async handleLeaveRoom(connection, payload, messageRoomId) {
|
|
453
|
+
// 从 payload 或 message.roomId 获取要离开的房间
|
|
454
|
+
const payloadRoomId = payload?.roomId;
|
|
455
|
+
const targetRoomId = payloadRoomId || messageRoomId ||
|
|
456
|
+
(connection.rooms.size === 1 ? [...connection.rooms][0] : undefined);
|
|
457
|
+
if (!targetRoomId) {
|
|
458
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_ERROR, {
|
|
459
|
+
success: false,
|
|
460
|
+
error: 'ROOM_ID_REQUIRED',
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (!connection.rooms.has(targetRoomId)) {
|
|
465
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.MSG_ERROR, {
|
|
466
|
+
success: false,
|
|
467
|
+
error: 'NOT_IN_ROOM',
|
|
468
|
+
});
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const workerId = await this.redisService.client.get(core_1.RedisKeys.roomWorker(targetRoomId));
|
|
472
|
+
if (workerId) {
|
|
473
|
+
const message = {
|
|
474
|
+
type: 'client-message',
|
|
475
|
+
requestId: (0, uuid_1.v4)(),
|
|
476
|
+
gatewayId: this.configService.gatewayId,
|
|
477
|
+
connectionId: connection.id,
|
|
478
|
+
userId: connection.userId,
|
|
479
|
+
roomId: targetRoomId,
|
|
480
|
+
msgType: core_1.MessageType.ROOM_LEAVE,
|
|
481
|
+
payload: { roomId: targetRoomId },
|
|
482
|
+
timestamp: Date.now(),
|
|
483
|
+
};
|
|
484
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* 转发消息到Worker
|
|
489
|
+
*
|
|
490
|
+
* @param connection 连接
|
|
491
|
+
* @param msgType 消息类型
|
|
492
|
+
* @param payload 消息内容
|
|
493
|
+
* @param roomId 目标房间ID
|
|
494
|
+
*/
|
|
495
|
+
async forwardToWorker(connection, msgType, payload, roomId) {
|
|
496
|
+
const workerId = await this.redisService.client.get(core_1.RedisKeys.roomWorker(roomId));
|
|
497
|
+
if (!workerId) {
|
|
498
|
+
this.sendErrorByType(connection.id, 'ROOM_NOT_FOUND');
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const message = {
|
|
502
|
+
type: 'client-message',
|
|
503
|
+
requestId: (0, uuid_1.v4)(),
|
|
504
|
+
gatewayId: this.configService.gatewayId,
|
|
505
|
+
connectionId: connection.id,
|
|
506
|
+
userId: connection.userId,
|
|
507
|
+
roomId,
|
|
508
|
+
msgType,
|
|
509
|
+
payload,
|
|
510
|
+
timestamp: Date.now(),
|
|
511
|
+
};
|
|
512
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* 选择处理鉴权的Worker
|
|
516
|
+
*/
|
|
517
|
+
async selectAuthWorker() {
|
|
518
|
+
const workers = await this.getHealthyWorkers();
|
|
519
|
+
if (workers.length === 0)
|
|
520
|
+
return null;
|
|
521
|
+
return workers[Math.floor(Math.random() * workers.length)];
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* 根据游戏类型选择Worker
|
|
525
|
+
*/
|
|
526
|
+
async selectWorker(gameType) {
|
|
527
|
+
// 先尝试按游戏类型选择
|
|
528
|
+
let workers = await this.redisService.client.smembers(core_1.RedisKeys.workersByType(gameType));
|
|
529
|
+
// 过滤出健康的Worker
|
|
530
|
+
workers = await this.filterHealthyWorkers(workers);
|
|
531
|
+
// 如果没有专门的Worker,从所有Worker中选择
|
|
532
|
+
if (workers.length === 0) {
|
|
533
|
+
workers = await this.getHealthyWorkers();
|
|
534
|
+
}
|
|
535
|
+
if (workers.length === 0)
|
|
536
|
+
return null;
|
|
537
|
+
// TODO: 实现更智能的负载均衡
|
|
538
|
+
return workers[Math.floor(Math.random() * workers.length)];
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* 获取所有健康的Worker
|
|
542
|
+
*/
|
|
543
|
+
async getHealthyWorkers() {
|
|
544
|
+
const allWorkers = await this.redisService.client.smembers(core_1.RedisKeys.workersAll());
|
|
545
|
+
return this.filterHealthyWorkers(allWorkers);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 过滤出健康的Worker(心跳未超时且非draining状态)
|
|
549
|
+
*/
|
|
550
|
+
async filterHealthyWorkers(workerIds) {
|
|
551
|
+
const HEARTBEAT_TIMEOUT = 30000; // 30秒超时
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
const healthyWorkers = [];
|
|
554
|
+
for (const workerId of workerIds) {
|
|
555
|
+
const workerData = await this.redisService.client.hgetall(core_1.RedisKeys.worker(workerId));
|
|
556
|
+
if (!workerData || !workerData.lastHeartbeat) {
|
|
557
|
+
// 没有心跳信息,跳过
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
// 跳过draining状态的Worker
|
|
561
|
+
if (workerData.status === 'draining') {
|
|
562
|
+
this.logger.debug(`Worker ${workerId} is draining, skipping`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const lastHeartbeat = parseInt(workerData.lastHeartbeat);
|
|
566
|
+
if (now - lastHeartbeat <= HEARTBEAT_TIMEOUT) {
|
|
567
|
+
// 心跳正常且状态为active
|
|
568
|
+
healthyWorkers.push(workerId);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
this.logger.debug(`Worker ${workerId} heartbeat timeout: ${now - lastHeartbeat}ms`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return healthyWorkers;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* 加入或创建房间(Colyseus 风格的 concurrentJoinOrCreateRoomLock)
|
|
578
|
+
*
|
|
579
|
+
* 实现原理:
|
|
580
|
+
* 1. 使用 HINCRBY 原子递增计数器,返回值为 1 的是第一个请求(负责创建)
|
|
581
|
+
* 2. 其他请求(返回值 > 1)等待创建完成
|
|
582
|
+
* 3. 创建完成后,Worker 广播通知,所有等待者自动加入
|
|
583
|
+
* 4. 所有请求都发送到同一个 Worker,由 Worker 统一处理
|
|
584
|
+
*
|
|
585
|
+
* @param connection 客户端连接
|
|
586
|
+
* @param payload 请求载荷
|
|
587
|
+
* @param token 可选的认证令牌(用于 Room.onAuth)
|
|
588
|
+
*/
|
|
589
|
+
async handleJoinOrCreate(connection, payload, token, clientReqId) {
|
|
590
|
+
const parseResult = core_1.JoinOrCreateRequestSchema.safeParse(payload);
|
|
591
|
+
if (!parseResult.success) {
|
|
592
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_JOIN_FAIL, {
|
|
593
|
+
success: false,
|
|
594
|
+
error: 'INVALID_PAYLOAD',
|
|
595
|
+
});
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const { roomId, gameType, maxPlayers, config, waitTimeout = 5000 } = parseResult.data;
|
|
599
|
+
// 检查房间是否已经存在
|
|
600
|
+
const existingWorkerId = await this.redisService.client.get(core_1.RedisKeys.roomWorker(roomId));
|
|
601
|
+
if (existingWorkerId) {
|
|
602
|
+
// 房间已存在,直接加入
|
|
603
|
+
await this.forwardJoinRequest(connection, roomId, existingWorkerId, true, token);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
// 原子递增并发计数器,同时设置过期时间
|
|
607
|
+
const lockKey = core_1.RedisKeys.roomCreatingLock(roomId);
|
|
608
|
+
const lockTimeout = Math.ceil((waitTimeout + 5000) / 1000); // 转换为秒
|
|
609
|
+
// 使用 Lua 脚本实现原子的 HINCRBY + EXPIRE
|
|
610
|
+
const script = `
|
|
611
|
+
local count = redis.call('HINCRBY', KEYS[1], 'count', 1)
|
|
612
|
+
if count == 1 then
|
|
613
|
+
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
|
614
|
+
end
|
|
615
|
+
return count
|
|
616
|
+
`;
|
|
617
|
+
const requestCount = await this.redisService.client.eval(script, 1, lockKey, lockTimeout);
|
|
618
|
+
this.logger.debug(`[JoinOrCreate] ${connection.id} for room ${roomId}, requestCount=${requestCount}`);
|
|
619
|
+
// 选择 Worker(所有请求都发送到同一个 Worker)
|
|
620
|
+
let workerId = null;
|
|
621
|
+
if (requestCount === 1) {
|
|
622
|
+
// 第一个请求,选择 Worker 并记录
|
|
623
|
+
workerId = await this.selectWorker(gameType);
|
|
624
|
+
if (!workerId) {
|
|
625
|
+
// 清理锁
|
|
626
|
+
await this.redisService.client.del(lockKey);
|
|
627
|
+
this.logger.warn(`[JoinOrCreate] No worker available for gameType: ${gameType}`);
|
|
628
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_JOIN_FAIL, {
|
|
629
|
+
success: false,
|
|
630
|
+
error: 'NO_WORKER_AVAILABLE',
|
|
631
|
+
});
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
this.logger.debug(`[JoinOrCreate] Selected worker: ${workerId} for gameType: ${gameType}`);
|
|
635
|
+
await this.redisService.client.hset(lockKey, 'workerId', workerId);
|
|
636
|
+
// 预先设置房间到 Worker 的映射
|
|
637
|
+
await this.redisService.client.set(core_1.RedisKeys.roomWorker(roomId), workerId);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
// 等待 workerId 被设置(第一个请求可能还没选好)
|
|
641
|
+
// 最多等待 500ms,每 5ms 检查一次
|
|
642
|
+
for (let i = 0; i < 100; i++) {
|
|
643
|
+
workerId = await this.redisService.client.hget(lockKey, 'workerId');
|
|
644
|
+
if (workerId)
|
|
645
|
+
break;
|
|
646
|
+
await new Promise(r => setTimeout(r, 5));
|
|
647
|
+
}
|
|
648
|
+
if (!workerId) {
|
|
649
|
+
this.connectionService.sendByType(connection.id, core_1.MessageType.ROOM_JOIN_FAIL, {
|
|
650
|
+
success: false,
|
|
651
|
+
error: 'WORKER_SELECTION_TIMEOUT',
|
|
652
|
+
});
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// 所有请求都转发到 Worker,由 Worker 统一处理
|
|
657
|
+
// Worker 会:
|
|
658
|
+
// - 对 requestCount=1 的请求:创建房间并加入
|
|
659
|
+
// - 对 requestCount>1 的请求:等待房间创建完成后加入
|
|
660
|
+
const message = {
|
|
661
|
+
type: 'client-message',
|
|
662
|
+
requestId: clientReqId || (0, uuid_1.v4)(), // 使用客户端的 reqId,如果没有则生成
|
|
663
|
+
gatewayId: this.configService.gatewayId,
|
|
664
|
+
connectionId: connection.id,
|
|
665
|
+
userId: connection.userId,
|
|
666
|
+
roomId,
|
|
667
|
+
msgType: core_1.MessageType.MSG_ROOM_JOIN_OR_CREATE,
|
|
668
|
+
payload: {
|
|
669
|
+
roomId,
|
|
670
|
+
gameType,
|
|
671
|
+
maxPlayers,
|
|
672
|
+
config,
|
|
673
|
+
requestCount,
|
|
674
|
+
waitTimeout,
|
|
675
|
+
token, // 传递认证令牌给 Worker(用于 Room.onAuth)
|
|
676
|
+
},
|
|
677
|
+
timestamp: Date.now(),
|
|
678
|
+
};
|
|
679
|
+
this.logger.debug(`[JoinOrCreate] Publishing message to worker ${workerId}, channel: ${core_1.RedisChannels.rpcWorker(workerId)}`);
|
|
680
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* 转发加入请求(用于房间已存在时的 joinOrCreate)
|
|
684
|
+
*
|
|
685
|
+
* @param connection 客户端连接
|
|
686
|
+
* @param roomId 房间ID
|
|
687
|
+
* @param workerId 目标Worker ID
|
|
688
|
+
* @param isJoinOrCreate 是否为 joinOrCreate 操作
|
|
689
|
+
* @param token 可选的认证令牌
|
|
690
|
+
*/
|
|
691
|
+
async forwardJoinRequest(connection, roomId, workerId, isJoinOrCreate, token) {
|
|
692
|
+
const msgType = isJoinOrCreate ? core_1.MessageType.MSG_ROOM_JOIN_OR_CREATE : core_1.MessageType.ROOM_JOIN;
|
|
693
|
+
const message = {
|
|
694
|
+
type: 'client-message',
|
|
695
|
+
requestId: (0, uuid_1.v4)(),
|
|
696
|
+
gatewayId: this.configService.gatewayId,
|
|
697
|
+
connectionId: connection.id,
|
|
698
|
+
userId: connection.userId,
|
|
699
|
+
roomId,
|
|
700
|
+
msgType,
|
|
701
|
+
payload: { roomId, requestCount: 0, token }, // requestCount=0 表示房间已存在,直接加入
|
|
702
|
+
timestamp: Date.now(),
|
|
703
|
+
};
|
|
704
|
+
await this.redisService.publish(core_1.RedisChannels.rpcWorker(workerId), message);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
exports.MessageRouterService = MessageRouterService;
|
|
708
|
+
exports.MessageRouterService = MessageRouterService = MessageRouterService_1 = __decorate([
|
|
709
|
+
(0, common_1.Injectable)(),
|
|
710
|
+
__metadata("design:paramtypes", [config_service_1.ConfigService,
|
|
711
|
+
redis_service_1.RedisService,
|
|
712
|
+
connection_service_1.ConnectionService,
|
|
713
|
+
session_service_1.SessionService])
|
|
714
|
+
], MessageRouterService);
|
|
715
|
+
//# sourceMappingURL=message-router.service.js.map
|