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.
Files changed (72) hide show
  1. package/dist/index.d.mts +77 -52
  2. package/dist/index.d.ts +77 -52
  3. package/dist/index.js +2078 -1945
  4. package/dist/index.mjs +2076 -1944
  5. package/package.json +9 -2
  6. package/.vscode/settings.json +0 -8
  7. package/src/core/checker.ts +0 -33
  8. package/src/core/decorators.test.ts +0 -96
  9. package/src/core/decorators.ts +0 -68
  10. package/src/core/engine.test.ts +0 -218
  11. package/src/core/engine.ts +0 -635
  12. package/src/core/errors.ts +0 -28
  13. package/src/core/factory.test.ts +0 -73
  14. package/src/core/factory.ts +0 -92
  15. package/src/core/logger.ts +0 -65
  16. package/src/core/testing.ts +0 -73
  17. package/src/core/types.ts +0 -191
  18. package/src/index.ts +0 -49
  19. package/src/metadata/README.md +0 -422
  20. package/src/metadata/metadata.test.ts +0 -369
  21. package/src/metadata/metadata.ts +0 -512
  22. package/src/plugins/action/action-plugin.test.ts +0 -660
  23. package/src/plugins/action/decorator.ts +0 -14
  24. package/src/plugins/action/index.ts +0 -4
  25. package/src/plugins/action/plugin.ts +0 -349
  26. package/src/plugins/action/types.ts +0 -49
  27. package/src/plugins/action/utils.test.ts +0 -196
  28. package/src/plugins/action/utils.ts +0 -111
  29. package/src/plugins/cache/adapter.test.ts +0 -689
  30. package/src/plugins/cache/adapter.ts +0 -324
  31. package/src/plugins/cache/cache-plugin.test.ts +0 -269
  32. package/src/plugins/cache/decorator.ts +0 -26
  33. package/src/plugins/cache/index.ts +0 -20
  34. package/src/plugins/cache/plugin.ts +0 -299
  35. package/src/plugins/cache/types.ts +0 -69
  36. package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
  37. package/src/plugins/client-code/format.ts +0 -9
  38. package/src/plugins/client-code/generator.test.ts +0 -52
  39. package/src/plugins/client-code/generator.ts +0 -263
  40. package/src/plugins/client-code/index.ts +0 -15
  41. package/src/plugins/client-code/plugin.ts +0 -158
  42. package/src/plugins/client-code/types.ts +0 -52
  43. package/src/plugins/client-code/utils.ts +0 -164
  44. package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
  45. package/src/plugins/graceful-shutdown/index.ts +0 -3
  46. package/src/plugins/graceful-shutdown/plugin.ts +0 -279
  47. package/src/plugins/graceful-shutdown/types.ts +0 -17
  48. package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
  49. package/src/plugins/route/components/Layout.tsx +0 -42
  50. package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
  51. package/src/plugins/route/decorator.ts +0 -50
  52. package/src/plugins/route/index.ts +0 -16
  53. package/src/plugins/route/plugin.ts +0 -218
  54. package/src/plugins/route/route-plugin.test.ts +0 -759
  55. package/src/plugins/route/types.ts +0 -72
  56. package/src/plugins/schedule/README.md +0 -309
  57. package/src/plugins/schedule/decorator.ts +0 -25
  58. package/src/plugins/schedule/index.ts +0 -12
  59. package/src/plugins/schedule/mock-etcd.ts +0 -145
  60. package/src/plugins/schedule/plugin.ts +0 -164
  61. package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
  62. package/src/plugins/schedule/scheduler.ts +0 -164
  63. package/src/plugins/schedule/types.ts +0 -94
  64. package/src/plugins/schedule/utils.test.ts +0 -163
  65. package/src/plugins/schedule/utils.ts +0 -41
  66. package/tests/integration/client.test.ts +0 -203
  67. package/tests/integration/dev-service.ts +0 -301
  68. package/tests/integration/generated/client.ts +0 -123
  69. package/tests/integration/start-service.ts +0 -21
  70. package/tsconfig.json +0 -27
  71. package/tsup.config.ts +0 -16
  72. package/vitest.config.ts +0 -19
