@tachybase/module-cron 1.6.3 → 1.6.5
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/externalVersion.js +4 -4
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/server/plugin.js +2 -1
- package/dist/server/service/CronJobLock.d.ts +43 -0
- package/dist/server/service/CronJobLock.js +193 -0
- package/dist/server/service/StaticScheduleTrigger.d.ts +5 -0
- package/dist/server/service/StaticScheduleTrigger.js +39 -17
- package/package.json +7 -7
package/dist/externalVersion.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
"@tachybase/client": "1.6.
|
|
3
|
-
"@tego/server": "1.6.
|
|
2
|
+
"@tachybase/client": "1.6.5",
|
|
3
|
+
"@tego/server": "1.6.2",
|
|
4
4
|
"react": "18.3.1",
|
|
5
5
|
"antd": "5.22.5",
|
|
6
6
|
"dayjs": "1.11.13",
|
|
7
|
-
"@tachybase/schema": "1.6.
|
|
8
|
-
"@tachybase/module-workflow": "1.6.
|
|
7
|
+
"@tachybase/schema": "1.6.2",
|
|
8
|
+
"@tachybase/module-workflow": "1.6.5"
|
|
9
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cron-parser","version":"4.9.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"types/index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^3.2.1"},"devDependencies":{"eslint":"^8.27.0","sinon":"^15.0.1","tap":"^16.3.3","tsd":"^0.26.0"},"engines":{"node":">=12.0.0"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"files":["lib","types","LICENSE","README.md"],"_lastModified":"
|
|
1
|
+
{"name":"cron-parser","version":"4.9.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"types/index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^3.2.1"},"devDependencies":{"eslint":"^8.27.0","sinon":"^15.0.1","tap":"^16.3.3","tsd":"^0.26.0"},"engines":{"node":">=12.0.0"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"files":["lib","types","LICENSE","README.md"],"_lastModified":"2026-01-08T11:39:11.931Z"}
|
package/dist/server/plugin.js
CHANGED
|
@@ -69,11 +69,12 @@ module.exports = __toCommonJS(plugin_exports);
|
|
|
69
69
|
var import_server = require("@tego/server");
|
|
70
70
|
var import_cron_jobs_controller = require("./actions/cron-jobs-controller");
|
|
71
71
|
var import_CronJobModel = require("./model/CronJobModel");
|
|
72
|
+
var import_CronJobLock = require("./service/CronJobLock");
|
|
72
73
|
var import_StaticScheduleTrigger = require("./service/StaticScheduleTrigger");
|
|
73
74
|
var _PluginCronJobServer_decorators, _init, _a;
|
|
74
75
|
_PluginCronJobServer_decorators = [(0, import_server.InjectedPlugin)({
|
|
75
76
|
Controllers: [import_cron_jobs_controller.CronJobsController],
|
|
76
|
-
Services: [import_StaticScheduleTrigger.StaticScheduleTrigger]
|
|
77
|
+
Services: [import_CronJobLock.CronJobLock, import_StaticScheduleTrigger.StaticScheduleTrigger]
|
|
77
78
|
})];
|
|
78
79
|
class PluginCronJobServer extends (_a = import_server.Plugin) {
|
|
79
80
|
async afterAdd() {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基于缓存的分布式锁实现,用于防止定时任务在分布式环境下重复执行
|
|
3
|
+
*/
|
|
4
|
+
export declare class CronJobLock {
|
|
5
|
+
private app;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private cache;
|
|
8
|
+
private readonly LOCK_PREFIX;
|
|
9
|
+
private readonly DEFAULT_LOCK_TTL;
|
|
10
|
+
private readonly nodeId;
|
|
11
|
+
constructor();
|
|
12
|
+
load(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* 生成锁的 key
|
|
15
|
+
* @param cronJobId 任务 ID
|
|
16
|
+
* @param scheduledTime 计划执行时间戳
|
|
17
|
+
*/
|
|
18
|
+
private getLockKey;
|
|
19
|
+
/**
|
|
20
|
+
* 尝试获取锁(原子操作)
|
|
21
|
+
* @param cronJobId 任务 ID
|
|
22
|
+
* @param scheduledTime 计划执行时间戳
|
|
23
|
+
* @param ttl 锁的过期时间(毫秒),默认 5 分钟
|
|
24
|
+
* @returns 是否成功获取锁
|
|
25
|
+
*/
|
|
26
|
+
acquire(cronJobId: number, scheduledTime: number, ttl?: number): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* 释放锁
|
|
29
|
+
* @param cronJobId 任务 ID
|
|
30
|
+
* @param scheduledTime 计划执行时间戳
|
|
31
|
+
*/
|
|
32
|
+
release(cronJobId: number, scheduledTime: number): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* 检查锁是否被占用
|
|
35
|
+
* @param cronJobId 任务 ID
|
|
36
|
+
* @param scheduledTime 计划执行时间戳
|
|
37
|
+
*/
|
|
38
|
+
isLocked(cronJobId: number, scheduledTime: number): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* 获取当前节点的唯一标识
|
|
41
|
+
*/
|
|
42
|
+
private getNodeId;
|
|
43
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
var __decoratorStart = (base) => [, , , __create((base == null ? void 0 : base[__knownSymbol("metadata")]) ?? null)];
|
|
26
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
27
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
28
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
29
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
30
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
31
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
35
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
36
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
37
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
38
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
39
|
+
return __privateGet(this, extra);
|
|
40
|
+
}, set [name](x) {
|
|
41
|
+
return __privateSet(this, extra, x);
|
|
42
|
+
} }, name));
|
|
43
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
44
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
45
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
46
|
+
if (k) {
|
|
47
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
48
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
49
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
50
|
+
}
|
|
51
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
52
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
53
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
54
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
55
|
+
}
|
|
56
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
57
|
+
};
|
|
58
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
59
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
60
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
61
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
62
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
63
|
+
var CronJobLock_exports = {};
|
|
64
|
+
__export(CronJobLock_exports, {
|
|
65
|
+
CronJobLock: () => CronJobLock
|
|
66
|
+
});
|
|
67
|
+
module.exports = __toCommonJS(CronJobLock_exports);
|
|
68
|
+
var import_server = require("@tego/server");
|
|
69
|
+
var _logger_dec, _app_dec, _CronJobLock_decorators, _init;
|
|
70
|
+
_CronJobLock_decorators = [(0, import_server.Service)()], _app_dec = [(0, import_server.App)()], _logger_dec = [(0, import_server.InjectLog)()];
|
|
71
|
+
class CronJobLock {
|
|
72
|
+
constructor() {
|
|
73
|
+
this.app = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
|
|
74
|
+
this.logger = __runInitializers(_init, 12, this), __runInitializers(_init, 15, this);
|
|
75
|
+
this.cache = void 0;
|
|
76
|
+
this.LOCK_PREFIX = "cron-job:lock:";
|
|
77
|
+
// 锁的默认 TTL 为 5 分钟,防止锁无法释放
|
|
78
|
+
this.DEFAULT_LOCK_TTL = 5 * 60 * 1e3;
|
|
79
|
+
// 当前节点的唯一标识,在初始化时生成一次
|
|
80
|
+
this.nodeId = void 0;
|
|
81
|
+
this.nodeId = `${process.pid}-${Math.random().toString(36).substring(2, 10)}`;
|
|
82
|
+
}
|
|
83
|
+
async load() {
|
|
84
|
+
var _a;
|
|
85
|
+
const cache = (_a = this.app) == null ? void 0 : _a.cache;
|
|
86
|
+
if (!cache) {
|
|
87
|
+
this.logger.error(
|
|
88
|
+
"CronJobLock cache is not initialized. Please ensure cache service is configured before using CronJobLock."
|
|
89
|
+
);
|
|
90
|
+
throw new Error("CronJobLock cache is not initialized");
|
|
91
|
+
}
|
|
92
|
+
this.cache = cache;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 生成锁的 key
|
|
96
|
+
* @param cronJobId 任务 ID
|
|
97
|
+
* @param scheduledTime 计划执行时间戳
|
|
98
|
+
*/
|
|
99
|
+
getLockKey(cronJobId, scheduledTime) {
|
|
100
|
+
return `${this.LOCK_PREFIX}${cronJobId}:${scheduledTime}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 尝试获取锁(原子操作)
|
|
104
|
+
* @param cronJobId 任务 ID
|
|
105
|
+
* @param scheduledTime 计划执行时间戳
|
|
106
|
+
* @param ttl 锁的过期时间(毫秒),默认 5 分钟
|
|
107
|
+
* @returns 是否成功获取锁
|
|
108
|
+
*/
|
|
109
|
+
async acquire(cronJobId, scheduledTime, ttl) {
|
|
110
|
+
const lockKey = this.getLockKey(cronJobId, scheduledTime);
|
|
111
|
+
const lockTTL = ttl ?? this.DEFAULT_LOCK_TTL;
|
|
112
|
+
try {
|
|
113
|
+
const lockValue = {
|
|
114
|
+
nodeId: this.getNodeId(),
|
|
115
|
+
acquiredAt: Date.now()
|
|
116
|
+
};
|
|
117
|
+
const cacheClient = this.cache;
|
|
118
|
+
let acquired = false;
|
|
119
|
+
if (typeof cacheClient.setIfNotExists === "function") {
|
|
120
|
+
acquired = await cacheClient.setIfNotExists(lockKey, lockValue, lockTTL);
|
|
121
|
+
} else if (typeof cacheClient.setNx === "function") {
|
|
122
|
+
acquired = await cacheClient.setNx(lockKey, lockValue, lockTTL);
|
|
123
|
+
} else {
|
|
124
|
+
this.logger.warn(
|
|
125
|
+
"CronJobLock cache implementation does not support atomic set-if-not-exists operations. Distributed locking for cron jobs is disabled. / \u5F53\u524D\u7F13\u5B58\u5B9E\u73B0\u4E0D\u652F\u6301\u539F\u5B50\u6027 set-if-not-exists \u64CD\u4F5C\uFF0C\u5B9A\u65F6\u4EFB\u52A1\u7684\u5206\u5E03\u5F0F\u9501\u5DF2\u88AB\u7981\u7528\u3002"
|
|
126
|
+
);
|
|
127
|
+
throw new Error(
|
|
128
|
+
"CronJobLock cache implementation does not support atomic set-if-not-exists operations"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!acquired) {
|
|
132
|
+
this.logger.debug(`Lock already exists for cron job ${cronJobId} at ${scheduledTime}`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
this.logger.debug(`Lock acquired for cron job ${cronJobId} at ${scheduledTime}`);
|
|
136
|
+
return true;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof Error) {
|
|
139
|
+
this.logger.error(`Failed to acquire lock for cron job ${cronJobId}: ${error.message}`);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
this.logger.error(
|
|
143
|
+
`Failed to acquire lock for cron job ${cronJobId} due to unknown error type`,
|
|
144
|
+
{ error }
|
|
145
|
+
);
|
|
146
|
+
throw new Error("Failed to acquire lock due to unknown error");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 释放锁
|
|
151
|
+
* @param cronJobId 任务 ID
|
|
152
|
+
* @param scheduledTime 计划执行时间戳
|
|
153
|
+
*/
|
|
154
|
+
async release(cronJobId, scheduledTime) {
|
|
155
|
+
const lockKey = this.getLockKey(cronJobId, scheduledTime);
|
|
156
|
+
try {
|
|
157
|
+
await this.cache.del(lockKey);
|
|
158
|
+
this.logger.debug(`Lock released for cron job ${cronJobId} at ${scheduledTime}`);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.logger.error(`Failed to release lock for cron job ${cronJobId}: ${error.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 检查锁是否被占用
|
|
165
|
+
* @param cronJobId 任务 ID
|
|
166
|
+
* @param scheduledTime 计划执行时间戳
|
|
167
|
+
*/
|
|
168
|
+
async isLocked(cronJobId, scheduledTime) {
|
|
169
|
+
const lockKey = this.getLockKey(cronJobId, scheduledTime);
|
|
170
|
+
try {
|
|
171
|
+
const lock = await this.cache.get(lockKey);
|
|
172
|
+
return !!lock;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this.logger.error(`Failed to check lock for cron job ${cronJobId}: ${error.message}`);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 获取当前节点的唯一标识
|
|
180
|
+
*/
|
|
181
|
+
getNodeId() {
|
|
182
|
+
return this.nodeId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
_init = __decoratorStart(null);
|
|
186
|
+
__decorateElement(_init, 5, "app", _app_dec, CronJobLock);
|
|
187
|
+
__decorateElement(_init, 5, "logger", _logger_dec, CronJobLock);
|
|
188
|
+
CronJobLock = __decorateElement(_init, 0, "CronJobLock", _CronJobLock_decorators, CronJobLock);
|
|
189
|
+
__runInitializers(_init, 1, CronJobLock);
|
|
190
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
191
|
+
0 && (module.exports = {
|
|
192
|
+
CronJobLock
|
|
193
|
+
});
|
|
@@ -4,12 +4,17 @@ export declare class StaticScheduleTrigger {
|
|
|
4
4
|
private app;
|
|
5
5
|
private readonly db;
|
|
6
6
|
private readonly logger;
|
|
7
|
+
private readonly cronJobLock;
|
|
7
8
|
private timers;
|
|
8
9
|
load(): Promise<void>;
|
|
9
10
|
inspect(cronJobs: CronJobModel[]): void;
|
|
10
11
|
getNextTime(cronJob: CronJobModel, currentDate: Date, nextSecond?: boolean): number;
|
|
11
12
|
schedule(cronJob: CronJobModel, nextTime: number, toggle?: boolean): void;
|
|
12
13
|
trigger(cronJobId: number, time: number): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* 如果需要,调度下一次执行
|
|
16
|
+
*/
|
|
17
|
+
private scheduleNextIfNeeded;
|
|
13
18
|
on(cronJob: CronJobModel): void;
|
|
14
19
|
off(cronJob: CronJobModel): void;
|
|
15
20
|
}
|
|
@@ -79,14 +79,16 @@ var import_server = require("@tego/server");
|
|
|
79
79
|
var import_cron_parser = __toESM(require("cron-parser"));
|
|
80
80
|
var import_constants = require("../../constants");
|
|
81
81
|
var import_utils = require("../utils");
|
|
82
|
-
var
|
|
82
|
+
var import_CronJobLock = require("./CronJobLock");
|
|
83
|
+
var _cronJobLock_dec, _logger_dec, _db_dec, _app_dec, _StaticScheduleTrigger_decorators, _init;
|
|
83
84
|
const MAX_SAFE_INTERVAL = 2147483647;
|
|
84
|
-
_StaticScheduleTrigger_decorators = [(0, import_server.Service)()], _app_dec = [(0, import_server.App)()], _db_dec = [(0, import_server.Db)()], _logger_dec = [(0, import_server.InjectLog)()];
|
|
85
|
+
_StaticScheduleTrigger_decorators = [(0, import_server.Service)()], _app_dec = [(0, import_server.App)()], _db_dec = [(0, import_server.Db)()], _logger_dec = [(0, import_server.InjectLog)()], _cronJobLock_dec = [(0, import_server.Inject)(() => import_CronJobLock.CronJobLock)];
|
|
85
86
|
let _StaticScheduleTrigger = class _StaticScheduleTrigger {
|
|
86
87
|
constructor() {
|
|
87
88
|
this.app = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
|
|
88
89
|
this.db = __runInitializers(_init, 12, this), __runInitializers(_init, 15, this);
|
|
89
90
|
this.logger = __runInitializers(_init, 16, this), __runInitializers(_init, 19, this);
|
|
91
|
+
this.cronJobLock = __runInitializers(_init, 20, this), __runInitializers(_init, 23, this);
|
|
90
92
|
this.timers = /* @__PURE__ */ new Map();
|
|
91
93
|
}
|
|
92
94
|
async load() {
|
|
@@ -230,21 +232,30 @@ let _StaticScheduleTrigger = class _StaticScheduleTrigger {
|
|
|
230
232
|
}
|
|
231
233
|
async trigger(cronJobId, time) {
|
|
232
234
|
var _a, _b;
|
|
235
|
+
const eventKey = `${cronJobId}@${time}`;
|
|
233
236
|
try {
|
|
237
|
+
const lockAcquired = await this.cronJobLock.acquire(cronJobId, time);
|
|
238
|
+
if (!lockAcquired) {
|
|
239
|
+
this.logger.info(
|
|
240
|
+
`cronJobs [${cronJobId}] skipped: lock not acquired (another node is executing this job at ${new Date(time).toISOString()})`
|
|
241
|
+
);
|
|
242
|
+
this.timers.delete(eventKey);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
234
245
|
const cronJob = await this.db.getRepository(import_constants.DATABASE_CRON_JOBS).findOne({ filterByTk: cronJobId });
|
|
235
246
|
if (!cronJob) {
|
|
236
247
|
this.logger.warn(`Scheduled cron job ${cronJobId} no longer exists`);
|
|
237
|
-
|
|
238
|
-
this.
|
|
248
|
+
this.timers.delete(eventKey);
|
|
249
|
+
await this.cronJobLock.release(cronJobId, time);
|
|
239
250
|
return;
|
|
240
251
|
}
|
|
241
|
-
const eventKey = `${cronJob.id}@${time}`;
|
|
242
252
|
this.timers.delete(eventKey);
|
|
243
253
|
const pluginWorkflow = this.app.pm.get(import_module_workflow.PluginWorkflow);
|
|
244
254
|
const workflow = await this.db.getRepository("workflows").findOne({
|
|
245
255
|
filter: { key: cronJob.workflowKey, enabled: true }
|
|
246
256
|
});
|
|
247
257
|
if (!workflow) {
|
|
258
|
+
await this.cronJobLock.release(cronJobId, time);
|
|
248
259
|
return;
|
|
249
260
|
}
|
|
250
261
|
let error = null;
|
|
@@ -257,25 +268,35 @@ let _StaticScheduleTrigger = class _StaticScheduleTrigger {
|
|
|
257
268
|
} finally {
|
|
258
269
|
if (!error && (((_a = process == null ? void 0 : process.execution) == null ? void 0 : _a.status) === import_module_workflow.EXECUTION_STATUS.QUEUEING || ((_b = process == null ? void 0 : process.execution) == null ? void 0 : _b.status) >= 0)) {
|
|
259
270
|
await cronJob.increment(["limitExecuted", "allExecuted", "successExecuted"]);
|
|
260
|
-
cronJob.update({
|
|
261
|
-
lastTime:
|
|
271
|
+
await cronJob.update({
|
|
272
|
+
lastTime: new Date(time)
|
|
262
273
|
});
|
|
274
|
+
this.scheduleNextIfNeeded(cronJob);
|
|
263
275
|
} else {
|
|
264
276
|
await cronJob.increment(["limitExecuted", "allExecuted"]);
|
|
265
|
-
cronJob.update({
|
|
266
|
-
lastTime:
|
|
277
|
+
await cronJob.update({
|
|
278
|
+
lastTime: new Date(time)
|
|
267
279
|
});
|
|
268
280
|
}
|
|
269
|
-
|
|
270
|
-
if (!cronJob.repeat || cronJob.limit && cronJob.limitExecuted >= cronJob.limit) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const nextTime = this.getNextTime(cronJob, /* @__PURE__ */ new Date(), true);
|
|
274
|
-
if (nextTime) {
|
|
275
|
-
this.schedule(cronJob, nextTime);
|
|
281
|
+
await this.cronJobLock.release(cronJobId, time);
|
|
276
282
|
}
|
|
277
283
|
} catch (e) {
|
|
278
|
-
this.logger.error(`cronJobs [${cronJobId}] failed: ${e.message}`);
|
|
284
|
+
this.logger.error(`cronJobs [${cronJobId}] failed: ${(e == null ? void 0 : e.message) ?? String(e)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 如果需要,调度下一次执行
|
|
289
|
+
*/
|
|
290
|
+
scheduleNextIfNeeded(cronJob) {
|
|
291
|
+
if (!cronJob) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (!cronJob.repeat || cronJob.limit && cronJob.limitExecuted >= cronJob.limit) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const nextTime = this.getNextTime(cronJob, /* @__PURE__ */ new Date(), true);
|
|
298
|
+
if (nextTime) {
|
|
299
|
+
this.schedule(cronJob, nextTime);
|
|
279
300
|
}
|
|
280
301
|
}
|
|
281
302
|
on(cronJob) {
|
|
@@ -289,6 +310,7 @@ _init = __decoratorStart(null);
|
|
|
289
310
|
__decorateElement(_init, 5, "app", _app_dec, _StaticScheduleTrigger);
|
|
290
311
|
__decorateElement(_init, 5, "db", _db_dec, _StaticScheduleTrigger);
|
|
291
312
|
__decorateElement(_init, 5, "logger", _logger_dec, _StaticScheduleTrigger);
|
|
313
|
+
__decorateElement(_init, 5, "cronJobLock", _cronJobLock_dec, _StaticScheduleTrigger);
|
|
292
314
|
_StaticScheduleTrigger = __decorateElement(_init, 0, "StaticScheduleTrigger", _StaticScheduleTrigger_decorators, _StaticScheduleTrigger);
|
|
293
315
|
_StaticScheduleTrigger.inspectFields = ["startsOn", "mode", "endsOn", "repeat", "limit", "enabled", "limitExecuted"];
|
|
294
316
|
__runInitializers(_init, 1, _StaticScheduleTrigger);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tachybase/module-cron",
|
|
3
3
|
"displayName": "Cron job",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.5",
|
|
5
5
|
"description": "Schedule tasks to run at specific times, using a workflow approach",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"System management"
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"main": "dist/server/index.js",
|
|
10
10
|
"dependencies": {},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@tachybase/schema": "1.6.
|
|
13
|
-
"@tachybase/test": "1.6.
|
|
14
|
-
"@tego/client": "1.6.
|
|
15
|
-
"@tego/server": "1.6.
|
|
12
|
+
"@tachybase/schema": "1.6.2",
|
|
13
|
+
"@tachybase/test": "1.6.2",
|
|
14
|
+
"@tego/client": "1.6.2",
|
|
15
|
+
"@tego/server": "1.6.2",
|
|
16
16
|
"antd": "5.22.5",
|
|
17
17
|
"cron-parser": "4.9.0",
|
|
18
18
|
"dayjs": "1.11.13",
|
|
19
19
|
"react-js-cron": "^3.2.0",
|
|
20
|
-
"@tachybase/client": "1.6.
|
|
21
|
-
"@tachybase/module-workflow": "1.6.
|
|
20
|
+
"@tachybase/client": "1.6.5",
|
|
21
|
+
"@tachybase/module-workflow": "1.6.5"
|
|
22
22
|
},
|
|
23
23
|
"description.zh-CN": "安排任务在特定时间运行,采用工作流的方式",
|
|
24
24
|
"displayName.zh-CN": "定时任务",
|