koatty_schedule 3.3.1 → 3.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [3.3.2](https://github.com/thinkkoa/koatty_schedule/compare/v3.3.1...v3.3.2) (2025-06-22)
6
+
5
7
  ### [3.3.1](https://github.com/thinkkoa/koatty_schedule/compare/v3.3.0...v3.3.1) (2025-06-22)
6
8
 
7
9
 
package/README.md CHANGED
@@ -16,6 +16,7 @@ Powerful scheduled tasks and distributed locking solution for Koatty framework.
16
16
  - 🌍 **Timezone Smart**: Three-tier priority system for timezone configuration
17
17
  - 📊 **Health Monitoring**: Built-in health checks and detailed status reporting
18
18
  - 🔧 **Easy Configuration**: Method-level and global configuration options
19
+ - 🚀 **Smart Initialization**: Unified initialization timing for optimal dependency resolution
19
20
 
20
21
  ## Installation
21
22
 
@@ -391,7 +392,8 @@ DEBUG=koatty_schedule* npm start
391
392
 
392
393
  #### `@Scheduled(cron: string, timezone?: string)`
393
394
  - `cron`: Cron expression (6-part format with seconds)
394
- - `timezone`: Optional timezone override
395
+ - `timezone`: Optional timezone override (defaults to 'Asia/Beijing')
396
+ - **Processing**: Records metadata in IOC container, CronJob created at `appReady`
395
397
 
396
398
  #### `@RedLock(lockName?: string, options?: RedLockMethodOptions)`
397
399
  - `lockName`: Unique lock identifier (auto-generated if not provided)
package/dist/README.md CHANGED
@@ -16,6 +16,7 @@ Powerful scheduled tasks and distributed locking solution for Koatty framework.
16
16
  - 🌍 **Timezone Smart**: Three-tier priority system for timezone configuration
17
17
  - 📊 **Health Monitoring**: Built-in health checks and detailed status reporting
18
18
  - 🔧 **Easy Configuration**: Method-level and global configuration options
19
+ - 🚀 **Smart Initialization**: Unified initialization timing for optimal dependency resolution
19
20
 
20
21
  ## Installation
21
22
 
@@ -391,7 +392,8 @@ DEBUG=koatty_schedule* npm start
391
392
 
392
393
  #### `@Scheduled(cron: string, timezone?: string)`
393
394
  - `cron`: Cron expression (6-part format with seconds)
394
- - `timezone`: Optional timezone override
395
+ - `timezone`: Optional timezone override (defaults to 'Asia/Beijing')
396
+ - **Processing**: Records metadata in IOC container, CronJob created at `appReady`
395
397
 
396
398
  #### `@RedLock(lockName?: string, options?: RedLockMethodOptions)`
397
399
  - `lockName`: Unique lock identifier (auto-generated if not provided)
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-22 23:42:00
3
+ * @Date: 2025-06-23 00:55:18
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-22 23:41:54
3
+ * @Date: 2025-06-23 00:55:12
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -8,10 +8,10 @@
8
8
  'use strict';
9
9
 
10
10
  var koatty_container = require('koatty_container');
11
- var koatty_lib = require('koatty_lib');
12
11
  var redlock = require('@sesamecare-oss/redlock');
13
12
  var ioredis = require('ioredis');
14
13
  var koatty_logger = require('koatty_logger');
14
+ var koatty_lib = require('koatty_lib');
15
15
  var cron = require('cron');
16
16
 
17
17
  /*
@@ -24,7 +24,6 @@ var cron = require('cron');
24
24
  * @Copyright (c): <richenlin(at)gmail.com>
25
25
  */
26
26
  const COMPONENT_SCHEDULED = 'COMPONENT_SCHEDULED';
27
- const COMPONENT_REDLOCK = 'COMPONENT_REDLOCK';
28
27
  /**
29
28
  * Decorator types supported by the system
30
29
  */
@@ -110,13 +109,6 @@ function validateRedLockMethodOptions(options) {
110
109
  * Global configuration storage
111
110
  */
112
111
  let globalScheduledOptions = {};
113
- /**
114
- * Set global scheduled options
115
- * @param options - Global scheduled options
116
- */
117
- function setGlobalScheduledOptions(options) {
118
- globalScheduledOptions = { ...options };
119
- }
120
112
  /**
121
113
  * Get global scheduled options
122
114
  * @returns Global scheduled options
@@ -129,8 +121,8 @@ function getGlobalScheduledOptions() {
129
121
  * @param userTimezone - User specified timezone
130
122
  * @returns Effective timezone
131
123
  */
132
- function getEffectiveTimezone(userTimezone) {
133
- return userTimezone || globalScheduledOptions.timezone || 'Asia/Beijing';
124
+ function getEffectiveTimezone(options, userTimezone) {
125
+ return userTimezone || options.timezone || 'Asia/Beijing';
134
126
  }
135
127
  /**
136
128
  * Get effective RedLock method options with priority: method options > global options > defaults
@@ -147,150 +139,6 @@ function getEffectiveRedLockOptions(methodOptions) {
147
139
  };
148
140
  }
149
141
 
150
- /*
151
- * @Description:
152
- * @Usage:
153
- * @Author: richen
154
- * @Date: 2025-06-09 16:00:00
155
- * @LastEditTime: 2025-06-09 16:00:00
156
- * @License: BSD (3-Clause)
157
- * @Copyright (c): <richenlin(at)gmail.com>
158
- */
159
- /**
160
- * Redis-based distributed lock decorator
161
- *
162
- * @export
163
- * @param {string} [name] - The locker name. If name is duplicated, lock sharing contention will result.
164
- * If not provided, a unique name will be auto-generated using method name + random suffix.
165
- * IMPORTANT: Auto-generated names are unique per method deployment and not predictable.
166
- * @param {RedLockMethodOptions} [options] - Lock configuration options for this method
167
- *
168
- * @returns {MethodDecorator}
169
- * @throws {Error} When decorator is used on wrong class type or invalid configuration
170
- *
171
- * @example
172
- * ```typescript
173
- * class UserService {
174
- * @RedLock('user_update_lock', { lockTimeOut: 5000, maxRetries: 2 })
175
- * async updateUser(id: string, data: any) {
176
- * // This method will be protected by a distributed lock with predictable name
177
- * }
178
- *
179
- * @RedLock() // Auto-generated unique name like "deleteUser_abc123_xyz789"
180
- * async deleteUser(id: string) {
181
- * // This method will be protected by a distributed lock with auto-generated unique name
182
- * }
183
- * }
184
- * ```
185
- */
186
- function RedLock(lockName, options) {
187
- return (target, propertyKey, descriptor) => {
188
- const methodName = propertyKey.toString();
189
- // 验证装饰器使用的类型(从原型对象获取类构造函数)
190
- const targetClass = target.constructor;
191
- const componentType = koatty_container.IOCContainer.getType(targetClass);
192
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
193
- throw Error("@RedLock decorator can only be used on SERVICE or COMPONENT classes.");
194
- }
195
- // 验证方法名
196
- if (!methodName || typeof methodName !== 'string') {
197
- throw Error("Method name is required for @RedLock decorator");
198
- }
199
- // 验证方法描述符
200
- if (!descriptor || typeof descriptor.value !== 'function') {
201
- throw Error("@RedLock decorator can only be applied to methods");
202
- }
203
- // 生成唯一的锁名称:用户指定的 > 自动生成的唯一名称
204
- if (!lockName || lockName.trim() === '') {
205
- const randomSuffix = Math.random().toString(36).substring(2, 8); // 6位随机字符
206
- const timestamp = Date.now().toString(36); // 时间戳转36进制
207
- lockName = `${methodName}_${randomSuffix}_${timestamp}`;
208
- }
209
- // 验证选项
210
- if (options) {
211
- validateRedLockMethodOptions(options);
212
- }
213
- // 保存类到IOC容器
214
- koatty_container.IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
215
- // 保存RedLock元数据到 IOC 容器(lockName已确定)
216
- koatty_container.IOCContainer.attachClassMetadata(COMPONENT_REDLOCK, DecoratorType.REDLOCK, {
217
- method: methodName,
218
- name: lockName, // 确定的锁名称,不会为undefined
219
- options
220
- }, target, methodName);
221
- };
222
- }
223
-
224
- /*
225
- * @Description:
226
- * @Usage:
227
- * @Author: richen
228
- * @Date: 2025-06-09 16:00:00
229
- * @LastEditTime: 2025-06-09 16:00:00
230
- * @License: BSD (3-Clause)
231
- * @Copyright (c): <richenlin(at)gmail.com>
232
- */
233
- /**
234
- * Schedule task decorator with optimized preprocessing
235
- *
236
- * @export
237
- * @param {string} cron - Cron expression for task scheduling
238
- * @param {string} [timezone='Asia/Beijing'] - Timezone for the schedule
239
- *
240
- * Cron expression format:
241
- * * Seconds: 0-59
242
- * * Minutes: 0-59
243
- * * Hours: 0-23
244
- * * Day of Month: 1-31
245
- * * Months: 1-12 (Jan-Dec)
246
- * * Day of Week: 1-7 (Sun-Sat)
247
- *
248
- * @returns {MethodDecorator}
249
- * @throws {Error} When cron expression is invalid or decorator is used on wrong class type
250
- */
251
- function Scheduled(cron, timezone) {
252
- // 参数验证
253
- if (koatty_lib.Helper.isEmpty(cron)) {
254
- throw Error("Cron expression is required and cannot be empty");
255
- }
256
- // 验证cron表达式格式
257
- try {
258
- validateCronExpression(cron);
259
- }
260
- catch (error) {
261
- throw Error(`Invalid cron expression: ${error.message}`);
262
- }
263
- // 验证时区
264
- if (timezone && typeof timezone !== 'string') {
265
- throw Error("Timezone must be a string");
266
- }
267
- return (target, propertyKey, descriptor) => {
268
- // 验证装饰器使用的类型(从原型对象获取类构造函数)
269
- const targetClass = target.constructor;
270
- const componentType = koatty_container.IOCContainer.getType(targetClass);
271
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
272
- throw Error("@Scheduled decorator can only be used on SERVICE or COMPONENT classes.");
273
- }
274
- // 验证方法名
275
- const methodName = propertyKey.toString();
276
- if (!methodName || typeof methodName !== 'string') {
277
- throw Error("Method name is required for @Scheduled decorator");
278
- }
279
- // 验证方法描述符
280
- if (!descriptor || typeof descriptor.value !== 'function') {
281
- throw Error("@Scheduled decorator can only be applied to methods");
282
- }
283
- // 保存类到IOC容器
284
- koatty_container.IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
285
- // 保存调度元数据到 IOC 容器
286
- koatty_container.IOCContainer.attachClassMetadata(COMPONENT_SCHEDULED, DecoratorType.SCHEDULED, {
287
- method: methodName,
288
- cron,
289
- timezone // 保存用户指定的值,可能为undefined
290
- }, target, methodName);
291
- };
292
- }
293
-
294
142
  /*
295
143
  * @Description: RedLock utility for distributed locks
296
144
  * @Usage:
@@ -688,6 +536,8 @@ function timeoutPromise(ms) {
688
536
  /**
689
537
  * Initiation schedule locker client.
690
538
  *
539
+ * @param {RedLockOptions} options - RedLock 配置选项
540
+ * @param {Koatty} app - Koatty 应用实例
691
541
  * @returns {Promise<void>}
692
542
  */
693
543
  async function initRedLock(options, app) {
@@ -695,11 +545,12 @@ async function initRedLock(options, app) {
695
545
  koatty_logger.DefaultLogger.Warn(`RedLock initialization skipped: Koatty app not available or not initialized`);
696
546
  return;
697
547
  }
698
- app.once("appStart", async function () {
548
+ app.once("appReady", async function () {
699
549
  try {
700
550
  if (koatty_lib.Helper.isEmpty(options)) {
701
551
  throw Error(`Missing RedLock configuration. Please write a configuration item with the key name 'RedLock' in the db.ts file.`);
702
552
  }
553
+ // 获取RedLocker实例,在首次使用时自动初始化
703
554
  const redLocker = RedLocker.getInstance(options);
704
555
  await redLocker.initialize();
705
556
  koatty_logger.DefaultLogger.Info('RedLock initialized successfully');
@@ -710,72 +561,12 @@ async function initRedLock(options, app) {
710
561
  }
711
562
  });
712
563
  }
713
- /**
714
- * 批量注入RedLock锁 - 从IOC容器读取类元数据并应用所有RedLock装饰器
715
- *
716
- * @param {RedLockOptions} options - RedLock 配置选项
717
- * @param {Koatty} app - Koatty 应用实例
718
- */
719
- async function injectRedLock(_options, _app) {
720
- try {
721
- koatty_logger.DefaultLogger.Debug('Starting batch RedLock injection...');
722
- const componentList = koatty_container.IOCContainer.listClass("COMPONENT");
723
- for (const component of componentList) {
724
- const classMetadata = koatty_container.IOCContainer.getClassMetadata(COMPONENT_REDLOCK, DecoratorType.REDLOCK, component.target);
725
- if (!classMetadata) {
726
- continue;
727
- }
728
- let redlockCount = 0;
729
- for (const [className, metadata] of classMetadata) {
730
- try {
731
- const instance = koatty_container.IOCContainer.get(className);
732
- if (!instance) {
733
- continue;
734
- }
735
- // 查找所有RedLock方法的元数据
736
- for (const [key, value] of Object.entries(metadata)) {
737
- if (key.startsWith('REDLOCK')) {
738
- const redlockData = value;
739
- const targetMethod = instance[redlockData.method];
740
- if (!koatty_lib.Helper.isFunction(targetMethod)) {
741
- koatty_logger.DefaultLogger.Warn(`RedLock injection skipped: method ${redlockData.method} is not a function in ${className}`);
742
- continue;
743
- }
744
- // 生成有效的RedLock选项:方法级别 > 全局配置 > 默认值
745
- const effectiveOptions = getEffectiveRedLockOptions(redlockData.options);
746
- // 应用RedLock增强描述符
747
- const originalDescriptor = {
748
- value: targetMethod,
749
- writable: true,
750
- enumerable: false,
751
- configurable: true
752
- };
753
- const enhancedDescriptor = redLockerDescriptor(originalDescriptor, redlockData.name, // 使用装饰器中确定的锁名称
754
- redlockData.method, effectiveOptions);
755
- // 替换原方法
756
- Object.defineProperty(instance, redlockData.method, enhancedDescriptor);
757
- redlockCount++;
758
- koatty_logger.DefaultLogger.Debug(`RedLock applied to ${className}.${redlockData.method} with lock name: ${redlockData.name}`);
759
- }
760
- }
761
- }
762
- catch (error) {
763
- koatty_logger.DefaultLogger.Error(`Failed to process class ${className}:`, error);
764
- }
765
- }
766
- koatty_logger.DefaultLogger.Info(`Batch RedLock injection completed. ${redlockCount} locks applied.`);
767
- }
768
- }
769
- catch (error) {
770
- koatty_logger.DefaultLogger.Error('Failed to inject RedLocks:', error);
771
- }
772
- }
773
564
  /**
774
565
  * Create redLocker Descriptor with improved error handling and type safety
775
566
  * @param descriptor - Property descriptor
776
567
  * @param name - Lock name
777
568
  * @param method - Method name
778
- * @param options - RedLock options
569
+ * @param methodOptions - Method-level RedLock options
779
570
  * @returns Enhanced property descriptor
780
571
  */
781
572
  function redLockerDescriptor(descriptor, name, method, methodOptions) {
@@ -794,13 +585,6 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
794
585
  if (typeof value !== 'function') {
795
586
  throw new Error('Descriptor value must be a function');
796
587
  }
797
- // 设置默认选项,合并方法级别的选项
798
- const lockOptions = {
799
- lockTimeOut: methodOptions?.lockTimeOut,
800
- clockDriftFactor: methodOptions?.clockDriftFactor,
801
- maxRetries: methodOptions?.maxRetries,
802
- retryDelayMs: methodOptions?.retryDelayMs
803
- };
804
588
  /**
805
589
  * Enhanced function wrapper with proper lock renewal and safety
806
590
  */
@@ -864,6 +648,7 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
864
648
  async value(...props) {
865
649
  try {
866
650
  const redlock = RedLocker.getInstance();
651
+ const lockOptions = getEffectiveRedLockOptions(methodOptions);
867
652
  // Acquire a lock.
868
653
  const lockTime = lockOptions.lockTimeOut || 10000;
869
654
  if (lockTime <= 200) {
@@ -881,6 +666,169 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
881
666
  },
882
667
  };
883
668
  }
669
+ /**
670
+ * Generate lock name for RedLock decorator
671
+ */
672
+ function generateLockName(configName, methodName, target) {
673
+ if (configName) {
674
+ return configName;
675
+ }
676
+ try {
677
+ const targetObj = target;
678
+ const identifier = koatty_container.IOCContainer.getIdentifier(targetObj);
679
+ if (identifier) {
680
+ return `${identifier}_${methodName}`;
681
+ }
682
+ }
683
+ catch {
684
+ // Fallback if IOC container is not available
685
+ }
686
+ const targetWithConstructor = target;
687
+ const className = targetWithConstructor.constructor?.name || 'Unknown';
688
+ return `${className}_${methodName}`;
689
+ }
690
+
691
+ /*
692
+ * @Description:
693
+ * @Usage:
694
+ * @Author: richen
695
+ * @Date: 2025-06-09 16:00:00
696
+ * @LastEditTime: 2025-06-09 16:00:00
697
+ * @License: BSD (3-Clause)
698
+ * @Copyright (c): <richenlin(at)gmail.com>
699
+ */
700
+ /**
701
+ * Redis-based distributed lock decorator
702
+ *
703
+ * @export
704
+ * @param {string} [name] - The locker name. If name is duplicated, lock sharing contention will result.
705
+ * If not provided, a unique name will be auto-generated using method name + random suffix.
706
+ * IMPORTANT: Auto-generated names are unique per method deployment and not predictable.
707
+ * @param {RedLockMethodOptions} [options] - Lock configuration options for this method
708
+ *
709
+ * @returns {MethodDecorator}
710
+ * @throws {Error} When decorator is used on wrong class type or invalid configuration
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * class UserService {
715
+ * @RedLock('user_update_lock', { lockTimeOut: 5000, maxRetries: 2 })
716
+ * async updateUser(id: string, data: any) {
717
+ * // This method will be protected by a distributed lock with predictable name
718
+ * }
719
+ *
720
+ * @RedLock() // Auto-generated unique name like "deleteUser_abc123_xyz789"
721
+ * async deleteUser(id: string) {
722
+ * // This method will be protected by a distributed lock with auto-generated unique name
723
+ * }
724
+ * }
725
+ * ```
726
+ */
727
+ function RedLock(lockName, options) {
728
+ return (target, propertyKey, descriptor) => {
729
+ const methodName = propertyKey.toString();
730
+ // 验证装饰器使用的类型(从原型对象获取类构造函数)
731
+ const targetClass = target.constructor;
732
+ const componentType = koatty_container.IOCContainer.getType(targetClass);
733
+ if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
734
+ throw Error("@RedLock decorator can only be used on SERVICE or COMPONENT classes.");
735
+ }
736
+ // 验证方法名
737
+ if (!methodName || typeof methodName !== 'string') {
738
+ throw Error("Method name is required for @RedLock decorator");
739
+ }
740
+ // 验证方法描述符
741
+ if (!descriptor || typeof descriptor.value !== 'function') {
742
+ throw Error("@RedLock decorator can only be applied to methods");
743
+ }
744
+ // 生成锁名称:用户指定的 > 基于类名和方法名生成
745
+ const finalLockName = lockName || generateLockName(lockName, methodName, target);
746
+ // 验证选项
747
+ if (options) {
748
+ validateRedLockMethodOptions(options);
749
+ }
750
+ // 保存类到IOC容器
751
+ koatty_container.IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
752
+ try {
753
+ // 直接在装饰器中包装方法,而不是延迟处理
754
+ const enhancedDescriptor = redLockerDescriptor(descriptor, finalLockName, methodName, options);
755
+ return enhancedDescriptor;
756
+ }
757
+ catch (error) {
758
+ throw new Error(`Failed to apply RedLock to ${methodName}: ${error.message}`);
759
+ }
760
+ };
761
+ }
762
+
763
+ /*
764
+ * @Description:
765
+ * @Usage:
766
+ * @Author: richen
767
+ * @Date: 2025-06-09 16:00:00
768
+ * @LastEditTime: 2025-06-09 16:00:00
769
+ * @License: BSD (3-Clause)
770
+ * @Copyright (c): <richenlin(at)gmail.com>
771
+ */
772
+ /**
773
+ * Schedule task decorator with optimized preprocessing
774
+ *
775
+ * @export
776
+ * @param {string} cron - Cron expression for task scheduling
777
+ * @param {string} [timezone='Asia/Beijing'] - Timezone for the schedule
778
+ *
779
+ * Cron expression format:
780
+ * * Seconds: 0-59
781
+ * * Minutes: 0-59
782
+ * * Hours: 0-23
783
+ * * Day of Month: 1-31
784
+ * * Months: 1-12 (Jan-Dec)
785
+ * * Day of Week: 1-7 (Sun-Sat)
786
+ *
787
+ * @returns {MethodDecorator}
788
+ * @throws {Error} When cron expression is invalid or decorator is used on wrong class type
789
+ */
790
+ function Scheduled(cron, timezone = 'Asia/Beijing') {
791
+ // 参数验证
792
+ if (koatty_lib.Helper.isEmpty(cron)) {
793
+ throw Error("Cron expression is required and cannot be empty");
794
+ }
795
+ // 验证cron表达式格式
796
+ try {
797
+ validateCronExpression(cron);
798
+ }
799
+ catch (error) {
800
+ throw Error(`Invalid cron expression: ${error.message}`);
801
+ }
802
+ // 验证时区
803
+ if (timezone && typeof timezone !== 'string') {
804
+ throw Error("Timezone must be a string");
805
+ }
806
+ return (target, propertyKey, descriptor) => {
807
+ // 验证装饰器使用的类型(从原型对象获取类构造函数)
808
+ const targetClass = target.constructor;
809
+ const componentType = koatty_container.IOCContainer.getType(targetClass);
810
+ if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
811
+ throw Error("@Scheduled decorator can only be used on SERVICE or COMPONENT classes.");
812
+ }
813
+ // 验证方法名
814
+ const methodName = propertyKey.toString();
815
+ if (!methodName || typeof methodName !== 'string') {
816
+ throw Error("Method name is required for @Scheduled decorator");
817
+ }
818
+ // 验证方法描述符
819
+ if (!descriptor || typeof descriptor.value !== 'function') {
820
+ throw Error("@Scheduled decorator can only be applied to methods");
821
+ }
822
+ // 保存类到IOC容器
823
+ koatty_container.IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
824
+ // 保存调度元数据到 IOC 容器
825
+ koatty_container.IOCContainer.attachClassMetadata(COMPONENT_SCHEDULED, DecoratorType.SCHEDULED, {
826
+ method: methodName,
827
+ cron,
828
+ timezone // 保存确定的时区值
829
+ }, target, methodName);
830
+ };
831
+ }
884
832
 
885
833
  /**
886
834
  * @ author: richen
@@ -888,6 +836,29 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
888
836
  * @ license: MIT
889
837
  * @ version: 2020-07-06 10:29:20
890
838
  */
839
+ /**
840
+ * 初始化调度任务系统
841
+ * 在appReady时触发批量注入调度任务,确保所有初始化工作完成
842
+ *
843
+ * @param {Koatty} app - Koatty 应用实例
844
+ * @param {any} options - 调度任务配置
845
+ */
846
+ async function initSchedule(options, app) {
847
+ if (!app || !koatty_lib.Helper.isFunction(app.once)) {
848
+ koatty_logger.DefaultLogger.Warn(`Schedule initialization skipped: Koatty app not available or not initialized`);
849
+ return;
850
+ }
851
+ app.once("appReady", async function () {
852
+ try {
853
+ await injectSchedule(options);
854
+ koatty_logger.DefaultLogger.Info('Schedule system initialized successfully');
855
+ }
856
+ catch (error) {
857
+ koatty_logger.DefaultLogger.Error('Failed to initialize Schedule system:', error);
858
+ throw error;
859
+ }
860
+ });
861
+ }
891
862
  /**
892
863
  * Inject schedule job with enhanced error handling and validation
893
864
  *
@@ -898,11 +869,8 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
898
869
  */
899
870
  /**
900
871
  * 批量注入调度任务 - 从IOC容器读取类元数据并创建所有CronJob
901
- *
902
- * @param {RedLockOptions} options - RedLock 配置选项
903
- * @param {Koatty} app - Koatty 应用实例
904
872
  */
905
- async function injectSchedule(_options, _app) {
873
+ async function injectSchedule(options) {
906
874
  try {
907
875
  koatty_logger.DefaultLogger.Debug('Starting batch schedule injection...');
908
876
  const componentList = koatty_container.IOCContainer.listClass("COMPONENT");
@@ -928,7 +896,7 @@ async function injectSchedule(_options, _app) {
928
896
  continue;
929
897
  }
930
898
  const taskName = `${className}_${scheduleData.method}`;
931
- const tz = getEffectiveTimezone(scheduleData.timezone);
899
+ const tz = getEffectiveTimezone(options, scheduleData.timezone);
932
900
  new cron.CronJob(scheduleData.cron, () => {
933
901
  koatty_logger.DefaultLogger.Debug(`The schedule job ${taskName} started.`);
934
902
  Promise.resolve(targetMethod.call(instance))
@@ -992,11 +960,10 @@ const defaultOptions = {
992
960
  */
993
961
  async function KoattyScheduled(options, app) {
994
962
  options = { ...defaultOptions, ...options };
995
- // 保存全局配置
996
- setGlobalScheduledOptions(options);
963
+ // 初始化RedLock(appReady时触发,确保所有依赖就绪)
997
964
  await initRedLock(options, app);
998
- await injectRedLock();
999
- await injectSchedule();
965
+ // 初始化调度任务系统(appReady时触发,确保所有组件都已初始化)
966
+ await initSchedule(options, app);
1000
967
  }
1001
968
 
1002
969
  exports.KoattyScheduled = KoattyScheduled;
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-22 23:41:54
3
+ * @Date: 2025-06-23 00:55:12
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
7
7
  */
8
8
  import { IOCContainer } from 'koatty_container';
9
- import { Helper } from 'koatty_lib';
10
9
  import { Redlock } from '@sesamecare-oss/redlock';
11
10
  import { Redis } from 'ioredis';
12
11
  import { DefaultLogger } from 'koatty_logger';
12
+ import { Helper } from 'koatty_lib';
13
13
  import { CronJob } from 'cron';
14
14
 
15
15
  /*
@@ -22,7 +22,6 @@ import { CronJob } from 'cron';
22
22
  * @Copyright (c): <richenlin(at)gmail.com>
23
23
  */
24
24
  const COMPONENT_SCHEDULED = 'COMPONENT_SCHEDULED';
25
- const COMPONENT_REDLOCK = 'COMPONENT_REDLOCK';
26
25
  /**
27
26
  * Decorator types supported by the system
28
27
  */
@@ -108,13 +107,6 @@ function validateRedLockMethodOptions(options) {
108
107
  * Global configuration storage
109
108
  */
110
109
  let globalScheduledOptions = {};
111
- /**
112
- * Set global scheduled options
113
- * @param options - Global scheduled options
114
- */
115
- function setGlobalScheduledOptions(options) {
116
- globalScheduledOptions = { ...options };
117
- }
118
110
  /**
119
111
  * Get global scheduled options
120
112
  * @returns Global scheduled options
@@ -127,8 +119,8 @@ function getGlobalScheduledOptions() {
127
119
  * @param userTimezone - User specified timezone
128
120
  * @returns Effective timezone
129
121
  */
130
- function getEffectiveTimezone(userTimezone) {
131
- return userTimezone || globalScheduledOptions.timezone || 'Asia/Beijing';
122
+ function getEffectiveTimezone(options, userTimezone) {
123
+ return userTimezone || options.timezone || 'Asia/Beijing';
132
124
  }
133
125
  /**
134
126
  * Get effective RedLock method options with priority: method options > global options > defaults
@@ -145,150 +137,6 @@ function getEffectiveRedLockOptions(methodOptions) {
145
137
  };
146
138
  }
147
139
 
148
- /*
149
- * @Description:
150
- * @Usage:
151
- * @Author: richen
152
- * @Date: 2025-06-09 16:00:00
153
- * @LastEditTime: 2025-06-09 16:00:00
154
- * @License: BSD (3-Clause)
155
- * @Copyright (c): <richenlin(at)gmail.com>
156
- */
157
- /**
158
- * Redis-based distributed lock decorator
159
- *
160
- * @export
161
- * @param {string} [name] - The locker name. If name is duplicated, lock sharing contention will result.
162
- * If not provided, a unique name will be auto-generated using method name + random suffix.
163
- * IMPORTANT: Auto-generated names are unique per method deployment and not predictable.
164
- * @param {RedLockMethodOptions} [options] - Lock configuration options for this method
165
- *
166
- * @returns {MethodDecorator}
167
- * @throws {Error} When decorator is used on wrong class type or invalid configuration
168
- *
169
- * @example
170
- * ```typescript
171
- * class UserService {
172
- * @RedLock('user_update_lock', { lockTimeOut: 5000, maxRetries: 2 })
173
- * async updateUser(id: string, data: any) {
174
- * // This method will be protected by a distributed lock with predictable name
175
- * }
176
- *
177
- * @RedLock() // Auto-generated unique name like "deleteUser_abc123_xyz789"
178
- * async deleteUser(id: string) {
179
- * // This method will be protected by a distributed lock with auto-generated unique name
180
- * }
181
- * }
182
- * ```
183
- */
184
- function RedLock(lockName, options) {
185
- return (target, propertyKey, descriptor) => {
186
- const methodName = propertyKey.toString();
187
- // 验证装饰器使用的类型(从原型对象获取类构造函数)
188
- const targetClass = target.constructor;
189
- const componentType = IOCContainer.getType(targetClass);
190
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
191
- throw Error("@RedLock decorator can only be used on SERVICE or COMPONENT classes.");
192
- }
193
- // 验证方法名
194
- if (!methodName || typeof methodName !== 'string') {
195
- throw Error("Method name is required for @RedLock decorator");
196
- }
197
- // 验证方法描述符
198
- if (!descriptor || typeof descriptor.value !== 'function') {
199
- throw Error("@RedLock decorator can only be applied to methods");
200
- }
201
- // 生成唯一的锁名称:用户指定的 > 自动生成的唯一名称
202
- if (!lockName || lockName.trim() === '') {
203
- const randomSuffix = Math.random().toString(36).substring(2, 8); // 6位随机字符
204
- const timestamp = Date.now().toString(36); // 时间戳转36进制
205
- lockName = `${methodName}_${randomSuffix}_${timestamp}`;
206
- }
207
- // 验证选项
208
- if (options) {
209
- validateRedLockMethodOptions(options);
210
- }
211
- // 保存类到IOC容器
212
- IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
213
- // 保存RedLock元数据到 IOC 容器(lockName已确定)
214
- IOCContainer.attachClassMetadata(COMPONENT_REDLOCK, DecoratorType.REDLOCK, {
215
- method: methodName,
216
- name: lockName, // 确定的锁名称,不会为undefined
217
- options
218
- }, target, methodName);
219
- };
220
- }
221
-
222
- /*
223
- * @Description:
224
- * @Usage:
225
- * @Author: richen
226
- * @Date: 2025-06-09 16:00:00
227
- * @LastEditTime: 2025-06-09 16:00:00
228
- * @License: BSD (3-Clause)
229
- * @Copyright (c): <richenlin(at)gmail.com>
230
- */
231
- /**
232
- * Schedule task decorator with optimized preprocessing
233
- *
234
- * @export
235
- * @param {string} cron - Cron expression for task scheduling
236
- * @param {string} [timezone='Asia/Beijing'] - Timezone for the schedule
237
- *
238
- * Cron expression format:
239
- * * Seconds: 0-59
240
- * * Minutes: 0-59
241
- * * Hours: 0-23
242
- * * Day of Month: 1-31
243
- * * Months: 1-12 (Jan-Dec)
244
- * * Day of Week: 1-7 (Sun-Sat)
245
- *
246
- * @returns {MethodDecorator}
247
- * @throws {Error} When cron expression is invalid or decorator is used on wrong class type
248
- */
249
- function Scheduled(cron, timezone) {
250
- // 参数验证
251
- if (Helper.isEmpty(cron)) {
252
- throw Error("Cron expression is required and cannot be empty");
253
- }
254
- // 验证cron表达式格式
255
- try {
256
- validateCronExpression(cron);
257
- }
258
- catch (error) {
259
- throw Error(`Invalid cron expression: ${error.message}`);
260
- }
261
- // 验证时区
262
- if (timezone && typeof timezone !== 'string') {
263
- throw Error("Timezone must be a string");
264
- }
265
- return (target, propertyKey, descriptor) => {
266
- // 验证装饰器使用的类型(从原型对象获取类构造函数)
267
- const targetClass = target.constructor;
268
- const componentType = IOCContainer.getType(targetClass);
269
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
270
- throw Error("@Scheduled decorator can only be used on SERVICE or COMPONENT classes.");
271
- }
272
- // 验证方法名
273
- const methodName = propertyKey.toString();
274
- if (!methodName || typeof methodName !== 'string') {
275
- throw Error("Method name is required for @Scheduled decorator");
276
- }
277
- // 验证方法描述符
278
- if (!descriptor || typeof descriptor.value !== 'function') {
279
- throw Error("@Scheduled decorator can only be applied to methods");
280
- }
281
- // 保存类到IOC容器
282
- IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
283
- // 保存调度元数据到 IOC 容器
284
- IOCContainer.attachClassMetadata(COMPONENT_SCHEDULED, DecoratorType.SCHEDULED, {
285
- method: methodName,
286
- cron,
287
- timezone // 保存用户指定的值,可能为undefined
288
- }, target, methodName);
289
- };
290
- }
291
-
292
140
  /*
293
141
  * @Description: RedLock utility for distributed locks
294
142
  * @Usage:
@@ -686,6 +534,8 @@ function timeoutPromise(ms) {
686
534
  /**
687
535
  * Initiation schedule locker client.
688
536
  *
537
+ * @param {RedLockOptions} options - RedLock 配置选项
538
+ * @param {Koatty} app - Koatty 应用实例
689
539
  * @returns {Promise<void>}
690
540
  */
691
541
  async function initRedLock(options, app) {
@@ -693,11 +543,12 @@ async function initRedLock(options, app) {
693
543
  DefaultLogger.Warn(`RedLock initialization skipped: Koatty app not available or not initialized`);
694
544
  return;
695
545
  }
696
- app.once("appStart", async function () {
546
+ app.once("appReady", async function () {
697
547
  try {
698
548
  if (Helper.isEmpty(options)) {
699
549
  throw Error(`Missing RedLock configuration. Please write a configuration item with the key name 'RedLock' in the db.ts file.`);
700
550
  }
551
+ // 获取RedLocker实例,在首次使用时自动初始化
701
552
  const redLocker = RedLocker.getInstance(options);
702
553
  await redLocker.initialize();
703
554
  DefaultLogger.Info('RedLock initialized successfully');
@@ -708,72 +559,12 @@ async function initRedLock(options, app) {
708
559
  }
709
560
  });
710
561
  }
711
- /**
712
- * 批量注入RedLock锁 - 从IOC容器读取类元数据并应用所有RedLock装饰器
713
- *
714
- * @param {RedLockOptions} options - RedLock 配置选项
715
- * @param {Koatty} app - Koatty 应用实例
716
- */
717
- async function injectRedLock(_options, _app) {
718
- try {
719
- DefaultLogger.Debug('Starting batch RedLock injection...');
720
- const componentList = IOCContainer.listClass("COMPONENT");
721
- for (const component of componentList) {
722
- const classMetadata = IOCContainer.getClassMetadata(COMPONENT_REDLOCK, DecoratorType.REDLOCK, component.target);
723
- if (!classMetadata) {
724
- continue;
725
- }
726
- let redlockCount = 0;
727
- for (const [className, metadata] of classMetadata) {
728
- try {
729
- const instance = IOCContainer.get(className);
730
- if (!instance) {
731
- continue;
732
- }
733
- // 查找所有RedLock方法的元数据
734
- for (const [key, value] of Object.entries(metadata)) {
735
- if (key.startsWith('REDLOCK')) {
736
- const redlockData = value;
737
- const targetMethod = instance[redlockData.method];
738
- if (!Helper.isFunction(targetMethod)) {
739
- DefaultLogger.Warn(`RedLock injection skipped: method ${redlockData.method} is not a function in ${className}`);
740
- continue;
741
- }
742
- // 生成有效的RedLock选项:方法级别 > 全局配置 > 默认值
743
- const effectiveOptions = getEffectiveRedLockOptions(redlockData.options);
744
- // 应用RedLock增强描述符
745
- const originalDescriptor = {
746
- value: targetMethod,
747
- writable: true,
748
- enumerable: false,
749
- configurable: true
750
- };
751
- const enhancedDescriptor = redLockerDescriptor(originalDescriptor, redlockData.name, // 使用装饰器中确定的锁名称
752
- redlockData.method, effectiveOptions);
753
- // 替换原方法
754
- Object.defineProperty(instance, redlockData.method, enhancedDescriptor);
755
- redlockCount++;
756
- DefaultLogger.Debug(`RedLock applied to ${className}.${redlockData.method} with lock name: ${redlockData.name}`);
757
- }
758
- }
759
- }
760
- catch (error) {
761
- DefaultLogger.Error(`Failed to process class ${className}:`, error);
762
- }
763
- }
764
- DefaultLogger.Info(`Batch RedLock injection completed. ${redlockCount} locks applied.`);
765
- }
766
- }
767
- catch (error) {
768
- DefaultLogger.Error('Failed to inject RedLocks:', error);
769
- }
770
- }
771
562
  /**
772
563
  * Create redLocker Descriptor with improved error handling and type safety
773
564
  * @param descriptor - Property descriptor
774
565
  * @param name - Lock name
775
566
  * @param method - Method name
776
- * @param options - RedLock options
567
+ * @param methodOptions - Method-level RedLock options
777
568
  * @returns Enhanced property descriptor
778
569
  */
779
570
  function redLockerDescriptor(descriptor, name, method, methodOptions) {
@@ -792,13 +583,6 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
792
583
  if (typeof value !== 'function') {
793
584
  throw new Error('Descriptor value must be a function');
794
585
  }
795
- // 设置默认选项,合并方法级别的选项
796
- const lockOptions = {
797
- lockTimeOut: methodOptions?.lockTimeOut,
798
- clockDriftFactor: methodOptions?.clockDriftFactor,
799
- maxRetries: methodOptions?.maxRetries,
800
- retryDelayMs: methodOptions?.retryDelayMs
801
- };
802
586
  /**
803
587
  * Enhanced function wrapper with proper lock renewal and safety
804
588
  */
@@ -862,6 +646,7 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
862
646
  async value(...props) {
863
647
  try {
864
648
  const redlock = RedLocker.getInstance();
649
+ const lockOptions = getEffectiveRedLockOptions(methodOptions);
865
650
  // Acquire a lock.
866
651
  const lockTime = lockOptions.lockTimeOut || 10000;
867
652
  if (lockTime <= 200) {
@@ -879,6 +664,169 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
879
664
  },
880
665
  };
881
666
  }
667
+ /**
668
+ * Generate lock name for RedLock decorator
669
+ */
670
+ function generateLockName(configName, methodName, target) {
671
+ if (configName) {
672
+ return configName;
673
+ }
674
+ try {
675
+ const targetObj = target;
676
+ const identifier = IOCContainer.getIdentifier(targetObj);
677
+ if (identifier) {
678
+ return `${identifier}_${methodName}`;
679
+ }
680
+ }
681
+ catch {
682
+ // Fallback if IOC container is not available
683
+ }
684
+ const targetWithConstructor = target;
685
+ const className = targetWithConstructor.constructor?.name || 'Unknown';
686
+ return `${className}_${methodName}`;
687
+ }
688
+
689
+ /*
690
+ * @Description:
691
+ * @Usage:
692
+ * @Author: richen
693
+ * @Date: 2025-06-09 16:00:00
694
+ * @LastEditTime: 2025-06-09 16:00:00
695
+ * @License: BSD (3-Clause)
696
+ * @Copyright (c): <richenlin(at)gmail.com>
697
+ */
698
+ /**
699
+ * Redis-based distributed lock decorator
700
+ *
701
+ * @export
702
+ * @param {string} [name] - The locker name. If name is duplicated, lock sharing contention will result.
703
+ * If not provided, a unique name will be auto-generated using method name + random suffix.
704
+ * IMPORTANT: Auto-generated names are unique per method deployment and not predictable.
705
+ * @param {RedLockMethodOptions} [options] - Lock configuration options for this method
706
+ *
707
+ * @returns {MethodDecorator}
708
+ * @throws {Error} When decorator is used on wrong class type or invalid configuration
709
+ *
710
+ * @example
711
+ * ```typescript
712
+ * class UserService {
713
+ * @RedLock('user_update_lock', { lockTimeOut: 5000, maxRetries: 2 })
714
+ * async updateUser(id: string, data: any) {
715
+ * // This method will be protected by a distributed lock with predictable name
716
+ * }
717
+ *
718
+ * @RedLock() // Auto-generated unique name like "deleteUser_abc123_xyz789"
719
+ * async deleteUser(id: string) {
720
+ * // This method will be protected by a distributed lock with auto-generated unique name
721
+ * }
722
+ * }
723
+ * ```
724
+ */
725
+ function RedLock(lockName, options) {
726
+ return (target, propertyKey, descriptor) => {
727
+ const methodName = propertyKey.toString();
728
+ // 验证装饰器使用的类型(从原型对象获取类构造函数)
729
+ const targetClass = target.constructor;
730
+ const componentType = IOCContainer.getType(targetClass);
731
+ if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
732
+ throw Error("@RedLock decorator can only be used on SERVICE or COMPONENT classes.");
733
+ }
734
+ // 验证方法名
735
+ if (!methodName || typeof methodName !== 'string') {
736
+ throw Error("Method name is required for @RedLock decorator");
737
+ }
738
+ // 验证方法描述符
739
+ if (!descriptor || typeof descriptor.value !== 'function') {
740
+ throw Error("@RedLock decorator can only be applied to methods");
741
+ }
742
+ // 生成锁名称:用户指定的 > 基于类名和方法名生成
743
+ const finalLockName = lockName || generateLockName(lockName, methodName, target);
744
+ // 验证选项
745
+ if (options) {
746
+ validateRedLockMethodOptions(options);
747
+ }
748
+ // 保存类到IOC容器
749
+ IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
750
+ try {
751
+ // 直接在装饰器中包装方法,而不是延迟处理
752
+ const enhancedDescriptor = redLockerDescriptor(descriptor, finalLockName, methodName, options);
753
+ return enhancedDescriptor;
754
+ }
755
+ catch (error) {
756
+ throw new Error(`Failed to apply RedLock to ${methodName}: ${error.message}`);
757
+ }
758
+ };
759
+ }
760
+
761
+ /*
762
+ * @Description:
763
+ * @Usage:
764
+ * @Author: richen
765
+ * @Date: 2025-06-09 16:00:00
766
+ * @LastEditTime: 2025-06-09 16:00:00
767
+ * @License: BSD (3-Clause)
768
+ * @Copyright (c): <richenlin(at)gmail.com>
769
+ */
770
+ /**
771
+ * Schedule task decorator with optimized preprocessing
772
+ *
773
+ * @export
774
+ * @param {string} cron - Cron expression for task scheduling
775
+ * @param {string} [timezone='Asia/Beijing'] - Timezone for the schedule
776
+ *
777
+ * Cron expression format:
778
+ * * Seconds: 0-59
779
+ * * Minutes: 0-59
780
+ * * Hours: 0-23
781
+ * * Day of Month: 1-31
782
+ * * Months: 1-12 (Jan-Dec)
783
+ * * Day of Week: 1-7 (Sun-Sat)
784
+ *
785
+ * @returns {MethodDecorator}
786
+ * @throws {Error} When cron expression is invalid or decorator is used on wrong class type
787
+ */
788
+ function Scheduled(cron, timezone = 'Asia/Beijing') {
789
+ // 参数验证
790
+ if (Helper.isEmpty(cron)) {
791
+ throw Error("Cron expression is required and cannot be empty");
792
+ }
793
+ // 验证cron表达式格式
794
+ try {
795
+ validateCronExpression(cron);
796
+ }
797
+ catch (error) {
798
+ throw Error(`Invalid cron expression: ${error.message}`);
799
+ }
800
+ // 验证时区
801
+ if (timezone && typeof timezone !== 'string') {
802
+ throw Error("Timezone must be a string");
803
+ }
804
+ return (target, propertyKey, descriptor) => {
805
+ // 验证装饰器使用的类型(从原型对象获取类构造函数)
806
+ const targetClass = target.constructor;
807
+ const componentType = IOCContainer.getType(targetClass);
808
+ if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
809
+ throw Error("@Scheduled decorator can only be used on SERVICE or COMPONENT classes.");
810
+ }
811
+ // 验证方法名
812
+ const methodName = propertyKey.toString();
813
+ if (!methodName || typeof methodName !== 'string') {
814
+ throw Error("Method name is required for @Scheduled decorator");
815
+ }
816
+ // 验证方法描述符
817
+ if (!descriptor || typeof descriptor.value !== 'function') {
818
+ throw Error("@Scheduled decorator can only be applied to methods");
819
+ }
820
+ // 保存类到IOC容器
821
+ IOCContainer.saveClass("COMPONENT", targetClass, targetClass.name);
822
+ // 保存调度元数据到 IOC 容器
823
+ IOCContainer.attachClassMetadata(COMPONENT_SCHEDULED, DecoratorType.SCHEDULED, {
824
+ method: methodName,
825
+ cron,
826
+ timezone // 保存确定的时区值
827
+ }, target, methodName);
828
+ };
829
+ }
882
830
 
883
831
  /**
884
832
  * @ author: richen
@@ -886,6 +834,29 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
886
834
  * @ license: MIT
887
835
  * @ version: 2020-07-06 10:29:20
888
836
  */
837
+ /**
838
+ * 初始化调度任务系统
839
+ * 在appReady时触发批量注入调度任务,确保所有初始化工作完成
840
+ *
841
+ * @param {Koatty} app - Koatty 应用实例
842
+ * @param {any} options - 调度任务配置
843
+ */
844
+ async function initSchedule(options, app) {
845
+ if (!app || !Helper.isFunction(app.once)) {
846
+ DefaultLogger.Warn(`Schedule initialization skipped: Koatty app not available or not initialized`);
847
+ return;
848
+ }
849
+ app.once("appReady", async function () {
850
+ try {
851
+ await injectSchedule(options);
852
+ DefaultLogger.Info('Schedule system initialized successfully');
853
+ }
854
+ catch (error) {
855
+ DefaultLogger.Error('Failed to initialize Schedule system:', error);
856
+ throw error;
857
+ }
858
+ });
859
+ }
889
860
  /**
890
861
  * Inject schedule job with enhanced error handling and validation
891
862
  *
@@ -896,11 +867,8 @@ function redLockerDescriptor(descriptor, name, method, methodOptions) {
896
867
  */
897
868
  /**
898
869
  * 批量注入调度任务 - 从IOC容器读取类元数据并创建所有CronJob
899
- *
900
- * @param {RedLockOptions} options - RedLock 配置选项
901
- * @param {Koatty} app - Koatty 应用实例
902
870
  */
903
- async function injectSchedule(_options, _app) {
871
+ async function injectSchedule(options) {
904
872
  try {
905
873
  DefaultLogger.Debug('Starting batch schedule injection...');
906
874
  const componentList = IOCContainer.listClass("COMPONENT");
@@ -926,7 +894,7 @@ async function injectSchedule(_options, _app) {
926
894
  continue;
927
895
  }
928
896
  const taskName = `${className}_${scheduleData.method}`;
929
- const tz = getEffectiveTimezone(scheduleData.timezone);
897
+ const tz = getEffectiveTimezone(options, scheduleData.timezone);
930
898
  new CronJob(scheduleData.cron, () => {
931
899
  DefaultLogger.Debug(`The schedule job ${taskName} started.`);
932
900
  Promise.resolve(targetMethod.call(instance))
@@ -990,11 +958,10 @@ const defaultOptions = {
990
958
  */
991
959
  async function KoattyScheduled(options, app) {
992
960
  options = { ...defaultOptions, ...options };
993
- // 保存全局配置
994
- setGlobalScheduledOptions(options);
961
+ // 初始化RedLock(appReady时触发,确保所有依赖就绪)
995
962
  await initRedLock(options, app);
996
- await injectRedLock();
997
- await injectSchedule();
963
+ // 初始化调度任务系统(appReady时触发,确保所有组件都已初始化)
964
+ await initSchedule(options, app);
998
965
  }
999
966
 
1000
967
  export { KoattyScheduled, RedLock, Scheduled, SchedulerLock };
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koatty_schedule",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Schedule for koatty.",
5
5
  "scripts": {
6
6
  "build": "npm run build:js && npm run build:dts && npm run build:doc && npm run build:cp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koatty_schedule",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Schedule for koatty.",
5
5
  "scripts": {
6
6
  "build": "npm run build:js && npm run build:dts && npm run build:doc && npm run build:cp",