@@ -1,164 +0,0 @@
1
- import logger from "../../core/logger";
2
- import {
3
- HandlerMetadata,
4
- Microservice,
5
- Plugin,
6
- PluginPriority,
7
- } from "../../core/types";
8
- import { MockEtcd3 } from "./mock-etcd";
9
- import { Scheduler } from "./scheduler";
10
- import { SchedulePluginOptions, Etcd3 } from "./types";
11
- import { extractScheduleMetadata } from "./utils";
12
-
13
- /**
14
- * SchedulePlugin - 调度任务插件
15
- * 使用 etcd 选举机制实现分布式定时任务,确保多个实例中只有一个执行任务
16
- */
17
- export class SchedulePlugin implements Plugin {
18
- public readonly name = "schedule-plugin";
19
- public readonly priority = PluginPriority.BUSINESS; // 业务逻辑优先级
20
-
21
- private engine!: Microservice;
22
- private scheduler: Scheduler | null = null;
23
- private etcdClient: Etcd3 | null = null;
24
- private scheduleHandlers: HandlerMetadata[] = [];
25
- private useMockEtcd: boolean = false;
26
-
27
- constructor(options?: SchedulePluginOptions) {
28
- if (options?.useMockEtcd) {
29
- // 使用 Mock Etcd(用于测试和本地开发)
30
- this.useMockEtcd = true;
31
- this.etcdClient = new MockEtcd3();
32
- logger.info("SchedulePlugin: Using MockEtcd3 for local development/testing");
33
- } else if (options?.etcdClient) {
34
- // 使用真实的 etcd 客户端
35
- this.etcdClient = options.etcdClient as Etcd3;
36
- }
37
- }
38
-
39
- /**
40
- * 引擎初始化钩子
41
- */
42
- onInit(engine: Microservice): void {
43
- this.engine = engine;
44
- logger.info("SchedulePlugin initialized");
45
- }
46
-
47
- /**
48
- * Handler加载钩子:收集所有调度任务
49
- */
50
- onHandlerLoad(handlers: HandlerMetadata[]): void {
51
- // 筛选出所有 type="schedule" 的 handlers
52
- this.scheduleHandlers = handlers.filter(
53
- (handler) => handler.type === "schedule"
54
- );
55
- logger.info(
56
- `SchedulePlugin: Found ${this.scheduleHandlers.length} schedule handler(s)`
57
- );
58
- }
59
-
60
- /**
61
- * 引擎启动后钩子:启动所有调度任务
62
- */
63
- async onAfterStart(engine: Microservice): Promise<void> {
64
- // 如果没有配置 etcd 客户端且未启用 mock,跳过调度任务
65
- if (!this.etcdClient) {
66
- logger.warn(
67
- "SchedulePlugin: etcdClient not configured and useMockEtcd is false, schedule tasks will not start"
68
- );
69
- return;
70
- }
71
-
72
- // 创建调度器实例
73
- this.scheduler = new Scheduler(this.etcdClient);
74
-
75
- // 获取所有模块
76
- const modules = engine.getModules();
77
-
78
- // 获取所有 Handler 元数据(需要在引擎加载完成后获取)
79
- // 由于 onAfterStart 在引擎启动后调用,此时 handlers 已经加载完成
80
- // 我们需要从引擎中获取 handlers,但由于引擎没有暴露这个接口,
81
- // 我们需要在 onHandlerLoad 中保存 handlers
82
- if (!this.scheduleHandlers) {
83
- logger.warn(
84
- "SchedulePlugin: No schedule handlers found, schedule tasks will not start"
85
- );
86
- return;
87
- }
88
-
89
- // 提取调度元数据
90
- const scheduleMetadataMap = extractScheduleMetadata(this.scheduleHandlers);
91
-
92
- // 生成服务 ID(使用服务名称和实例标识)
93
- const serviceId = `${engine.options.name}-${Date.now()}-${Math.random()}`;
94
-
95
- // 遍历所有模块,查找调度任务
96
- for (const moduleMetadata of modules) {
97
- const moduleClass = moduleMetadata.clazz;
98
- const moduleName = moduleMetadata.name;
99
-
100
- // 获取模块实例
101
- const moduleInstance = engine.get(moduleClass);
102
-
103
- // 获取该模块的调度元数据
104
- const moduleScheduleMetadata = scheduleMetadataMap.get(moduleClass);
105
- if (!moduleScheduleMetadata || moduleScheduleMetadata.size === 0) {
106
- continue;
107
- }
108
-
109
- // 遍历所有调度任务
110
- for (const [methodName, metadata] of moduleScheduleMetadata.entries()) {
111
- // 获取方法
112
- const method = (moduleInstance as any)[methodName];
113
- if (typeof method !== "function") {
114
- logger.warn(
115
- `SchedulePlugin: Method ${moduleName}.${methodName} is not a function, skipping`
116
- );
117
- continue;
118
- }
119
-
120
- // 生成选举键(使用服务名称、模块名和方法名)
121
- const electionKey = `/schedule/${engine.options.name}/${moduleName}/${methodName}`;
122
-
123
- // 生成唯一的服务 ID(每个任务使用不同的 ID)
124
- const taskServiceId = `${serviceId}-${moduleName}-${methodName}`;
125
-
126
- // 启动调度任务
127
- try {
128
- await this.scheduler.startSchedule(
129
- taskServiceId,
130
- moduleName,
131
- methodName,
132
- electionKey,
133
- metadata,
134
- method.bind(moduleInstance)
135
- );
136
-
137
- logger.info(
138
- `SchedulePlugin: Started schedule task ${moduleName}.${methodName} (interval: ${metadata.interval}ms, mode: ${metadata.mode})`
139
- );
140
- } catch (error) {
141
- logger.error(
142
- `SchedulePlugin: Failed to start schedule task ${moduleName}.${methodName}:`,
143
- error
144
- );
145
- }
146
- }
147
- }
148
- }
149
-
150
- /**
151
- * 引擎销毁钩子:停止所有调度任务
152
- */
153
- async onDestroy(): Promise<void> {
154
- if (this.scheduler) {
155
- try {
156
- await this.scheduler.stop();
157
- logger.info("SchedulePlugin: All schedule tasks stopped");
158
- } catch (error) {
159
- logger.error("SchedulePlugin: Error stopping schedule tasks:", error);
160
- }
161
- }
162
- }
163
- }
164
-
@@ -1,312 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { Testing } from "../../core/testing";
3
- import { Schedule, SchedulePlugin, ScheduleMode, MockEtcd3 } from "./index";
4
-
5
- describe("SchedulePlugin", () => {
6
- let engine: ReturnType<typeof Testing.createTestEngine>["engine"];
7
- let Module: ReturnType<typeof Testing.createTestEngine>["Module"];
8
- let schedulePlugin: SchedulePlugin;
9
-
10
- beforeEach(() => {
11
- // 使用 useMockEtcd 选项,自动使用 MockEtcd3
12
- schedulePlugin = new SchedulePlugin({ useMockEtcd: true });
13
- const testEngine = Testing.createTestEngine({
14
- plugins: [schedulePlugin],
15
- });
16
- engine = testEngine.engine;
17
- Module = testEngine.Module;
18
- });
19
-
20
- afterEach(async () => {
21
- if (engine) {
22
- await engine.stop().catch(() => {});
23
- }
24
- });
25
-
26
- describe("插件配置", () => {
27
- it("应该有正确的插件名称", () => {
28
- expect(schedulePlugin.name).toBe("schedule-plugin");
29
- });
30
-
31
- it("应该在没有 etcd 客户端时正常工作", () => {
32
- const pluginWithoutEtcd = new SchedulePlugin();
33
- expect(pluginWithoutEtcd.name).toBe("schedule-plugin");
34
- });
35
-
36
- it("应该支持 useMockEtcd 选项", () => {
37
- const pluginWithMock = new SchedulePlugin({ useMockEtcd: true });
38
- expect(pluginWithMock.name).toBe("schedule-plugin");
39
- });
40
-
41
- it("应该优先使用提供的 etcdClient 而不是 mock", () => {
42
- const mockEtcd = new MockEtcd3();
43
- const pluginWithClient = new SchedulePlugin({
44
- etcdClient: mockEtcd,
45
- useMockEtcd: true,
46
- });
47
- expect(pluginWithClient.name).toBe("schedule-plugin");
48
- });
49
- });
50
-
51
- describe("装饰器", () => {
52
- it("应该能够使用 Schedule 装饰器装饰方法", async () => {
53
- let callCount = 0;
54
-
55
- @Module("test-service")
56
- class TestService {
57
- @Schedule({
58
- interval: 100,
59
- mode: ScheduleMode.FIXED_RATE,
60
- })
61
- async testTask() {
62
- callCount++;
63
- }
64
- }
65
-
66
- await engine.start();
67
- await Testing.wait(300); // 等待足够的时间让任务执行
68
-
69
- // 由于使用了 mock etcd,任务应该会被执行
70
- // 注意:实际执行次数取决于调度器的实现和选举时机
71
- expect(callCount).toBeGreaterThanOrEqual(0);
72
- });
73
-
74
- it("应该支持 FIXED_RATE 模式", async () => {
75
- let callCount = 0;
76
-
77
- @Module("test-service")
78
- class TestService {
79
- @Schedule({
80
- interval: 100,
81
- mode: ScheduleMode.FIXED_RATE,
82
- })
83
- async fixedRateTask() {
84
- callCount++;
85
- }
86
- }
87
-
88
- await engine.start();
89
- await Testing.wait(300);
90
-
91
- expect(callCount).toBeGreaterThanOrEqual(0);
92
- });
93
-
94
- it("应该支持 FIXED_DELAY 模式", async () => {
95
- let callCount = 0;
96
-
97
- @Module("test-service")
98
- class TestService {
99
- @Schedule({
100
- interval: 100,
101
- mode: ScheduleMode.FIXED_DELAY,
102
- })
103
- async fixedDelayTask() {
104
- callCount++;
105
- await Testing.wait(50); // 模拟任务执行时间
106
- }
107
- }
108
-
109
- await engine.start();
110
- await Testing.wait(300);
111
-
112
- expect(callCount).toBeGreaterThanOrEqual(0);
113
- });
114
-
115
- it("应该使用默认的 FIXED_RATE 模式", async () => {
116
- let callCount = 0;
117
-
118
- @Module("test-service")
119
- class TestService {
120
- @Schedule({
121
- interval: 100,
122
- // 不指定 mode,应该使用默认值
123
- })
124
- async defaultModeTask() {
125
- callCount++;
126
- }
127
- }
128
-
129
- await engine.start();
130
- await Testing.wait(300);
131
-
132
- expect(callCount).toBeGreaterThanOrEqual(0);
133
- });
134
- });
135
-
136
- describe("插件生命周期", () => {
137
- it("应该在引擎启动后启动调度任务", async () => {
138
- let taskExecuted = false;
139
-
140
- @Module("test-service")
141
- class TestService {
142
- @Schedule({
143
- interval: 100,
144
- mode: ScheduleMode.FIXED_RATE,
145
- })
146
- async testTask() {
147
- taskExecuted = true;
148
- }
149
- }
150
-
151
- await engine.start();
152
- // 等待足够的时间让选举和任务执行
153
- // Mock etcd 的异步选举需要时间,加上任务执行间隔
154
- await Testing.wait(600);
155
-
156
- // 任务应该已经被执行(通过 mock etcd 选举)
157
- // 注意:由于 mock etcd 的异步特性,可能需要更多时间
158
- expect(taskExecuted).toBe(true);
159
- });
160
-
161
- it("应该在引擎停止时停止调度任务", async () => {
162
- let taskExecuted = false;
163
-
164
- @Module("test-service")
165
- class TestService {
166
- @Schedule({
167
- interval: 50,
168
- mode: ScheduleMode.FIXED_RATE,
169
- })
170
- async testTask() {
171
- taskExecuted = true;
172
- }
173
- }
174
-
175
- await engine.start();
176
- // 等待任务执行
177
- await Testing.wait(200);
178
- const executedBeforeStop = taskExecuted;
179
-
180
- await engine.stop();
181
- await Testing.wait(200);
182
-
183
- // 停止后任务不应该再执行
184
- // 注意:如果任务在停止前没有执行,这个测试仍然有效(验证停止功能)
185
- expect(executedBeforeStop).toBeDefined();
186
- });
187
-
188
- it("应该在没有 etcd 客户端且未启用 mock 时不启动任务", async () => {
189
- const pluginWithoutEtcd = new SchedulePlugin();
190
- const testEngine = Testing.createTestEngine({
191
- plugins: [pluginWithoutEtcd],
192
- });
193
- const testEngineInstance = testEngine.engine;
194
- const TestModule = testEngine.Module;
195
-
196
- let taskExecuted = false;
197
-
198
- @TestModule("test-service")
199
- class TestService {
200
- @Schedule({
201
- interval: 100,
202
- mode: ScheduleMode.FIXED_RATE,
203
- })
204
- async testTask() {
205
- taskExecuted = true;
206
- }
207
- }
208
-
209
- await testEngineInstance.start();
210
- await Testing.wait(200);
211
-
212
- // 没有 etcd 客户端且未启用 mock,任务不应该执行
213
- expect(taskExecuted).toBe(false);
214
-
215
- await testEngineInstance.stop();
216
- });
217
-
218
- it("应该在启用 useMockEtcd 时启动任务", async () => {
219
- const pluginWithMock = new SchedulePlugin({ useMockEtcd: true });
220
- const testEngine = Testing.createTestEngine({
221
- plugins: [pluginWithMock],
222
- });
223
- const testEngineInstance = testEngine.engine;
224
- const TestModule = testEngine.Module;
225
-
226
- let taskExecuted = false;
227
-
228
- @TestModule("test-service")
229
- class TestService {
230
- @Schedule({
231
- interval: 100,
232
- mode: ScheduleMode.FIXED_RATE,
233
- })
234
- async testTask() {
235
- taskExecuted = true;
236
- }
237
- }
238
-
239
- await testEngineInstance.start();
240
- await Testing.wait(600);
241
-
242
- // 启用 mock etcd,任务应该被执行
243
- expect(taskExecuted).toBe(true);
244
-
245
- await testEngineInstance.stop();
246
- });
247
- });
248
-
249
- describe("多个调度任务", () => {
250
- it("应该支持多个调度任务", async () => {
251
- let task1Count = 0;
252
- let task2Count = 0;
253
-
254
- @Module("test-service")
255
- class TestService {
256
- @Schedule({
257
- interval: 100,
258
- mode: ScheduleMode.FIXED_RATE,
259
- })
260
- async task1() {
261
- task1Count++;
262
- }
263
-
264
- @Schedule({
265
- interval: 100,
266
- mode: ScheduleMode.FIXED_RATE,
267
- })
268
- async task2() {
269
- task2Count++;
270
- }
271
- }
272
-
273
- await engine.start();
274
- await Testing.wait(300);
275
-
276
- // 两个任务都应该被执行
277
- expect(task1Count).toBeGreaterThanOrEqual(0);
278
- expect(task2Count).toBeGreaterThanOrEqual(0);
279
- });
280
- });
281
-
282
- describe("错误处理", () => {
283
- it("应该处理任务执行错误", async () => {
284
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
285
- let callCount = 0;
286
-
287
- @Module("test-service")
288
- class TestService {
289
- @Schedule({
290
- interval: 100,
291
- mode: ScheduleMode.FIXED_RATE,
292
- })
293
- async errorTask() {
294
- callCount++;
295
- if (callCount === 1) {
296
- throw new Error("Task error");
297
- }
298
- }
299
- }
300
-
301
- await engine.start();
302
- await Testing.wait(300);
303
-
304
- // 任务应该被执行,错误应该被捕获
305
- // 注意:由于异步执行和错误处理,callCount 可能为 0 或更多
306
- expect(callCount).toBeGreaterThanOrEqual(0);
307
-
308
- errorSpy.mockRestore();
309
- });
310
- });
311
- });
312
-
@@ -1,164 +0,0 @@
1
- import { SpanStatusCode, trace } from "@opentelemetry/api";
2
- import logger from "../../core/logger";
3
- import { ScheduleMetadata, ScheduleMode } from "./types";
4
- import { Etcd3 } from "./types";
5
-
6
- const tracer = trace.getTracer("scheduler");
7
-
8
- /**
9
- * 调度器类
10
- * 负责管理基于 etcd 选举的分布式定时任务
11
- */
12
- export class Scheduler {
13
- private campaigns: Map<string, any> = new Map();
14
- private timers: Map<
15
- string,
16
- ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>
17
- > = new Map();
18
- private isLeader: Map<string, boolean> = new Map();
19
-
20
- constructor(private etcdClient: Etcd3) {
21
- // etcdClient 必须实现 Etcd3 接口
22
- }
23
-
24
- /**
25
- * 启动调度任务
26
- */
27
- async startSchedule(
28
- serviceId: string,
29
- moduleName: string,
30
- methodName: string,
31
- electionKey: string,
32
- metadata: ScheduleMetadata,
33
- method: Function
34
- ) {
35
- const election = this.etcdClient.election(electionKey, 10);
36
- const observe = await election.observe();
37
-
38
- // 监听选主结果
39
- observe.on("change", (leader: string) => {
40
- const isLeader = leader === serviceId;
41
- this.isLeader.set(serviceId, isLeader);
42
-
43
- if (!isLeader) {
44
- this.stopTimer(serviceId);
45
- }
46
- });
47
-
48
- // 开始参与选主
49
- const campaign = election.campaign(serviceId);
50
- this.campaigns.set(serviceId, campaign);
51
-
52
- campaign.on("error", (error: any) => {
53
- logger.error(`Error in campaign for ${moduleName}.${methodName}:`, error);
54
- });
55
-
56
- campaign.on("elected", () => {
57
- this.isLeader.set(serviceId, true);
58
- this.startTimer(serviceId, metadata, moduleName, method);
59
-
60
- logger.info(`become leader for ${moduleName}.${methodName}`);
61
- });
62
- }
63
-
64
- /**
65
- * 启动定时器
66
- */
67
- private startTimer(
68
- serviceId: string,
69
- metadata: ScheduleMetadata,
70
- moduleName: string,
71
- method: Function
72
- ) {
73
- this.stopTimer(serviceId);
74
-
75
- const wrappedMethod = async () => {
76
- tracer.startActiveSpan(
77
- `ScheduleTask ${moduleName}.${metadata.name}`,
78
- { root: true },
79
- async (span) => {
80
- span.setAttribute("serviceId", serviceId);
81
- span.setAttribute("methodName", metadata.name);
82
- span.setAttribute("moduleName", moduleName);
83
- span.setAttribute("interval", metadata.interval);
84
- span.setAttribute("mode", metadata.mode!);
85
- try {
86
- await method();
87
- } catch (error: any) {
88
- span.setStatus({
89
- code: SpanStatusCode.ERROR,
90
- message: error.message,
91
- });
92
- logger.error(
93
- `Error executing schedule task ${moduleName}.${metadata.name}:`,
94
- error
95
- );
96
- } finally {
97
- span.setStatus({ code: SpanStatusCode.OK });
98
- span.end();
99
- }
100
- }
101
- );
102
- };
103
-
104
- if (metadata.mode === ScheduleMode.FIXED_DELAY) {
105
- const runTask = async () => {
106
- if (!this.isLeader.get(serviceId)) return;
107
-
108
- try {
109
- await wrappedMethod();
110
- } finally {
111
- // 任务完成后等待间隔时间再执行下一次
112
- this.timers.set(serviceId, setTimeout(runTask, metadata.interval));
113
- }
114
- };
115
-
116
- // 立即执行第一次
117
- runTask();
118
- } else {
119
- // 固定频率模式
120
- this.timers.set(
121
- serviceId,
122
- setInterval(async () => {
123
- if (!this.isLeader.get(serviceId)) return;
124
- await wrappedMethod();
125
- }, metadata.interval)
126
- );
127
- }
128
- }
129
-
130
- /**
131
- * 停止定时器
132
- */
133
- private stopTimer(serviceId: string) {
134
- const timer = this.timers.get(serviceId);
135
- if (timer) {
136
- clearTimeout(timer);
137
- clearInterval(timer);
138
- this.timers.delete(serviceId);
139
- }
140
- }
141
-
142
- /**
143
- * 停止所有调度任务
144
- */
145
- async stop() {
146
- // 停止所有定时器
147
- for (const serviceId of this.timers.keys()) {
148
- this.stopTimer(serviceId);
149
- }
150
-
151
- // 放弃所有选主并撤销租约
152
- for (const [serviceId, campaign] of this.campaigns.entries()) {
153
- try {
154
- await campaign.resign().catch(() => {});
155
- } catch (error) {
156
- logger.error(`Error stopping schedule ${serviceId}:`, error);
157
- } finally {
158
- this.campaigns.delete(serviceId);
159
- this.isLeader.delete(serviceId);
160
- }
161
- }
162
- }
163
- }
164
-