imean-service-engine 2.0.0 → 2.0.2
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/dist/index.d.mts +77 -52
- package/dist/index.d.ts +77 -52
- package/dist/index.js +2078 -1945
- package/dist/index.mjs +2076 -1944
- package/package.json +9 -2
- package/.vscode/settings.json +0 -8
- package/src/core/checker.ts +0 -33
- package/src/core/decorators.test.ts +0 -96
- package/src/core/decorators.ts +0 -68
- package/src/core/engine.test.ts +0 -218
- package/src/core/engine.ts +0 -635
- package/src/core/errors.ts +0 -28
- package/src/core/factory.test.ts +0 -73
- package/src/core/factory.ts +0 -92
- package/src/core/logger.ts +0 -65
- package/src/core/testing.ts +0 -73
- package/src/core/types.ts +0 -191
- package/src/index.ts +0 -49
- package/src/metadata/README.md +0 -422
- package/src/metadata/metadata.test.ts +0 -369
- package/src/metadata/metadata.ts +0 -512
- package/src/plugins/action/action-plugin.test.ts +0 -660
- package/src/plugins/action/decorator.ts +0 -14
- package/src/plugins/action/index.ts +0 -4
- package/src/plugins/action/plugin.ts +0 -349
- package/src/plugins/action/types.ts +0 -49
- package/src/plugins/action/utils.test.ts +0 -196
- package/src/plugins/action/utils.ts +0 -111
- package/src/plugins/cache/adapter.test.ts +0 -689
- package/src/plugins/cache/adapter.ts +0 -324
- package/src/plugins/cache/cache-plugin.test.ts +0 -269
- package/src/plugins/cache/decorator.ts +0 -26
- package/src/plugins/cache/index.ts +0 -20
- package/src/plugins/cache/plugin.ts +0 -299
- package/src/plugins/cache/types.ts +0 -69
- package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
- package/src/plugins/client-code/format.ts +0 -9
- package/src/plugins/client-code/generator.test.ts +0 -52
- package/src/plugins/client-code/generator.ts +0 -263
- package/src/plugins/client-code/index.ts +0 -15
- package/src/plugins/client-code/plugin.ts +0 -158
- package/src/plugins/client-code/types.ts +0 -52
- package/src/plugins/client-code/utils.ts +0 -164
- package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
- package/src/plugins/graceful-shutdown/index.ts +0 -3
- package/src/plugins/graceful-shutdown/plugin.ts +0 -279
- package/src/plugins/graceful-shutdown/types.ts +0 -17
- package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
- package/src/plugins/route/components/Layout.tsx +0 -42
- package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
- package/src/plugins/route/decorator.ts +0 -50
- package/src/plugins/route/index.ts +0 -16
- package/src/plugins/route/plugin.ts +0 -218
- package/src/plugins/route/route-plugin.test.ts +0 -759
- package/src/plugins/route/types.ts +0 -72
- package/src/plugins/schedule/README.md +0 -309
- package/src/plugins/schedule/decorator.ts +0 -25
- package/src/plugins/schedule/index.ts +0 -12
- package/src/plugins/schedule/mock-etcd.ts +0 -145
- package/src/plugins/schedule/plugin.ts +0 -164
- package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
- package/src/plugins/schedule/scheduler.ts +0 -164
- package/src/plugins/schedule/types.ts +0 -94
- package/src/plugins/schedule/utils.test.ts +0 -163
- package/src/plugins/schedule/utils.ts +0 -41
- package/tests/integration/client.test.ts +0 -203
- package/tests/integration/dev-service.ts +0 -301
- package/tests/integration/generated/client.ts +0 -123
- package/tests/integration/start-service.ts +0 -21
- package/tsconfig.json +0 -27
- package/tsup.config.ts +0 -16
- package/vitest.config.ts +0 -19
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi, afterEach } from "vitest";
|
|
2
|
-
import { GracefulShutdownPlugin } from "./plugin";
|
|
3
|
-
import { Testing } from "../../core/testing";
|
|
4
|
-
import { Handler } from "../../core/decorators";
|
|
5
|
-
import { ActionPlugin } from "../action";
|
|
6
|
-
import { RoutePlugin } from "../route";
|
|
7
|
-
import { Route } from "../route";
|
|
8
|
-
|
|
9
|
-
describe("GracefulShutdownPlugin", () => {
|
|
10
|
-
let plugin: GracefulShutdownPlugin;
|
|
11
|
-
let originalExit: typeof process.exit;
|
|
12
|
-
let exitCode: number | null = null;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
// 保存原始的 process.exit
|
|
16
|
-
originalExit = process.exit;
|
|
17
|
-
exitCode = null;
|
|
18
|
-
|
|
19
|
-
// Mock process.exit
|
|
20
|
-
process.exit = vi.fn((code?: number) => {
|
|
21
|
-
exitCode = code ?? 0;
|
|
22
|
-
return undefined as never;
|
|
23
|
-
}) as typeof process.exit;
|
|
24
|
-
|
|
25
|
-
plugin = new GracefulShutdownPlugin({
|
|
26
|
-
shutdownTimeout: 1000, // 1秒超时,用于测试
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
// 恢复原始的 process.exit
|
|
32
|
-
process.exit = originalExit;
|
|
33
|
-
|
|
34
|
-
// 清理所有信号监听器
|
|
35
|
-
process.removeAllListeners("SIGINT");
|
|
36
|
-
process.removeAllListeners("SIGTERM");
|
|
37
|
-
process.removeAllListeners("SIGBREAK");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("插件初始化", () => {
|
|
41
|
-
it("应该正确初始化插件", () => {
|
|
42
|
-
expect(plugin.name).toBe("graceful-shutdown-plugin");
|
|
43
|
-
expect(plugin.getActiveHandlersCount()).toBe(0);
|
|
44
|
-
expect(plugin.isShuttingDownNow()).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("应该支持自定义超时时间", () => {
|
|
48
|
-
const customPlugin = new GracefulShutdownPlugin({
|
|
49
|
-
shutdownTimeout: 5000,
|
|
50
|
-
});
|
|
51
|
-
expect(customPlugin).toBeDefined();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("应该支持禁用插件", () => {
|
|
55
|
-
const disabledPlugin = new GracefulShutdownPlugin({
|
|
56
|
-
enabled: false,
|
|
57
|
-
});
|
|
58
|
-
expect(disabledPlugin).toBeDefined();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe("处理器追踪", () => {
|
|
63
|
-
it("应该追踪 Action 处理器的执行", async () => {
|
|
64
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
65
|
-
plugins: [new ActionPlugin(), plugin],
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
@TestModule("test-module")
|
|
69
|
-
class TestService {
|
|
70
|
-
@Handler({
|
|
71
|
-
type: "action",
|
|
72
|
-
options: {
|
|
73
|
-
params: [],
|
|
74
|
-
returns: { type: "string" },
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
async testAction() {
|
|
78
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
79
|
-
return "done";
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
await engine.start();
|
|
84
|
-
const port = engine.getPort();
|
|
85
|
-
|
|
86
|
-
// 发送请求
|
|
87
|
-
const promise = fetch(`http://127.0.0.1:${port}/test-module/testAction`, {
|
|
88
|
-
method: "POST",
|
|
89
|
-
headers: { "Content-Type": "application/ejson" },
|
|
90
|
-
body: JSON.stringify({}),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// 等待一小段时间,确保处理器开始执行
|
|
94
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
95
|
-
|
|
96
|
-
// 检查活跃处理器计数
|
|
97
|
-
expect(plugin.getActiveHandlersCount()).toBeGreaterThan(0);
|
|
98
|
-
|
|
99
|
-
// 等待请求完成
|
|
100
|
-
await promise;
|
|
101
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
102
|
-
|
|
103
|
-
// 检查活跃处理器计数应该为0
|
|
104
|
-
expect(plugin.getActiveHandlersCount()).toBe(0);
|
|
105
|
-
|
|
106
|
-
await engine.stop();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("应该追踪 Route 处理器的执行", async () => {
|
|
110
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
111
|
-
plugins: [new RoutePlugin(), plugin],
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
@TestModule("test-module")
|
|
115
|
-
class TestService {
|
|
116
|
-
@Route({
|
|
117
|
-
method: "GET",
|
|
118
|
-
path: "/test",
|
|
119
|
-
})
|
|
120
|
-
async testRoute() {
|
|
121
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
122
|
-
return { success: true };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
await engine.start();
|
|
127
|
-
const port = engine.getPort();
|
|
128
|
-
|
|
129
|
-
// 发送请求
|
|
130
|
-
const promise = fetch(`http://127.0.0.1:${port}/test`);
|
|
131
|
-
|
|
132
|
-
// 等待一小段时间,确保处理器开始执行
|
|
133
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
134
|
-
|
|
135
|
-
// 检查活跃处理器计数
|
|
136
|
-
expect(plugin.getActiveHandlersCount()).toBeGreaterThan(0);
|
|
137
|
-
|
|
138
|
-
// 等待请求完成
|
|
139
|
-
await promise;
|
|
140
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
141
|
-
|
|
142
|
-
// 检查活跃处理器计数应该为0
|
|
143
|
-
expect(plugin.getActiveHandlersCount()).toBe(0);
|
|
144
|
-
|
|
145
|
-
await engine.stop();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("优雅停机", () => {
|
|
150
|
-
it("应该在收到 SIGINT 信号时启动优雅停机", async () => {
|
|
151
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
152
|
-
plugins: [new ActionPlugin(), plugin],
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
@TestModule("test-module")
|
|
156
|
-
class TestService {
|
|
157
|
-
@Handler({
|
|
158
|
-
type: "action",
|
|
159
|
-
options: {
|
|
160
|
-
params: [],
|
|
161
|
-
returns: { type: "string" },
|
|
162
|
-
},
|
|
163
|
-
})
|
|
164
|
-
async testAction() {
|
|
165
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
166
|
-
return "done";
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await engine.start();
|
|
171
|
-
|
|
172
|
-
// 发送一个请求
|
|
173
|
-
const requestPromise = fetch(
|
|
174
|
-
`http://127.0.0.1:${engine.getPort()}/test-module/testAction`,
|
|
175
|
-
{
|
|
176
|
-
method: "POST",
|
|
177
|
-
headers: { "Content-Type": "application/ejson" },
|
|
178
|
-
body: JSON.stringify({}),
|
|
179
|
-
}
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// 等待处理器开始执行
|
|
183
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
184
|
-
|
|
185
|
-
// 模拟 SIGINT 信号
|
|
186
|
-
process.emit("SIGINT" as any, "SIGINT");
|
|
187
|
-
|
|
188
|
-
// 等待停机流程
|
|
189
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
190
|
-
|
|
191
|
-
// 检查是否正在停机
|
|
192
|
-
expect(plugin.isShuttingDownNow()).toBe(true);
|
|
193
|
-
|
|
194
|
-
// 等待请求完成
|
|
195
|
-
await requestPromise;
|
|
196
|
-
|
|
197
|
-
// 等待停机完成
|
|
198
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
199
|
-
|
|
200
|
-
// 检查是否调用了 process.exit
|
|
201
|
-
expect(exitCode).toBe(0);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("应该在所有处理器完成后立即停机", async () => {
|
|
205
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
206
|
-
plugins: [new ActionPlugin(), plugin],
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
@TestModule("test-module")
|
|
210
|
-
class TestService {
|
|
211
|
-
@Handler({
|
|
212
|
-
type: "action",
|
|
213
|
-
options: {
|
|
214
|
-
params: [],
|
|
215
|
-
returns: { type: "string" },
|
|
216
|
-
},
|
|
217
|
-
})
|
|
218
|
-
async quickAction() {
|
|
219
|
-
return "done";
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await engine.start();
|
|
224
|
-
|
|
225
|
-
// 发送一个快速完成的请求
|
|
226
|
-
await fetch(
|
|
227
|
-
`http://127.0.0.1:${engine.getPort()}/test-module/quickAction`,
|
|
228
|
-
{
|
|
229
|
-
method: "POST",
|
|
230
|
-
headers: { "Content-Type": "application/ejson" },
|
|
231
|
-
body: JSON.stringify({}),
|
|
232
|
-
}
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
// 等待一小段时间确保请求完成
|
|
236
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
237
|
-
|
|
238
|
-
// 确保没有活跃的处理器
|
|
239
|
-
expect(plugin.getActiveHandlersCount()).toBe(0);
|
|
240
|
-
|
|
241
|
-
// 模拟 SIGINT 信号(Windows 也支持)
|
|
242
|
-
process.emit("SIGINT" as any, "SIGINT");
|
|
243
|
-
|
|
244
|
-
// 等待停机流程(需要更长时间,因为需要等待引擎停止)
|
|
245
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
246
|
-
|
|
247
|
-
// 检查是否调用了 process.exit
|
|
248
|
-
// 注意:在某些情况下,process.exit 可能不会立即调用
|
|
249
|
-
// 所以我们检查 exitCode 是否为 0 或 null(null 表示还没有调用)
|
|
250
|
-
expect(exitCode === 0 || exitCode === null).toBe(true);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("应该在超时后强制停机", async () => {
|
|
254
|
-
const fastTimeoutPlugin = new GracefulShutdownPlugin({
|
|
255
|
-
shutdownTimeout: 100, // 100ms 超时
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
259
|
-
plugins: [new ActionPlugin(), fastTimeoutPlugin],
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
@TestModule("test-module")
|
|
263
|
-
class TestService {
|
|
264
|
-
@Handler({
|
|
265
|
-
type: "action",
|
|
266
|
-
options: {
|
|
267
|
-
params: [],
|
|
268
|
-
returns: { type: "string" },
|
|
269
|
-
},
|
|
270
|
-
})
|
|
271
|
-
async slowAction() {
|
|
272
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
273
|
-
return "done";
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
await engine.start();
|
|
278
|
-
|
|
279
|
-
// 发送一个慢请求
|
|
280
|
-
const requestPromise = fetch(
|
|
281
|
-
`http://127.0.0.1:${engine.getPort()}/test-module/slowAction`,
|
|
282
|
-
{
|
|
283
|
-
method: "POST",
|
|
284
|
-
headers: { "Content-Type": "application/ejson" },
|
|
285
|
-
body: JSON.stringify({}),
|
|
286
|
-
}
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// 等待处理器开始执行
|
|
290
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
291
|
-
|
|
292
|
-
// 模拟 SIGINT 信号
|
|
293
|
-
process.emit("SIGINT" as any, "SIGINT");
|
|
294
|
-
|
|
295
|
-
// 等待超时
|
|
296
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
297
|
-
|
|
298
|
-
// 检查是否调用了 process.exit(应该因为超时而退出)
|
|
299
|
-
expect(exitCode).toBe(0);
|
|
300
|
-
|
|
301
|
-
// 清理
|
|
302
|
-
try {
|
|
303
|
-
await requestPromise;
|
|
304
|
-
} catch {
|
|
305
|
-
// 忽略错误,因为服务器可能已经关闭
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it("应该在停机期间拒绝新的请求", async () => {
|
|
310
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
311
|
-
plugins: [new ActionPlugin(), plugin],
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
@TestModule("test-module")
|
|
315
|
-
class TestService {
|
|
316
|
-
@Handler({
|
|
317
|
-
type: "action",
|
|
318
|
-
options: {
|
|
319
|
-
params: [],
|
|
320
|
-
returns: { type: "string" },
|
|
321
|
-
},
|
|
322
|
-
})
|
|
323
|
-
async testAction() {
|
|
324
|
-
return "done";
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
await engine.start();
|
|
329
|
-
|
|
330
|
-
// 模拟 SIGINT 信号
|
|
331
|
-
process.emit("SIGINT" as any, "SIGINT");
|
|
332
|
-
|
|
333
|
-
// 等待停机流程启动
|
|
334
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
335
|
-
|
|
336
|
-
// 尝试发送新请求(应该被拒绝)
|
|
337
|
-
try {
|
|
338
|
-
await fetch(
|
|
339
|
-
`http://127.0.0.1:${engine.getPort()}/test-module/testAction`,
|
|
340
|
-
{
|
|
341
|
-
method: "POST",
|
|
342
|
-
headers: { "Content-Type": "application/ejson" },
|
|
343
|
-
body: JSON.stringify({}),
|
|
344
|
-
}
|
|
345
|
-
);
|
|
346
|
-
// 如果请求成功,测试失败
|
|
347
|
-
expect.fail("Request should be rejected during shutdown");
|
|
348
|
-
} catch (error) {
|
|
349
|
-
// 请求应该被拒绝或失败
|
|
350
|
-
expect(error).toBeDefined();
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
describe("错误处理", () => {
|
|
356
|
-
it("应该正确处理处理器执行错误", async () => {
|
|
357
|
-
const { engine, Module: TestModule } = Testing.createTestEngine({
|
|
358
|
-
plugins: [new ActionPlugin(), plugin],
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
@TestModule("test-module")
|
|
362
|
-
class TestService {
|
|
363
|
-
@Handler({
|
|
364
|
-
type: "action",
|
|
365
|
-
options: {
|
|
366
|
-
params: [],
|
|
367
|
-
returns: { type: "string" },
|
|
368
|
-
},
|
|
369
|
-
})
|
|
370
|
-
async failingAction() {
|
|
371
|
-
throw new Error("Test error");
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
await engine.start();
|
|
376
|
-
|
|
377
|
-
// 发送一个会失败的请求
|
|
378
|
-
try {
|
|
379
|
-
await fetch(
|
|
380
|
-
`http://127.0.0.1:${engine.getPort()}/test-module/failingAction`,
|
|
381
|
-
{
|
|
382
|
-
method: "POST",
|
|
383
|
-
headers: { "Content-Type": "application/ejson" },
|
|
384
|
-
body: JSON.stringify({}),
|
|
385
|
-
}
|
|
386
|
-
);
|
|
387
|
-
} catch {
|
|
388
|
-
// 忽略错误
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// 等待一小段时间
|
|
392
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
393
|
-
|
|
394
|
-
// 检查活跃处理器计数应该为0(即使出错也应该减少计数)
|
|
395
|
-
expect(plugin.getActiveHandlersCount()).toBe(0);
|
|
396
|
-
|
|
397
|
-
await engine.stop();
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import { Microservice } from "../../core/engine";
|
|
2
|
-
import {
|
|
3
|
-
HandlerMetadata,
|
|
4
|
-
Plugin,
|
|
5
|
-
PluginPriority,
|
|
6
|
-
} from "../../core/types";
|
|
7
|
-
import logger from "../../core/logger";
|
|
8
|
-
import { GracefulShutdownPluginOptions } from "./types";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 优雅停机插件
|
|
12
|
-
*
|
|
13
|
-
* 功能:
|
|
14
|
-
* 1. 追踪所有处理器的执行状态(包括 Action、Route、Schedule 等)
|
|
15
|
-
* 2. 监听系统停机信号(SIGINT、SIGTERM 等)
|
|
16
|
-
* 3. 收到信号后,等待所有正在执行的处理器完成
|
|
17
|
-
* 4. 如果超时或所有处理完成,执行优雅停机
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* // 使用默认配置(10分钟超时)
|
|
22
|
-
* const gracefulShutdownPlugin = new GracefulShutdownPlugin();
|
|
23
|
-
*
|
|
24
|
-
* // 自定义超时时间(5分钟)
|
|
25
|
-
* const gracefulShutdownPlugin = new GracefulShutdownPlugin({
|
|
26
|
-
* shutdownTimeout: 5 * 60 * 1000, // 5分钟
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export class GracefulShutdownPlugin implements Plugin {
|
|
31
|
-
public readonly name = "graceful-shutdown-plugin";
|
|
32
|
-
public readonly priority = PluginPriority.SYSTEM; // 系统级插件,最高优先级
|
|
33
|
-
|
|
34
|
-
private engine: Microservice | null = null;
|
|
35
|
-
private options: Required<GracefulShutdownPluginOptions>;
|
|
36
|
-
|
|
37
|
-
// 正在执行的处理器计数
|
|
38
|
-
private activeHandlers: number = 0;
|
|
39
|
-
|
|
40
|
-
// 是否正在停机
|
|
41
|
-
private isShuttingDown: boolean = false;
|
|
42
|
-
|
|
43
|
-
// 停机超时定时器
|
|
44
|
-
private shutdownTimer: NodeJS.Timeout | null = null;
|
|
45
|
-
|
|
46
|
-
// 信号监听器(用于清理)
|
|
47
|
-
private signalListeners: Map<string, () => void> = new Map();
|
|
48
|
-
|
|
49
|
-
constructor(options?: GracefulShutdownPluginOptions) {
|
|
50
|
-
this.options = {
|
|
51
|
-
shutdownTimeout: options?.shutdownTimeout ?? 10 * 60 * 1000, // 默认10分钟
|
|
52
|
-
enabled: options?.enabled ?? true,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 引擎初始化钩子
|
|
58
|
-
*/
|
|
59
|
-
onInit(engine: Microservice): void {
|
|
60
|
-
this.engine = engine;
|
|
61
|
-
logger.info("GracefulShutdownPlugin initialized");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Handler加载钩子:拦截所有处理器,追踪执行状态
|
|
66
|
-
*/
|
|
67
|
-
onHandlerLoad(handlers: HandlerMetadata[]): void {
|
|
68
|
-
if (!this.options.enabled) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 拦截所有处理器(包括 action、route、schedule 等)
|
|
73
|
-
for (const handler of handlers) {
|
|
74
|
-
handler.wrap(async (next, instance, ...args) => {
|
|
75
|
-
// 如果正在停机,拒绝新的请求
|
|
76
|
-
if (this.isShuttingDown) {
|
|
77
|
-
throw new Error("Service is shutting down, new requests are not accepted");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 增加活跃处理器计数
|
|
81
|
-
this.incrementActiveHandlers();
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
// 执行处理器
|
|
85
|
-
const result = await next();
|
|
86
|
-
return result;
|
|
87
|
-
} catch (error) {
|
|
88
|
-
// 即使出错也要减少计数
|
|
89
|
-
throw error;
|
|
90
|
-
} finally {
|
|
91
|
-
// 减少活跃处理器计数
|
|
92
|
-
this.decrementActiveHandlers();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
logger.info(
|
|
98
|
-
`GracefulShutdownPlugin: Tracking ${handlers.length} handler(s)`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 引擎启动后钩子:注册系统信号监听器
|
|
104
|
-
*/
|
|
105
|
-
async onAfterStart(engine: Microservice): Promise<void> {
|
|
106
|
-
if (!this.options.enabled) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.engine = engine;
|
|
111
|
-
this.registerSignalHandlers();
|
|
112
|
-
logger.info(
|
|
113
|
-
`GracefulShutdownPlugin: Signal handlers registered, shutdown timeout: ${this.options.shutdownTimeout}ms`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 注册系统信号监听器
|
|
119
|
-
*/
|
|
120
|
-
private registerSignalHandlers(): void {
|
|
121
|
-
// 支持的信号列表(兼容不同操作系统)
|
|
122
|
-
const signals: NodeJS.Signals[] = [
|
|
123
|
-
"SIGINT", // Ctrl+C (Unix/Linux/Mac)
|
|
124
|
-
"SIGTERM", // 终止信号 (Unix/Linux/Mac)
|
|
125
|
-
"SIGBREAK", // Ctrl+Break (Windows)
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
for (const signal of signals) {
|
|
129
|
-
// 检查信号是否在当前平台支持
|
|
130
|
-
if (process.platform === "win32" && signal === "SIGTERM") {
|
|
131
|
-
// Windows 不支持 SIGTERM,跳过
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const handler = () => {
|
|
136
|
-
logger.info(`GracefulShutdownPlugin: Received ${signal} signal`);
|
|
137
|
-
this.initiateShutdown();
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// 注册信号监听器
|
|
141
|
-
process.on(signal, handler);
|
|
142
|
-
|
|
143
|
-
// 保存监听器引用,用于清理
|
|
144
|
-
this.signalListeners.set(signal, handler);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 增加活跃处理器计数
|
|
150
|
-
*/
|
|
151
|
-
private incrementActiveHandlers(): void {
|
|
152
|
-
this.activeHandlers++;
|
|
153
|
-
logger.debug(
|
|
154
|
-
`GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* 减少活跃处理器计数
|
|
160
|
-
*/
|
|
161
|
-
private decrementActiveHandlers(): void {
|
|
162
|
-
this.activeHandlers = Math.max(0, this.activeHandlers - 1);
|
|
163
|
-
logger.debug(
|
|
164
|
-
`GracefulShutdownPlugin: Active handlers: ${this.activeHandlers}`
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// 如果正在停机且所有处理器已完成,立即执行停机
|
|
168
|
-
if (this.isShuttingDown && this.activeHandlers === 0) {
|
|
169
|
-
logger.info(
|
|
170
|
-
"GracefulShutdownPlugin: All handlers completed, proceeding with shutdown"
|
|
171
|
-
);
|
|
172
|
-
this.completeShutdown();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 启动优雅停机流程
|
|
178
|
-
*/
|
|
179
|
-
private async initiateShutdown(): Promise<void> {
|
|
180
|
-
// 防止重复调用
|
|
181
|
-
if (this.isShuttingDown) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this.isShuttingDown = true;
|
|
186
|
-
logger.info(
|
|
187
|
-
`GracefulShutdownPlugin: Initiating graceful shutdown, waiting for ${this.activeHandlers} active handler(s) to complete`
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
// 如果当前没有活跃的处理器,立即停机
|
|
191
|
-
if (this.activeHandlers === 0) {
|
|
192
|
-
logger.info(
|
|
193
|
-
"GracefulShutdownPlugin: No active handlers, proceeding with shutdown immediately"
|
|
194
|
-
);
|
|
195
|
-
await this.completeShutdown();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 设置超时定时器
|
|
200
|
-
this.shutdownTimer = setTimeout(() => {
|
|
201
|
-
logger.warn(
|
|
202
|
-
`GracefulShutdownPlugin: Shutdown timeout (${this.options.shutdownTimeout}ms) reached, forcing shutdown`
|
|
203
|
-
);
|
|
204
|
-
this.completeShutdown();
|
|
205
|
-
}, this.options.shutdownTimeout);
|
|
206
|
-
|
|
207
|
-
// 等待所有处理器完成(通过 decrementActiveHandlers 触发)
|
|
208
|
-
// 如果超时,completeShutdown 会被定时器调用
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* 完成停机流程
|
|
213
|
-
*/
|
|
214
|
-
private async completeShutdown(): Promise<void> {
|
|
215
|
-
// 清除超时定时器
|
|
216
|
-
if (this.shutdownTimer) {
|
|
217
|
-
clearTimeout(this.shutdownTimer);
|
|
218
|
-
this.shutdownTimer = null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 清理信号监听器
|
|
222
|
-
this.cleanupSignalHandlers();
|
|
223
|
-
|
|
224
|
-
// 停止引擎
|
|
225
|
-
if (this.engine) {
|
|
226
|
-
try {
|
|
227
|
-
await this.engine.stop();
|
|
228
|
-
logger.info("GracefulShutdownPlugin: Engine stopped successfully");
|
|
229
|
-
} catch (error) {
|
|
230
|
-
logger.error("GracefulShutdownPlugin: Failed to stop engine", error);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 退出进程
|
|
235
|
-
logger.info("GracefulShutdownPlugin: Process exiting");
|
|
236
|
-
process.exit(0);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* 清理信号监听器
|
|
241
|
-
*/
|
|
242
|
-
private cleanupSignalHandlers(): void {
|
|
243
|
-
for (const [signal, handler] of this.signalListeners.entries()) {
|
|
244
|
-
process.removeListener(signal, handler);
|
|
245
|
-
}
|
|
246
|
-
this.signalListeners.clear();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* 引擎销毁钩子:清理资源
|
|
251
|
-
*/
|
|
252
|
-
async onDestroy(): Promise<void> {
|
|
253
|
-
// 清理信号监听器
|
|
254
|
-
this.cleanupSignalHandlers();
|
|
255
|
-
|
|
256
|
-
// 清除超时定时器
|
|
257
|
-
if (this.shutdownTimer) {
|
|
258
|
-
clearTimeout(this.shutdownTimer);
|
|
259
|
-
this.shutdownTimer = null;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
logger.info("GracefulShutdownPlugin: Cleaned up");
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* 获取当前活跃处理器数量(用于测试和监控)
|
|
267
|
-
*/
|
|
268
|
-
getActiveHandlersCount(): number {
|
|
269
|
-
return this.activeHandlers;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 检查是否正在停机(用于测试和监控)
|
|
274
|
-
*/
|
|
275
|
-
isShuttingDownNow(): boolean {
|
|
276
|
-
return this.isShuttingDown;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|