@roomkit/worker 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.
Files changed (158) hide show
  1. package/README.md +122 -0
  2. package/dist/src/admin/admin.controller.d.ts +275 -0
  3. package/dist/src/admin/admin.controller.d.ts.map +1 -0
  4. package/dist/src/admin/admin.controller.js +296 -0
  5. package/dist/src/admin/admin.controller.js.map +1 -0
  6. package/dist/src/admin/admin.module.d.ts +3 -0
  7. package/dist/src/admin/admin.module.d.ts.map +1 -0
  8. package/dist/src/admin/admin.module.js +35 -0
  9. package/dist/src/admin/admin.module.js.map +1 -0
  10. package/dist/src/admin/admin.service.d.ts +282 -0
  11. package/dist/src/admin/admin.service.d.ts.map +1 -0
  12. package/dist/src/admin/admin.service.js +508 -0
  13. package/dist/src/admin/admin.service.js.map +1 -0
  14. package/dist/src/auth/auth.module.d.ts +10 -0
  15. package/dist/src/auth/auth.module.d.ts.map +1 -0
  16. package/dist/src/auth/auth.module.js +37 -0
  17. package/dist/src/auth/auth.module.js.map +1 -0
  18. package/dist/src/auth/auth.service.d.ts +21 -0
  19. package/dist/src/auth/auth.service.d.ts.map +1 -0
  20. package/dist/src/auth/auth.service.js +69 -0
  21. package/dist/src/auth/auth.service.js.map +1 -0
  22. package/dist/src/circuit-breaker/circuit-breaker.module.d.ts +3 -0
  23. package/dist/src/circuit-breaker/circuit-breaker.module.d.ts.map +1 -0
  24. package/dist/src/circuit-breaker/circuit-breaker.module.js +24 -0
  25. package/dist/src/circuit-breaker/circuit-breaker.module.js.map +1 -0
  26. package/dist/src/circuit-breaker/circuit-breaker.service.d.ts +89 -0
  27. package/dist/src/circuit-breaker/circuit-breaker.service.d.ts.map +1 -0
  28. package/dist/src/circuit-breaker/circuit-breaker.service.js +207 -0
  29. package/dist/src/circuit-breaker/circuit-breaker.service.js.map +1 -0
  30. package/dist/src/config/config.module.d.ts +3 -0
  31. package/dist/src/config/config.module.d.ts.map +1 -0
  32. package/dist/src/config/config.module.js +22 -0
  33. package/dist/src/config/config.module.js.map +1 -0
  34. package/dist/src/config/config.service.d.ts +19 -0
  35. package/dist/src/config/config.service.d.ts.map +1 -0
  36. package/dist/src/config/config.service.js +58 -0
  37. package/dist/src/config/config.service.js.map +1 -0
  38. package/dist/src/config/worker.config.d.ts +82 -0
  39. package/dist/src/config/worker.config.d.ts.map +1 -0
  40. package/dist/src/config/worker.config.js +35 -0
  41. package/dist/src/config/worker.config.js.map +1 -0
  42. package/dist/src/index.d.ts +48 -0
  43. package/dist/src/index.d.ts.map +1 -0
  44. package/dist/src/index.js +108 -0
  45. package/dist/src/index.js.map +1 -0
  46. package/dist/src/lifecycle/graceful-shutdown.service.d.ts +71 -0
  47. package/dist/src/lifecycle/graceful-shutdown.service.d.ts.map +1 -0
  48. package/dist/src/lifecycle/graceful-shutdown.service.js +221 -0
  49. package/dist/src/lifecycle/graceful-shutdown.service.js.map +1 -0
  50. package/dist/src/lifecycle/index.d.ts +2 -0
  51. package/dist/src/lifecycle/index.d.ts.map +1 -0
  52. package/dist/src/lifecycle/index.js +18 -0
  53. package/dist/src/lifecycle/index.js.map +1 -0
  54. package/dist/src/main.d.ts +2 -0
  55. package/dist/src/main.d.ts.map +1 -0
  56. package/dist/src/main.js +54 -0
  57. package/dist/src/main.js.map +1 -0
  58. package/dist/src/metrics/metrics.controller.d.ts +15 -0
  59. package/dist/src/metrics/metrics.controller.d.ts.map +1 -0
  60. package/dist/src/metrics/metrics.controller.js +44 -0
  61. package/dist/src/metrics/metrics.controller.js.map +1 -0
  62. package/dist/src/metrics/metrics.module.d.ts +7 -0
  63. package/dist/src/metrics/metrics.module.d.ts.map +1 -0
  64. package/dist/src/metrics/metrics.module.js +28 -0
  65. package/dist/src/metrics/metrics.module.js.map +1 -0
  66. package/dist/src/metrics/metrics.service.d.ts +65 -0
  67. package/dist/src/metrics/metrics.service.d.ts.map +1 -0
  68. package/dist/src/metrics/metrics.service.js +306 -0
  69. package/dist/src/metrics/metrics.service.js.map +1 -0
  70. package/dist/src/migration/migration.module.d.ts +3 -0
  71. package/dist/src/migration/migration.module.d.ts.map +1 -0
  72. package/dist/src/migration/migration.module.js +30 -0
  73. package/dist/src/migration/migration.module.js.map +1 -0
  74. package/dist/src/migration/migration.service.d.ts +68 -0
  75. package/dist/src/migration/migration.service.d.ts.map +1 -0
  76. package/dist/src/migration/migration.service.js +295 -0
  77. package/dist/src/migration/migration.service.js.map +1 -0
  78. package/dist/src/migration/room-migration.service.d.ts +92 -0
  79. package/dist/src/migration/room-migration.service.d.ts.map +1 -0
  80. package/dist/src/migration/room-migration.service.js +297 -0
  81. package/dist/src/migration/room-migration.service.js.map +1 -0
  82. package/dist/src/redis/redis.module.d.ts +3 -0
  83. package/dist/src/redis/redis.module.d.ts.map +1 -0
  84. package/dist/src/redis/redis.module.js +22 -0
  85. package/dist/src/redis/redis.module.js.map +1 -0
  86. package/dist/src/redis/redis.service.d.ts +68 -0
  87. package/dist/src/redis/redis.service.d.ts.map +1 -0
  88. package/dist/src/redis/redis.service.js +260 -0
  89. package/dist/src/redis/redis.service.js.map +1 -0
  90. package/dist/src/room/client-proxy.d.ts +43 -0
  91. package/dist/src/room/client-proxy.d.ts.map +1 -0
  92. package/dist/src/room/client-proxy.js +91 -0
  93. package/dist/src/room/client-proxy.js.map +1 -0
  94. package/dist/src/room/game-room.d.ts +243 -0
  95. package/dist/src/room/game-room.d.ts.map +1 -0
  96. package/dist/src/room/game-room.js +434 -0
  97. package/dist/src/room/game-room.js.map +1 -0
  98. package/dist/src/room/message-handler.service.d.ts +70 -0
  99. package/dist/src/room/message-handler.service.d.ts.map +1 -0
  100. package/dist/src/room/message-handler.service.js +541 -0
  101. package/dist/src/room/message-handler.service.js.map +1 -0
  102. package/dist/src/room/room-manager.service.d.ts +132 -0
  103. package/dist/src/room/room-manager.service.d.ts.map +1 -0
  104. package/dist/src/room/room-manager.service.js +571 -0
  105. package/dist/src/room/room-manager.service.js.map +1 -0
  106. package/dist/src/room/room-persistence.service.d.ts +101 -0
  107. package/dist/src/room/room-persistence.service.d.ts.map +1 -0
  108. package/dist/src/room/room-persistence.service.js +307 -0
  109. package/dist/src/room/room-persistence.service.js.map +1 -0
  110. package/dist/src/room/room-registry.service.d.ts +16 -0
  111. package/dist/src/room/room-registry.service.d.ts.map +1 -0
  112. package/dist/src/room/room-registry.service.js +60 -0
  113. package/dist/src/room/room-registry.service.js.map +1 -0
  114. package/dist/src/room/room.module.d.ts +15 -0
  115. package/dist/src/room/room.module.d.ts.map +1 -0
  116. package/dist/src/room/room.module.js +55 -0
  117. package/dist/src/room/room.module.js.map +1 -0
  118. package/dist/src/room/rooms/echo-room.d.ts +18 -0
  119. package/dist/src/room/rooms/echo-room.d.ts.map +1 -0
  120. package/dist/src/room/rooms/echo-room.js +85 -0
  121. package/dist/src/room/rooms/echo-room.js.map +1 -0
  122. package/dist/src/room/rooms/index.d.ts +2 -0
  123. package/dist/src/room/rooms/index.d.ts.map +1 -0
  124. package/dist/src/room/rooms/index.js +6 -0
  125. package/dist/src/room/rooms/index.js.map +1 -0
  126. package/dist/src/worker.module.d.ts +10 -0
  127. package/dist/src/worker.module.d.ts.map +1 -0
  128. package/dist/src/worker.module.js +46 -0
  129. package/dist/src/worker.module.js.map +1 -0
  130. package/dist/test/circuit-breaker/circuit-breaker.service.spec.d.ts +2 -0
  131. package/dist/test/circuit-breaker/circuit-breaker.service.spec.d.ts.map +1 -0
  132. package/dist/test/circuit-breaker/circuit-breaker.service.spec.js +220 -0
  133. package/dist/test/circuit-breaker/circuit-breaker.service.spec.js.map +1 -0
  134. package/dist/test/integration/admin-api.spec.d.ts +2 -0
  135. package/dist/test/integration/admin-api.spec.d.ts.map +1 -0
  136. package/dist/test/integration/admin-api.spec.js +246 -0
  137. package/dist/test/integration/admin-api.spec.js.map +1 -0
  138. package/dist/test/migration/game-room-migration.spec.d.ts +2 -0
  139. package/dist/test/migration/game-room-migration.spec.d.ts.map +1 -0
  140. package/dist/test/migration/game-room-migration.spec.js +222 -0
  141. package/dist/test/migration/game-room-migration.spec.js.map +1 -0
  142. package/dist/test/room/client-proxy.spec.d.ts +2 -0
  143. package/dist/test/room/client-proxy.spec.d.ts.map +1 -0
  144. package/dist/test/room/client-proxy.spec.js +117 -0
  145. package/dist/test/room/client-proxy.spec.js.map +1 -0
  146. package/dist/test/room/game-room.spec.d.ts +2 -0
  147. package/dist/test/room/game-room.spec.d.ts.map +1 -0
  148. package/dist/test/room/game-room.spec.js +219 -0
  149. package/dist/test/room/game-room.spec.js.map +1 -0
  150. package/dist/test/room/room-manager.service.spec.d.ts +2 -0
  151. package/dist/test/room/room-manager.service.spec.d.ts.map +1 -0
  152. package/dist/test/room/room-manager.service.spec.js +280 -0
  153. package/dist/test/room/room-manager.service.spec.js.map +1 -0
  154. package/dist/test/setup.d.ts +2 -0
  155. package/dist/test/setup.d.ts.map +1 -0
  156. package/dist/test/setup.js +56 -0
  157. package/dist/test/setup.js.map +1 -0
  158. package/package.json +74 -0
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * 房间迁移服务
4
+ *
5
+ * 功能:
6
+ * 1. 主动迁移:Worker 主动将房间迁移到另一个 Worker
7
+ * 2. 被动接管:Worker 故障时,其他 Worker 接管孤儿房间
8
+ * 3. 迁移协调:确保迁移过程中的数据一致性
9
+ * 4. 路由更新:通知 Gateway 更新房间路由
10
+ *
11
+ * 设计:
12
+ * - 迁移过程:暂停房间 → 保存快照 → 通知 Gateway → 新 Worker 加载 → 确认完成
13
+ * - 容错机制:超时重试、回滚、失败通知
14
+ */
15
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
16
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
17
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
18
+ 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;
19
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
20
+ };
21
+ var __metadata = (this && this.__metadata) || function (k, v) {
22
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
23
+ };
24
+ var RoomMigrationService_1;
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.RoomMigrationService = void 0;
27
+ const common_1 = require("@nestjs/common");
28
+ const redis_service_js_1 = require("../redis/redis.service.js");
29
+ const config_service_js_1 = require("../config/config.service.js");
30
+ const room_persistence_service_js_1 = require("../room/room-persistence.service.js");
31
+ const core_1 = require("@roomkit/core");
32
+ let RoomMigrationService = RoomMigrationService_1 = class RoomMigrationService {
33
+ redis;
34
+ config;
35
+ persistence;
36
+ logger = new common_1.Logger(RoomMigrationService_1.name);
37
+ // 正在迁移的房间
38
+ migratingRooms = new Set();
39
+ // 迁移超时(默认 30 秒)
40
+ MIGRATION_TIMEOUT = 30000;
41
+ constructor(redis, config, persistence) {
42
+ this.redis = redis;
43
+ this.config = config;
44
+ this.persistence = persistence;
45
+ }
46
+ /**
47
+ * 设置 RoomManager(避免循环依赖)
48
+ */
49
+ setRoomManager(roomManager) {
50
+ this.roomManager = roomManager;
51
+ }
52
+ roomManager = null;
53
+ /**
54
+ * 初始化迁移服务
55
+ */
56
+ async initialize() {
57
+ // 订阅迁移请求频道
58
+ await this.redis.subscribe(core_1.RedisChannels.migrationRequest(this.config.workerId), (message) => {
59
+ const request = JSON.parse(message);
60
+ this.handleMigrationRequest(request);
61
+ });
62
+ // 订阅接管请求频道(广播到所有 Worker)
63
+ await this.redis.subscribe(core_1.RedisChannels.takeoverRequest(), (message) => {
64
+ const request = JSON.parse(message);
65
+ this.handleTakeoverRequest(request);
66
+ });
67
+ this.logger.log('Migration service initialized');
68
+ }
69
+ /**
70
+ * 主动迁移房间到指定 Worker
71
+ */
72
+ async migrateRoom(roomId, targetWorkerId, reason = 'manual') {
73
+ const startTime = Date.now();
74
+ if (this.migratingRooms.has(roomId)) {
75
+ return {
76
+ success: false,
77
+ roomId,
78
+ sourceWorkerId: this.config.workerId,
79
+ error: 'Migration already in progress',
80
+ duration: 0,
81
+ };
82
+ }
83
+ this.migratingRooms.add(roomId);
84
+ this.logger.log(`Starting migration for room ${roomId} to ${targetWorkerId}`);
85
+ try {
86
+ // 1. 保存最终快照
87
+ await this.persistence.saveSnapshot(roomId);
88
+ await this.persistence.flushEvents(roomId);
89
+ // 2. 发送迁移请求到目标 Worker
90
+ const request = {
91
+ roomId,
92
+ sourceWorkerId: this.config.workerId,
93
+ targetWorkerId,
94
+ reason,
95
+ timestamp: Date.now(),
96
+ };
97
+ await this.redis.publish(core_1.RedisChannels.migrationRequest(targetWorkerId), request);
98
+ // 3. 等待确认(通过 Redis 状态检查)
99
+ const confirmed = await this.waitForMigrationConfirmation(roomId, targetWorkerId);
100
+ if (!confirmed) {
101
+ throw new Error('Migration confirmation timeout');
102
+ }
103
+ // 4. 通知 Gateway 更新路由
104
+ await this.notifyGatewayRouteChange(roomId, targetWorkerId);
105
+ const duration = Date.now() - startTime;
106
+ this.logger.log(`Migration completed for room ${roomId} in ${duration}ms`);
107
+ return {
108
+ success: true,
109
+ roomId,
110
+ sourceWorkerId: this.config.workerId,
111
+ targetWorkerId,
112
+ duration,
113
+ };
114
+ }
115
+ catch (error) {
116
+ const err = error;
117
+ this.logger.error(`Migration failed for room ${roomId}: ${err.message}`);
118
+ return {
119
+ success: false,
120
+ roomId,
121
+ sourceWorkerId: this.config.workerId,
122
+ targetWorkerId,
123
+ error: err.message,
124
+ duration: Date.now() - startTime,
125
+ };
126
+ }
127
+ finally {
128
+ this.migratingRooms.delete(roomId);
129
+ }
130
+ }
131
+ /**
132
+ * 处理迁移请求(作为目标 Worker)
133
+ */
134
+ async handleMigrationRequest(request) {
135
+ this.logger.log(`Received migration request for room ${request.roomId} from ${request.sourceWorkerId}`);
136
+ try {
137
+ // 1. 从持久化恢复房间
138
+ const snapshot = await this.persistence.recoverRoom(request.roomId);
139
+ if (!snapshot) {
140
+ throw new Error('Failed to recover room from persistence');
141
+ }
142
+ // 2. 重建房间(需要 RoomManager 支持,这里先标记)
143
+ if (!this.roomManager) {
144
+ throw new Error('RoomManager not set');
145
+ }
146
+ await this.roomManager.restoreRoom(snapshot);
147
+ // 3. 更新 Redis 中的房间所有权
148
+ await this.redis.client.hset(`gw:room:${request.roomId}:worker`, {
149
+ workerId: this.config.workerId,
150
+ migratedFrom: request.sourceWorkerId,
151
+ migratedAt: Date.now().toString(),
152
+ });
153
+ // 4. 发布确认消息
154
+ await this.redis.publish(core_1.RedisChannels.migrationConfirm(request.roomId), {
155
+ roomId: request.roomId,
156
+ targetWorkerId: this.config.workerId,
157
+ success: true,
158
+ timestamp: Date.now(),
159
+ });
160
+ this.logger.log(`Successfully migrated room ${request.roomId}`);
161
+ }
162
+ catch (error) {
163
+ const err = error;
164
+ this.logger.error(`Failed to handle migration request for room ${request.roomId}: ${err.message}`);
165
+ // 发布失败消息
166
+ await this.redis.publish(core_1.RedisChannels.migrationConfirm(request.roomId), {
167
+ roomId: request.roomId,
168
+ targetWorkerId: this.config.workerId,
169
+ success: false,
170
+ error: err.message,
171
+ timestamp: Date.now(),
172
+ });
173
+ }
174
+ }
175
+ /**
176
+ * 处理接管请求(孤儿房间)
177
+ */
178
+ async handleTakeoverRequest(request) {
179
+ this.logger.log(`Received takeover request for room ${request.roomId} (original worker: ${request.originalWorkerId})`);
180
+ // 简单策略:第一个响应的 Worker 接管
181
+ // 生产环境应该有更复杂的选择算法(负载、地理位置等)
182
+ try {
183
+ // 1. 尝试获取接管锁
184
+ const lockKey = `gw:room:${request.roomId}:takeover-lock`;
185
+ const acquired = await this.redis.client.set(lockKey, this.config.workerId, 'EX', 60, // 60秒过期
186
+ 'NX');
187
+ if (!acquired) {
188
+ this.logger.debug(`Another worker is taking over room ${request.roomId}`);
189
+ return;
190
+ }
191
+ // 2. 从持久化恢复房间
192
+ const snapshot = await this.persistence.recoverRoom(request.roomId);
193
+ if (!snapshot) {
194
+ this.logger.warn(`No snapshot found for room ${request.roomId}, cannot takeover`);
195
+ await this.redis.client.del(lockKey);
196
+ return;
197
+ }
198
+ // 3. 重建房间
199
+ if (!this.roomManager) {
200
+ this.logger.warn('RoomManager not set, cannot takeover');
201
+ await this.redis.client.del(lockKey);
202
+ return;
203
+ }
204
+ await this.roomManager.restoreRoom(snapshot);
205
+ // 4. 更新所有权
206
+ await this.redis.client.hset(`gw:room:${request.roomId}:worker`, {
207
+ workerId: this.config.workerId,
208
+ takenOverFrom: request.originalWorkerId,
209
+ takenOverAt: Date.now().toString(),
210
+ });
211
+ // 5. 通知 Gateway
212
+ await this.notifyGatewayRouteChange(request.roomId, this.config.workerId);
213
+ this.logger.log(`Successfully took over room ${request.roomId} from ${request.originalWorkerId}`);
214
+ }
215
+ catch (error) {
216
+ const err = error;
217
+ this.logger.error(`Failed to takeover room ${request.roomId}: ${err.message}`);
218
+ }
219
+ }
220
+ /**
221
+ * 等待迁移确认
222
+ */
223
+ async waitForMigrationConfirmation(roomId, targetWorkerId) {
224
+ return new Promise((resolve) => {
225
+ const channel = core_1.RedisChannels.migrationConfirm(roomId);
226
+ let confirmed = false;
227
+ const handler = (message) => {
228
+ if (message.targetWorkerId === targetWorkerId) {
229
+ confirmed = message.success;
230
+ cleanup();
231
+ resolve(confirmed);
232
+ }
233
+ };
234
+ const cleanup = () => {
235
+ // Redis 订阅没有 unsubscribe 方法,需要手动实现
236
+ // 这里简单处理,生产环境可以使用更复杂的订阅管理
237
+ clearTimeout(timeoutHandle);
238
+ };
239
+ const timeoutHandle = setTimeout(() => {
240
+ if (!confirmed) {
241
+ cleanup();
242
+ resolve(false);
243
+ }
244
+ }, this.MIGRATION_TIMEOUT);
245
+ this.redis.subscribe(channel, handler);
246
+ });
247
+ }
248
+ /**
249
+ * 通知 Gateway 更新路由
250
+ */
251
+ async notifyGatewayRouteChange(roomId, newWorkerId) {
252
+ await this.redis.publish(core_1.RedisChannels.routeUpdate(), {
253
+ type: 'room-migrated',
254
+ roomId,
255
+ oldWorkerId: this.config.workerId,
256
+ newWorkerId,
257
+ timestamp: Date.now(),
258
+ });
259
+ }
260
+ /**
261
+ * 请求其他 Worker 接管孤儿房间
262
+ */
263
+ async requestTakeover(roomId, originalWorkerId, reason = 'worker-failed') {
264
+ const request = {
265
+ roomId,
266
+ originalWorkerId,
267
+ reason,
268
+ timestamp: Date.now(),
269
+ };
270
+ await this.redis.publish(core_1.RedisChannels.takeoverRequest(), request);
271
+ this.logger.log(`Requested takeover for room ${roomId}`);
272
+ }
273
+ /**
274
+ * 获取迁移统计
275
+ */
276
+ getStats() {
277
+ return {
278
+ migratingRooms: this.migratingRooms.size,
279
+ activeRooms: Array.from(this.migratingRooms),
280
+ };
281
+ }
282
+ /**
283
+ * 模块销毁时清理
284
+ */
285
+ async onModuleDestroy() {
286
+ this.logger.log('Shutting down migration service...');
287
+ this.migratingRooms.clear();
288
+ }
289
+ };
290
+ exports.RoomMigrationService = RoomMigrationService;
291
+ exports.RoomMigrationService = RoomMigrationService = RoomMigrationService_1 = __decorate([
292
+ (0, common_1.Injectable)(),
293
+ __metadata("design:paramtypes", [redis_service_js_1.RedisService,
294
+ config_service_js_1.ConfigService,
295
+ room_persistence_service_js_1.RoomPersistenceService])
296
+ ], RoomMigrationService);
297
+ //# sourceMappingURL=room-migration.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-migration.service.js","sourceRoot":"","sources":["../../../src/migration/room-migration.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare class RedisModule {
2
+ }
3
+ //# sourceMappingURL=redis.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.module.d.ts","sourceRoot":"","sources":["../../../src/redis/redis.module.ts"],"names":[],"mappings":"AAGA,qBAKa,WAAW;CAAG"}
@@ -0,0 +1,22 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.RedisModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const redis_service_1 = require("./redis.service");
12
+ let RedisModule = class RedisModule {
13
+ };
14
+ exports.RedisModule = RedisModule;
15
+ exports.RedisModule = RedisModule = __decorate([
16
+ (0, common_1.Global)(),
17
+ (0, common_1.Module)({
18
+ providers: [redis_service_1.RedisService],
19
+ exports: [redis_service_1.RedisService],
20
+ })
21
+ ], RedisModule);
22
+ //# sourceMappingURL=redis.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.module.js","sourceRoot":"","sources":["../../../src/redis/redis.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,mDAA+C;AAOxC,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IALvB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,4BAAY,CAAC;QACzB,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,WAAW,CAAG"}
@@ -0,0 +1,68 @@
1
+ import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
2
+ import Redis from 'ioredis';
3
+ import { ConfigService } from '../config/config.service';
4
+ export declare class RedisService implements OnModuleInit, OnModuleDestroy {
5
+ private readonly configService;
6
+ private readonly logger;
7
+ readonly client: Redis;
8
+ readonly subscriber: Redis;
9
+ private messageHandlers;
10
+ private registeredGameTypes;
11
+ private heartbeatTimer?;
12
+ private cleanupTimer?;
13
+ private readonly HEARTBEAT_INTERVAL;
14
+ private readonly HEARTBEAT_TIMEOUT;
15
+ private readonly CLEANUP_INTERVAL;
16
+ constructor(configService: ConfigService);
17
+ onModuleInit(): Promise<void>;
18
+ onModuleDestroy(): Promise<void>;
19
+ /**
20
+ * 注册Worker(由 RoomRegistryService 调用)
21
+ */
22
+ registerWorker(gameTypes: string[]): Promise<void>;
23
+ /**
24
+ * 注销Worker
25
+ */
26
+ private unregisterWorker;
27
+ /**
28
+ * 订阅通道
29
+ */
30
+ private subscribeToChannels;
31
+ /**
32
+ * 订阅频道并注册处理器
33
+ */
34
+ subscribe(channel: string, handler: (message: string) => void): Promise<void>;
35
+ /**
36
+ * 注册消息处理器
37
+ */
38
+ onMessage(channel: string, handler: (message: string) => void): void;
39
+ /**
40
+ * 发布消息
41
+ */
42
+ publish(channel: string, message: string | object): Promise<void>;
43
+ /**
44
+ * 更新Worker统计信息
45
+ */
46
+ updateStats(roomCount: number, playerCount: number): Promise<void>;
47
+ /**
48
+ * 启动心跳定时器
49
+ */
50
+ private startHeartbeat;
51
+ /**
52
+ * 发送心跳
53
+ */
54
+ private sendHeartbeat;
55
+ /**
56
+ * 启动清理定时器
57
+ */
58
+ private startCleanup;
59
+ /**
60
+ * 清理过期的Worker节点
61
+ */
62
+ private cleanupDeadWorkers;
63
+ /**
64
+ * 移除Worker(从所有列表中)
65
+ */
66
+ private removeWorker;
67
+ }
68
+ //# sourceMappingURL=redis.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.service.d.ts","sourceRoot":"","sources":["../../../src/redis/redis.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAE,eAAe,EAAU,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,MAAM,SAAS,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,qBACa,YAAa,YAAW,YAAY,EAAE,eAAe;IAkBpD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAjB1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAExD,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAE3B,OAAO,CAAC,eAAe,CAAqD;IAC5E,OAAO,CAAC,mBAAmB,CAAgB;IAG3C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAC,CAAiB;IAGtC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAEb,aAAa,EAAE,aAAa;IAanD,YAAY;IA4BZ,eAAe;IAgBrB;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BxD;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,mBAAmB;IAMjC;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnF;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAI7D;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvE;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;YACW,aAAa;IAK3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;YACW,kBAAkB;IAgChC;;OAEG;YACW,YAAY;CAsB3B"}
@@ -0,0 +1,260 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ var RedisService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.RedisService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const ioredis_1 = __importDefault(require("ioredis"));
19
+ const config_service_1 = require("../config/config.service");
20
+ const core_1 = require("@roomkit/core");
21
+ let RedisService = RedisService_1 = class RedisService {
22
+ configService;
23
+ logger = new common_1.Logger(RedisService_1.name);
24
+ client;
25
+ subscriber;
26
+ messageHandlers = new Map();
27
+ registeredGameTypes = [];
28
+ // 心跳和清理定时器
29
+ heartbeatTimer;
30
+ cleanupTimer;
31
+ // 常量配置
32
+ HEARTBEAT_INTERVAL = 5000; // 5秒心跳
33
+ HEARTBEAT_TIMEOUT = 30000; // 30秒超时
34
+ CLEANUP_INTERVAL = 60000; // 60秒清理一次
35
+ constructor(configService) {
36
+ this.configService = configService;
37
+ const redisConfig = {
38
+ host: this.configService.redisHost,
39
+ port: this.configService.redisPort,
40
+ password: this.configService.redisPassword,
41
+ retryStrategy: (times) => Math.min(times * 50, 2000),
42
+ maxRetriesPerRequest: 3,
43
+ };
44
+ this.client = new ioredis_1.default(redisConfig);
45
+ this.subscriber = new ioredis_1.default(redisConfig);
46
+ }
47
+ async onModuleInit() {
48
+ this.client.on('connect', () => this.logger.log('Redis client connected'));
49
+ this.client.on('error', (err) => this.logger.error('Redis client error', err));
50
+ this.subscriber.on('connect', () => this.logger.log('Redis subscriber connected'));
51
+ this.subscriber.on('error', (err) => this.logger.error('Redis subscriber error', err));
52
+ // 设置消息处理
53
+ this.subscriber.on('message', (channel, message) => {
54
+ this.logger.debug(`[Redis] Received message on channel: ${channel}, length: ${message.length}`);
55
+ const handler = this.messageHandlers.get(channel);
56
+ if (handler) {
57
+ handler(message);
58
+ }
59
+ else {
60
+ this.logger.warn(`[Redis] No handler registered for channel: ${channel}`);
61
+ }
62
+ });
63
+ // 订阅通道
64
+ await this.subscribeToChannels();
65
+ // 启动心跳定时器
66
+ this.startHeartbeat();
67
+ // 启动清理定时器
68
+ this.startCleanup();
69
+ }
70
+ async onModuleDestroy() {
71
+ // 停止定时器
72
+ if (this.heartbeatTimer) {
73
+ clearInterval(this.heartbeatTimer);
74
+ }
75
+ if (this.cleanupTimer) {
76
+ clearInterval(this.cleanupTimer);
77
+ }
78
+ // 注销Worker
79
+ await this.unregisterWorker();
80
+ await this.client.quit();
81
+ await this.subscriber.quit();
82
+ }
83
+ /**
84
+ * 注册Worker(由 RoomRegistryService 调用)
85
+ */
86
+ async registerWorker(gameTypes) {
87
+ const workerId = this.configService.workerId;
88
+ this.registeredGameTypes = gameTypes;
89
+ // 注册Worker信息
90
+ await this.client.hset(core_1.RedisKeys.worker(workerId), {
91
+ workerId,
92
+ gameTypes: JSON.stringify(gameTypes),
93
+ status: 'active', // 状态: active | draining | offline
94
+ roomCount: 0,
95
+ playerCount: 0,
96
+ lastHeartbeat: Date.now(),
97
+ startedAt: Date.now(),
98
+ });
99
+ // 设置TTL为30秒(需要心跳续期)
100
+ await this.client.expire(core_1.RedisKeys.worker(workerId), 30);
101
+ // 添加到Worker列表
102
+ await this.client.sadd(core_1.RedisKeys.workersAll(), workerId);
103
+ // 添加到各游戏类型的Worker列表
104
+ for (const gameType of gameTypes) {
105
+ await this.client.sadd(core_1.RedisKeys.workersByType(gameType), workerId);
106
+ }
107
+ this.logger.log(`Worker registered: ${workerId}, gameTypes: ${gameTypes.join(', ')}, status: active`);
108
+ }
109
+ /**
110
+ * 注销Worker
111
+ */
112
+ async unregisterWorker() {
113
+ const workerId = this.configService.workerId;
114
+ const gameTypes = this.registeredGameTypes;
115
+ // 更新状态为离线
116
+ await this.client.hset(core_1.RedisKeys.worker(workerId), 'status', 'offline');
117
+ // 从Worker列表移除
118
+ await this.client.srem(core_1.RedisKeys.workersAll(), workerId);
119
+ // 从各游戏类型的Worker列表移除
120
+ for (const gameType of gameTypes) {
121
+ await this.client.srem(core_1.RedisKeys.workersByType(gameType), workerId);
122
+ }
123
+ this.logger.log(`Worker unregistered: ${workerId}`);
124
+ }
125
+ /**
126
+ * 订阅通道
127
+ */
128
+ async subscribeToChannels() {
129
+ // Worker 频道由 MessageHandlerService 订阅,这里不需要提前订阅
130
+ // 避免订阅时 handler 还未注册的问题
131
+ this.logger.log('Redis subscription ready');
132
+ }
133
+ /**
134
+ * 订阅频道并注册处理器
135
+ */
136
+ async subscribe(channel, handler) {
137
+ this.messageHandlers.set(channel, handler);
138
+ await this.subscriber.subscribe(channel);
139
+ this.logger.log(`Subscribed to channel: ${channel}`);
140
+ }
141
+ /**
142
+ * 注册消息处理器
143
+ */
144
+ onMessage(channel, handler) {
145
+ this.messageHandlers.set(channel, handler);
146
+ }
147
+ /**
148
+ * 发布消息
149
+ */
150
+ async publish(channel, message) {
151
+ const data = typeof message === 'string' ? message : JSON.stringify(message);
152
+ await this.client.publish(channel, data);
153
+ }
154
+ /**
155
+ * 更新Worker统计信息
156
+ */
157
+ async updateStats(roomCount, playerCount) {
158
+ const workerId = this.configService.workerId;
159
+ await this.client.hset(core_1.RedisKeys.worker(workerId), 'roomCount', roomCount.toString());
160
+ await this.client.hset(core_1.RedisKeys.worker(workerId), 'playerCount', playerCount.toString());
161
+ await this.client.hset(core_1.RedisKeys.worker(workerId), 'lastHeartbeat', Date.now().toString());
162
+ // 续期TTL(根据状态决定时长)
163
+ const status = await this.client.hget(core_1.RedisKeys.worker(workerId), 'status');
164
+ const ttl = status === 'draining' ? 120 : 30;
165
+ await this.client.expire(core_1.RedisKeys.worker(workerId), ttl);
166
+ }
167
+ /**
168
+ * 启动心跳定时器
169
+ */
170
+ startHeartbeat() {
171
+ this.heartbeatTimer = setInterval(async () => {
172
+ try {
173
+ await this.sendHeartbeat();
174
+ }
175
+ catch (error) {
176
+ this.logger.error('Failed to send heartbeat', error);
177
+ }
178
+ }, this.HEARTBEAT_INTERVAL);
179
+ this.logger.log(`Heartbeat started: interval=${this.HEARTBEAT_INTERVAL}ms`);
180
+ }
181
+ /**
182
+ * 发送心跳
183
+ */
184
+ async sendHeartbeat() {
185
+ const workerId = this.configService.workerId;
186
+ await this.client.hset(core_1.RedisKeys.worker(workerId), 'lastHeartbeat', Date.now().toString());
187
+ }
188
+ /**
189
+ * 启动清理定时器
190
+ */
191
+ startCleanup() {
192
+ this.cleanupTimer = setInterval(async () => {
193
+ try {
194
+ await this.cleanupDeadWorkers();
195
+ }
196
+ catch (error) {
197
+ this.logger.error('Failed to cleanup dead workers', error);
198
+ }
199
+ }, this.CLEANUP_INTERVAL);
200
+ this.logger.log(`Cleanup started: interval=${this.CLEANUP_INTERVAL}ms`);
201
+ }
202
+ /**
203
+ * 清理过期的Worker节点
204
+ */
205
+ async cleanupDeadWorkers() {
206
+ const now = Date.now();
207
+ const timeout = this.HEARTBEAT_TIMEOUT;
208
+ // 获取所有Worker
209
+ const workerIds = await this.client.smembers(core_1.RedisKeys.workersAll());
210
+ let cleaned = 0;
211
+ for (const workerId of workerIds) {
212
+ const workerData = await this.client.hgetall(core_1.RedisKeys.worker(workerId));
213
+ if (!workerData || !workerData.lastHeartbeat) {
214
+ // 没有心跳信息,清理掉
215
+ await this.removeWorker(workerId);
216
+ cleaned++;
217
+ continue;
218
+ }
219
+ const lastHeartbeat = parseInt(workerData.lastHeartbeat);
220
+ if (now - lastHeartbeat > timeout) {
221
+ // 心跳超时,清理掉
222
+ this.logger.warn(`Removing dead worker: ${workerId} (last heartbeat: ${new Date(lastHeartbeat).toISOString()})`);
223
+ await this.removeWorker(workerId);
224
+ cleaned++;
225
+ }
226
+ }
227
+ if (cleaned > 0) {
228
+ this.logger.log(`Cleaned up ${cleaned} dead worker(s)`);
229
+ }
230
+ }
231
+ /**
232
+ * 移除Worker(从所有列表中)
233
+ */
234
+ async removeWorker(workerId) {
235
+ // 获取Worker信息
236
+ const workerData = await this.client.hgetall(core_1.RedisKeys.worker(workerId));
237
+ // 从总列表移除
238
+ await this.client.srem(core_1.RedisKeys.workersAll(), workerId);
239
+ // 从游戏类型列表移除
240
+ if (workerData.gameTypes) {
241
+ try {
242
+ const gameTypes = JSON.parse(workerData.gameTypes);
243
+ for (const gameType of gameTypes) {
244
+ await this.client.srem(core_1.RedisKeys.workersByType(gameType), workerId);
245
+ }
246
+ }
247
+ catch (e) {
248
+ // 忽略JSON解析错误
249
+ }
250
+ }
251
+ // 删除Worker数据
252
+ await this.client.del(core_1.RedisKeys.worker(workerId));
253
+ }
254
+ };
255
+ exports.RedisService = RedisService;
256
+ exports.RedisService = RedisService = RedisService_1 = __decorate([
257
+ (0, common_1.Injectable)(),
258
+ __metadata("design:paramtypes", [config_service_1.ConfigService])
259
+ ], RedisService);
260
+ //# sourceMappingURL=redis.service.js.map