@ruiapp/rapid-core 0.8.10 → 0.8.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +339 -38
- package/dist/plugins/cronJob/CronJobPluginTypes.d.ts +26 -0
- package/dist/plugins/cronJob/entityWatchers/cronJobEntityWatchers.d.ts +7 -0
- package/dist/plugins/cronJob/entityWatchers/index.d.ts +6 -0
- package/dist/plugins/cronJob/models/CronJob.d.ts +3 -0
- package/dist/plugins/cronJob/models/index.d.ts +2 -0
- package/dist/plugins/cronJob/services/CronJobService.d.ts +4 -1
- package/dist/utilities/timeUtility.d.ts +1 -0
- package/package.json +1 -1
- package/src/plugins/cronJob/CronJobPlugin.ts +30 -5
- package/src/plugins/cronJob/CronJobPluginTypes.ts +29 -0
- package/src/plugins/cronJob/entityWatchers/cronJobEntityWatchers.ts +24 -0
- package/src/plugins/cronJob/entityWatchers/index.ts +4 -0
- package/src/plugins/cronJob/models/CronJob.ts +129 -0
- package/src/plugins/cronJob/models/index.ts +3 -0
- package/src/plugins/cronJob/services/CronJobService.ts +168 -24
- package/src/utilities/timeUtility.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -2498,6 +2498,9 @@ function getNowStringWithTimezone() {
|
|
|
2498
2498
|
}
|
|
2499
2499
|
function getDateString(timeString) {
|
|
2500
2500
|
return dayjs__default["default"](timeString).format("YYYY-MM-DD");
|
|
2501
|
+
}
|
|
2502
|
+
function formatDateTimeWithTimezone(source) {
|
|
2503
|
+
return dayjs__default["default"](source).format("YYYY-MM-DD HH:mm:ss.SSSZ");
|
|
2501
2504
|
}
|
|
2502
2505
|
|
|
2503
2506
|
// TODO Generate mapper and cache it.
|
|
@@ -6260,7 +6263,7 @@ var SequenceAutoIncrementRecord = {
|
|
|
6260
6263
|
],
|
|
6261
6264
|
};
|
|
6262
6265
|
|
|
6263
|
-
var pluginModels$
|
|
6266
|
+
var pluginModels$7 = [SequenceRule, SequenceAutoIncrementRecord];
|
|
6264
6267
|
|
|
6265
6268
|
var generateSn = {
|
|
6266
6269
|
namespace: "svc",
|
|
@@ -6499,7 +6502,7 @@ class SequencePlugin$1 {
|
|
|
6499
6502
|
}
|
|
6500
6503
|
}
|
|
6501
6504
|
async configureModels(server, applicationConfig) {
|
|
6502
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
6505
|
+
server.appendApplicationConfig({ models: pluginModels$7 });
|
|
6503
6506
|
}
|
|
6504
6507
|
async configureServices(server, applicationConfig) {
|
|
6505
6508
|
this.#sequenceService = new SequenceService(server);
|
|
@@ -6983,7 +6986,7 @@ var AccessToken = {
|
|
|
6983
6986
|
],
|
|
6984
6987
|
};
|
|
6985
6988
|
|
|
6986
|
-
var pluginModels$
|
|
6989
|
+
var pluginModels$6 = [AccessToken];
|
|
6987
6990
|
|
|
6988
6991
|
var changePassword = {
|
|
6989
6992
|
namespace: "auth",
|
|
@@ -7103,7 +7106,7 @@ class AuthPlugin {
|
|
|
7103
7106
|
}
|
|
7104
7107
|
}
|
|
7105
7108
|
async configureModels(server, applicationConfig) {
|
|
7106
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
7109
|
+
server.appendApplicationConfig({ models: pluginModels$6 });
|
|
7107
7110
|
}
|
|
7108
7111
|
async configureServices(server, applicationConfig) {
|
|
7109
7112
|
this.#authService = new AuthService(server, server.config.jwtKey);
|
|
@@ -7378,7 +7381,7 @@ var getLicense$1 = /*#__PURE__*/Object.freeze({
|
|
|
7378
7381
|
|
|
7379
7382
|
var pluginActionHandlers$6 = [getLicense$1];
|
|
7380
7383
|
|
|
7381
|
-
var pluginModels$
|
|
7384
|
+
var pluginModels$5 = [];
|
|
7382
7385
|
|
|
7383
7386
|
var getLicense = {
|
|
7384
7387
|
namespace: "svc",
|
|
@@ -7549,7 +7552,7 @@ class LicensePlugin {
|
|
|
7549
7552
|
}
|
|
7550
7553
|
}
|
|
7551
7554
|
async configureModels(server, applicationConfig) {
|
|
7552
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
7555
|
+
server.appendApplicationConfig({ models: pluginModels$5 });
|
|
7553
7556
|
}
|
|
7554
7557
|
async configureServices(server, applicationConfig) {
|
|
7555
7558
|
this.#licenseService = new LicenseService(server, this.#encryptionKey);
|
|
@@ -7565,7 +7568,7 @@ class LicensePlugin {
|
|
|
7565
7568
|
|
|
7566
7569
|
var pluginActionHandlers$5 = [];
|
|
7567
7570
|
|
|
7568
|
-
var pluginModels$
|
|
7571
|
+
var pluginModels$4 = [];
|
|
7569
7572
|
|
|
7570
7573
|
var pluginRoutes$4 = [];
|
|
7571
7574
|
|
|
@@ -7631,7 +7634,7 @@ class MailPlugin {
|
|
|
7631
7634
|
}
|
|
7632
7635
|
}
|
|
7633
7636
|
async configureModels(server, applicationConfig) {
|
|
7634
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
7637
|
+
server.appendApplicationConfig({ models: pluginModels$4 });
|
|
7635
7638
|
}
|
|
7636
7639
|
async configureServices(server, applicationConfig) {
|
|
7637
7640
|
this.#mailService = new MailService(server, this.#config.smtpServer);
|
|
@@ -7704,7 +7707,7 @@ var Notification = {
|
|
|
7704
7707
|
],
|
|
7705
7708
|
};
|
|
7706
7709
|
|
|
7707
|
-
var pluginModels$
|
|
7710
|
+
var pluginModels$3 = [Notification];
|
|
7708
7711
|
|
|
7709
7712
|
var pluginRoutes$3 = [];
|
|
7710
7713
|
|
|
@@ -7756,7 +7759,7 @@ class SequencePlugin {
|
|
|
7756
7759
|
}
|
|
7757
7760
|
}
|
|
7758
7761
|
async configureModels(server, applicationConfig) {
|
|
7759
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
7762
|
+
server.appendApplicationConfig({ models: pluginModels$3 });
|
|
7760
7763
|
}
|
|
7761
7764
|
async configureServices(server, applicationConfig) {
|
|
7762
7765
|
this.#notificationService = new NotificationService(server);
|
|
@@ -8274,7 +8277,7 @@ var UserSettingItemSetting = {
|
|
|
8274
8277
|
],
|
|
8275
8278
|
};
|
|
8276
8279
|
|
|
8277
|
-
var pluginModels$
|
|
8280
|
+
var pluginModels$2 = [SystemSettingGroupSetting, SystemSettingItem, SystemSettingItemSetting, UserSettingGroupSetting, UserSettingItem, UserSettingItemSetting];
|
|
8278
8281
|
|
|
8279
8282
|
var getUserSettingValues = {
|
|
8280
8283
|
namespace: "svc",
|
|
@@ -8529,7 +8532,7 @@ class SettingPlugin {
|
|
|
8529
8532
|
}
|
|
8530
8533
|
}
|
|
8531
8534
|
async configureModels(server, applicationConfig) {
|
|
8532
|
-
server.appendApplicationConfig({ models: pluginModels$
|
|
8535
|
+
server.appendApplicationConfig({ models: pluginModels$2 });
|
|
8533
8536
|
}
|
|
8534
8537
|
async configureServices(server, applicationConfig) {
|
|
8535
8538
|
this.#settingService = new SettingService(server);
|
|
@@ -8541,6 +8544,136 @@ class SettingPlugin {
|
|
|
8541
8544
|
async onApplicationLoaded(server, applicationConfig) { }
|
|
8542
8545
|
}
|
|
8543
8546
|
|
|
8547
|
+
var CronJob = {
|
|
8548
|
+
maintainedBy: "cronJob",
|
|
8549
|
+
namespace: "sys",
|
|
8550
|
+
name: "sys_cron_job",
|
|
8551
|
+
singularCode: "sys_cron_job",
|
|
8552
|
+
pluralCode: "sys_cron_jobs",
|
|
8553
|
+
schema: "public",
|
|
8554
|
+
tableName: "sys_cron_jobs",
|
|
8555
|
+
properties: [
|
|
8556
|
+
{
|
|
8557
|
+
name: "id",
|
|
8558
|
+
code: "id",
|
|
8559
|
+
columnName: "id",
|
|
8560
|
+
type: "integer",
|
|
8561
|
+
required: true,
|
|
8562
|
+
autoIncrement: true,
|
|
8563
|
+
},
|
|
8564
|
+
{
|
|
8565
|
+
name: "code",
|
|
8566
|
+
code: "code",
|
|
8567
|
+
columnName: "code",
|
|
8568
|
+
type: "text",
|
|
8569
|
+
required: true,
|
|
8570
|
+
},
|
|
8571
|
+
{
|
|
8572
|
+
name: "description",
|
|
8573
|
+
code: "description",
|
|
8574
|
+
columnName: "description",
|
|
8575
|
+
type: "text",
|
|
8576
|
+
required: false,
|
|
8577
|
+
},
|
|
8578
|
+
{
|
|
8579
|
+
name: "cronTime",
|
|
8580
|
+
code: "cronTime",
|
|
8581
|
+
columnName: "cron_time",
|
|
8582
|
+
type: "text",
|
|
8583
|
+
required: true,
|
|
8584
|
+
},
|
|
8585
|
+
{
|
|
8586
|
+
name: "disabled",
|
|
8587
|
+
code: "disabled",
|
|
8588
|
+
columnName: "disabled",
|
|
8589
|
+
type: "boolean",
|
|
8590
|
+
required: true,
|
|
8591
|
+
defaultValue: "false",
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
name: "jobOptions",
|
|
8595
|
+
code: "jobOptions",
|
|
8596
|
+
columnName: "job_options",
|
|
8597
|
+
type: "json",
|
|
8598
|
+
required: false,
|
|
8599
|
+
},
|
|
8600
|
+
{
|
|
8601
|
+
name: "isRunning",
|
|
8602
|
+
code: "isRunning",
|
|
8603
|
+
columnName: "is_running",
|
|
8604
|
+
type: "boolean",
|
|
8605
|
+
required: true,
|
|
8606
|
+
defaultValue: "false",
|
|
8607
|
+
},
|
|
8608
|
+
{
|
|
8609
|
+
name: "nextRunningTime",
|
|
8610
|
+
code: "nextRunningTime",
|
|
8611
|
+
columnName: "next_running_time",
|
|
8612
|
+
type: "datetime",
|
|
8613
|
+
required: false,
|
|
8614
|
+
},
|
|
8615
|
+
{
|
|
8616
|
+
name: "lastRunningTime",
|
|
8617
|
+
code: "lastRunningTime",
|
|
8618
|
+
columnName: "last_running_time",
|
|
8619
|
+
type: "datetime",
|
|
8620
|
+
required: false,
|
|
8621
|
+
},
|
|
8622
|
+
// success, failed, error
|
|
8623
|
+
{
|
|
8624
|
+
name: "lastRunningResult",
|
|
8625
|
+
code: "lastRunningResult",
|
|
8626
|
+
columnName: "last_running_result",
|
|
8627
|
+
type: "text",
|
|
8628
|
+
required: false,
|
|
8629
|
+
},
|
|
8630
|
+
{
|
|
8631
|
+
name: "lastErrorMessage",
|
|
8632
|
+
code: "lastErrorMessage",
|
|
8633
|
+
columnName: "last_error_message",
|
|
8634
|
+
type: "text",
|
|
8635
|
+
required: false,
|
|
8636
|
+
},
|
|
8637
|
+
{
|
|
8638
|
+
name: "lastErrorStack",
|
|
8639
|
+
code: "lastErrorStack",
|
|
8640
|
+
columnName: "last_error_stack",
|
|
8641
|
+
type: "text",
|
|
8642
|
+
required: false,
|
|
8643
|
+
},
|
|
8644
|
+
{
|
|
8645
|
+
name: "actionHandlerCode",
|
|
8646
|
+
code: "actionHandlerCode",
|
|
8647
|
+
columnName: "action_handler_code",
|
|
8648
|
+
type: "text",
|
|
8649
|
+
required: false,
|
|
8650
|
+
},
|
|
8651
|
+
{
|
|
8652
|
+
name: "handler",
|
|
8653
|
+
code: "handler",
|
|
8654
|
+
columnName: "handler",
|
|
8655
|
+
type: "text",
|
|
8656
|
+
required: false,
|
|
8657
|
+
},
|
|
8658
|
+
{
|
|
8659
|
+
name: "handleOptions",
|
|
8660
|
+
code: "handleOptions",
|
|
8661
|
+
columnName: "handle_options",
|
|
8662
|
+
type: "json",
|
|
8663
|
+
required: false,
|
|
8664
|
+
},
|
|
8665
|
+
{
|
|
8666
|
+
name: "onError",
|
|
8667
|
+
code: "onError",
|
|
8668
|
+
columnName: "on_error",
|
|
8669
|
+
type: "text",
|
|
8670
|
+
required: false,
|
|
8671
|
+
},
|
|
8672
|
+
],
|
|
8673
|
+
};
|
|
8674
|
+
|
|
8675
|
+
var pluginModels$1 = [CronJob];
|
|
8676
|
+
|
|
8544
8677
|
const code$1 = "runCronJob";
|
|
8545
8678
|
async function handler$1(plugin, ctx, options) {
|
|
8546
8679
|
const { server, routerContext } = ctx;
|
|
@@ -8593,9 +8726,30 @@ var runCronJob = {
|
|
|
8593
8726
|
|
|
8594
8727
|
var pluginRoutes$1 = [runCronJob];
|
|
8595
8728
|
|
|
8729
|
+
var cronJobEntityWatchers = [
|
|
8730
|
+
{
|
|
8731
|
+
eventName: "entity.update",
|
|
8732
|
+
modelSingularCode: "sys_cron_job",
|
|
8733
|
+
handler: async (ctx) => {
|
|
8734
|
+
const { server, payload, routerContext: routeContext } = ctx;
|
|
8735
|
+
const cronJobService = server.getService("cronJobService");
|
|
8736
|
+
const changes = payload.changes;
|
|
8737
|
+
const after = payload.after;
|
|
8738
|
+
await cronJobService.updateJobConfig(routeContext, {
|
|
8739
|
+
code: after.code,
|
|
8740
|
+
cronTime: changes.cronTime,
|
|
8741
|
+
disabled: changes.disabled,
|
|
8742
|
+
jobOptions: changes.jobOptions,
|
|
8743
|
+
});
|
|
8744
|
+
},
|
|
8745
|
+
},
|
|
8746
|
+
];
|
|
8747
|
+
|
|
8748
|
+
var pluginEntityWatchers = [...cronJobEntityWatchers];
|
|
8749
|
+
|
|
8596
8750
|
class CronJobService {
|
|
8597
8751
|
#server;
|
|
8598
|
-
#
|
|
8752
|
+
#namedJobInstances;
|
|
8599
8753
|
constructor(server) {
|
|
8600
8754
|
this.#server = server;
|
|
8601
8755
|
}
|
|
@@ -8607,32 +8761,80 @@ class CronJobService {
|
|
|
8607
8761
|
getJobConfigurationByCode(code) {
|
|
8608
8762
|
return lodash.find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
8609
8763
|
}
|
|
8764
|
+
#createJobInstance(job) {
|
|
8765
|
+
return cron.CronJob.from({
|
|
8766
|
+
...(job.jobOptions || {}),
|
|
8767
|
+
cronTime: job.cronTime,
|
|
8768
|
+
onTick: async () => {
|
|
8769
|
+
await this.tryExecuteJob(job);
|
|
8770
|
+
},
|
|
8771
|
+
});
|
|
8772
|
+
}
|
|
8773
|
+
async #startJobInstance(routeContext, jobConfiguration, jobInstance) {
|
|
8774
|
+
const server = this.#server;
|
|
8775
|
+
const jobCode = jobConfiguration.code;
|
|
8776
|
+
const cronJobManager = server.getEntityManager("sys_cron_job");
|
|
8777
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
8778
|
+
routeContext,
|
|
8779
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
8780
|
+
});
|
|
8781
|
+
if (cronJobInDb) {
|
|
8782
|
+
let nextRunningTime;
|
|
8783
|
+
nextRunningTime = formatDateTimeWithTimezone(jobInstance.nextDate().toISO());
|
|
8784
|
+
await cronJobManager.updateEntityById({
|
|
8785
|
+
routeContext,
|
|
8786
|
+
id: cronJobInDb.id,
|
|
8787
|
+
entityToSave: {
|
|
8788
|
+
nextRunningTime,
|
|
8789
|
+
},
|
|
8790
|
+
});
|
|
8791
|
+
}
|
|
8792
|
+
jobInstance.start();
|
|
8793
|
+
}
|
|
8794
|
+
async #setJobNextRunningTime(routeContext, jobCode, nextRunningTime) {
|
|
8795
|
+
const server = this.#server;
|
|
8796
|
+
const cronJobManager = server.getEntityManager("sys_cron_job");
|
|
8797
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
8798
|
+
routeContext,
|
|
8799
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
8800
|
+
});
|
|
8801
|
+
await cronJobManager.updateEntityById({
|
|
8802
|
+
routeContext,
|
|
8803
|
+
id: cronJobInDb.id,
|
|
8804
|
+
entityToSave: {
|
|
8805
|
+
nextRunningTime,
|
|
8806
|
+
},
|
|
8807
|
+
});
|
|
8808
|
+
}
|
|
8610
8809
|
/**
|
|
8611
8810
|
* 重新加载定时任务
|
|
8612
8811
|
*/
|
|
8613
|
-
reloadJobs() {
|
|
8812
|
+
async reloadJobs() {
|
|
8614
8813
|
const server = this.#server;
|
|
8615
|
-
|
|
8616
|
-
|
|
8814
|
+
const routeContext = RouteContext.newSystemOperationContext(server);
|
|
8815
|
+
if (this.#namedJobInstances) {
|
|
8816
|
+
for (const job of this.#namedJobInstances) {
|
|
8617
8817
|
job.instance.stop();
|
|
8618
8818
|
}
|
|
8619
8819
|
}
|
|
8620
|
-
this.#
|
|
8621
|
-
const
|
|
8622
|
-
|
|
8623
|
-
|
|
8820
|
+
this.#namedJobInstances = [];
|
|
8821
|
+
const cronJobManager = server.getEntityManager("sys_cron_job");
|
|
8822
|
+
const cronJobConfigurationsInDb = await cronJobManager.findEntities({ routeContext });
|
|
8823
|
+
const cronJobConfigurations = server.listCronJobs();
|
|
8824
|
+
for (const cronJobConfig of cronJobConfigurations) {
|
|
8825
|
+
const jobCode = cronJobConfig.code;
|
|
8826
|
+
const jobConfigInDb = lodash.find(cronJobConfigurationsInDb, { code: jobCode });
|
|
8827
|
+
if (jobConfigInDb) {
|
|
8828
|
+
overrideJobConfig(cronJobConfig, jobConfigInDb);
|
|
8829
|
+
}
|
|
8830
|
+
if (cronJobConfig.disabled) {
|
|
8831
|
+
await this.#setJobNextRunningTime(routeContext, jobCode, null);
|
|
8624
8832
|
continue;
|
|
8625
8833
|
}
|
|
8626
|
-
const jobInstance =
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
await this.tryExecuteJob(job);
|
|
8631
|
-
},
|
|
8632
|
-
});
|
|
8633
|
-
jobInstance.start();
|
|
8634
|
-
this.#jobInstances.push({
|
|
8635
|
-
code: job.code,
|
|
8834
|
+
const jobInstance = this.#createJobInstance(cronJobConfig);
|
|
8835
|
+
await this.#startJobInstance(routeContext, cronJobConfig, jobInstance);
|
|
8836
|
+
this.#namedJobInstances.push({
|
|
8837
|
+
code: jobCode,
|
|
8636
8838
|
instance: jobInstance,
|
|
8637
8839
|
});
|
|
8638
8840
|
}
|
|
@@ -8640,7 +8842,8 @@ class CronJobService {
|
|
|
8640
8842
|
async tryExecuteJob(job) {
|
|
8641
8843
|
const server = this.#server;
|
|
8642
8844
|
const logger = server.getLogger();
|
|
8643
|
-
|
|
8845
|
+
const jobCode = job.code;
|
|
8846
|
+
logger.info(`Executing cron job '${jobCode}'...`);
|
|
8644
8847
|
let handlerContext = {
|
|
8645
8848
|
logger,
|
|
8646
8849
|
routerContext: RouteContext.newSystemOperationContext(server),
|
|
@@ -8649,21 +8852,54 @@ class CronJobService {
|
|
|
8649
8852
|
applicationConfig: null,
|
|
8650
8853
|
input: null,
|
|
8651
8854
|
};
|
|
8855
|
+
let result;
|
|
8856
|
+
let lastErrorMessage;
|
|
8857
|
+
let lastErrorStack;
|
|
8652
8858
|
try {
|
|
8653
8859
|
await this.executeJob(handlerContext, job);
|
|
8654
|
-
|
|
8860
|
+
result = "success";
|
|
8861
|
+
logger.info(`Completed cron job '${jobCode}'...`);
|
|
8655
8862
|
}
|
|
8656
8863
|
catch (ex) {
|
|
8657
|
-
logger.error('Cron job "%s" execution error: %s',
|
|
8864
|
+
logger.error('Cron job "%s" execution error: %s', jobCode, ex.message);
|
|
8865
|
+
if (lodash.isString(ex)) {
|
|
8866
|
+
lastErrorMessage = ex;
|
|
8867
|
+
}
|
|
8868
|
+
else {
|
|
8869
|
+
lastErrorMessage = ex.message;
|
|
8870
|
+
lastErrorStack = ex.stack;
|
|
8871
|
+
}
|
|
8872
|
+
result = "failed";
|
|
8658
8873
|
if (job.onError) {
|
|
8659
8874
|
try {
|
|
8660
8875
|
await job.onError(handlerContext, ex);
|
|
8661
8876
|
}
|
|
8662
8877
|
catch (ex) {
|
|
8663
|
-
logger.error('Error handler of cron job "%s" execution failed: %s',
|
|
8878
|
+
logger.error('Error handler of cron job "%s" execution failed: %s', jobCode, ex.message);
|
|
8664
8879
|
}
|
|
8665
8880
|
}
|
|
8666
8881
|
}
|
|
8882
|
+
const cronJobManager = server.getEntityManager("sys_cron_job");
|
|
8883
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
8884
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
8885
|
+
});
|
|
8886
|
+
if (cronJobInDb) {
|
|
8887
|
+
let nextRunningTime;
|
|
8888
|
+
const namedJobInstance = lodash.find(this.#namedJobInstances, { code: jobCode });
|
|
8889
|
+
if (namedJobInstance && namedJobInstance.instance) {
|
|
8890
|
+
nextRunningTime = formatDateTimeWithTimezone(namedJobInstance.instance.nextDate().toISO());
|
|
8891
|
+
}
|
|
8892
|
+
await cronJobManager.updateEntityById({
|
|
8893
|
+
id: cronJobInDb.id,
|
|
8894
|
+
entityToSave: {
|
|
8895
|
+
nextRunningTime,
|
|
8896
|
+
lastRunningResult: result,
|
|
8897
|
+
lastRunningTime: getNowStringWithTimezone(),
|
|
8898
|
+
lastErrorMessage,
|
|
8899
|
+
lastErrorStack,
|
|
8900
|
+
},
|
|
8901
|
+
});
|
|
8902
|
+
}
|
|
8667
8903
|
}
|
|
8668
8904
|
/**
|
|
8669
8905
|
* 执行指定任务
|
|
@@ -8680,6 +8916,50 @@ class CronJobService {
|
|
|
8680
8916
|
await job.handler(handlerContext, job.handleOptions);
|
|
8681
8917
|
}
|
|
8682
8918
|
}
|
|
8919
|
+
async updateJobConfig(routeContext, options) {
|
|
8920
|
+
const server = this.#server;
|
|
8921
|
+
const cronJobs = server.listCronJobs();
|
|
8922
|
+
const jobCode = options.code;
|
|
8923
|
+
if (!jobCode) {
|
|
8924
|
+
throw new Error(`options.code is required.`);
|
|
8925
|
+
}
|
|
8926
|
+
const cronJobConfig = lodash.find(cronJobs, { code: jobCode });
|
|
8927
|
+
if (!cronJobConfig) {
|
|
8928
|
+
throw new Error(`Cron job with code "${jobCode}" not found.`);
|
|
8929
|
+
}
|
|
8930
|
+
if (!["cronTime", "disabled", "jobOptions"].some((field) => !lodash.isNil(options[field]))) {
|
|
8931
|
+
return;
|
|
8932
|
+
}
|
|
8933
|
+
overrideJobConfig(cronJobConfig, options);
|
|
8934
|
+
const namedJobInstance = lodash.find(this.#namedJobInstances, { code: jobCode });
|
|
8935
|
+
if (namedJobInstance && namedJobInstance.instance) {
|
|
8936
|
+
namedJobInstance.instance.stop();
|
|
8937
|
+
namedJobInstance.instance = null;
|
|
8938
|
+
}
|
|
8939
|
+
if (cronJobConfig.disabled) {
|
|
8940
|
+
await this.#setJobNextRunningTime(routeContext, jobCode, null);
|
|
8941
|
+
}
|
|
8942
|
+
else {
|
|
8943
|
+
const jobInstance = this.#createJobInstance(cronJobConfig);
|
|
8944
|
+
await this.#startJobInstance(routeContext, cronJobConfig, jobInstance);
|
|
8945
|
+
if (namedJobInstance) {
|
|
8946
|
+
namedJobInstance.instance = jobInstance;
|
|
8947
|
+
}
|
|
8948
|
+
else {
|
|
8949
|
+
this.#namedJobInstances.push({
|
|
8950
|
+
code: cronJobConfig.code,
|
|
8951
|
+
instance: jobInstance,
|
|
8952
|
+
});
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
}
|
|
8957
|
+
function overrideJobConfig(original, overrides) {
|
|
8958
|
+
["cronTime", "disabled", "jobOptions"].forEach((field) => {
|
|
8959
|
+
if (!lodash.isNil(overrides[field])) {
|
|
8960
|
+
original[field] = overrides[field];
|
|
8961
|
+
}
|
|
8962
|
+
});
|
|
8683
8963
|
}
|
|
8684
8964
|
|
|
8685
8965
|
class CronJobPlugin {
|
|
@@ -8710,11 +8990,16 @@ class CronJobPlugin {
|
|
|
8710
8990
|
server.registerActionHandler(this, actionHandler);
|
|
8711
8991
|
}
|
|
8712
8992
|
}
|
|
8713
|
-
async registerEventHandlers(server) {
|
|
8993
|
+
async registerEventHandlers(server) {
|
|
8994
|
+
for (const entityWatcher of pluginEntityWatchers)
|
|
8995
|
+
server.registerEntityWatcher(entityWatcher);
|
|
8996
|
+
}
|
|
8714
8997
|
async registerMessageHandlers(server) { }
|
|
8715
8998
|
async registerTaskProcessors(server) { }
|
|
8716
8999
|
async onLoadingApplication(server, applicationConfig) { }
|
|
8717
|
-
async configureModels(server, applicationConfig) {
|
|
9000
|
+
async configureModels(server, applicationConfig) {
|
|
9001
|
+
server.appendApplicationConfig({ models: pluginModels$1 });
|
|
9002
|
+
}
|
|
8718
9003
|
async configureModelProperties(server, applicationConfig) { }
|
|
8719
9004
|
async configureServices(server, applicationConfig) {
|
|
8720
9005
|
this.#cronJobService = new CronJobService(server);
|
|
@@ -8723,9 +9008,24 @@ class CronJobPlugin {
|
|
|
8723
9008
|
async configureRoutes(server, applicationConfig) {
|
|
8724
9009
|
server.appendApplicationConfig({ routes: pluginRoutes$1 });
|
|
8725
9010
|
}
|
|
8726
|
-
async onApplicationLoaded(server, applicationConfig) {
|
|
9011
|
+
async onApplicationLoaded(server, applicationConfig) {
|
|
9012
|
+
await saveCronJobsToDatabase(server);
|
|
9013
|
+
}
|
|
8727
9014
|
async onApplicationReady(server, applicationConfig) {
|
|
8728
|
-
this.#cronJobService.reloadJobs();
|
|
9015
|
+
await this.#cronJobService.reloadJobs();
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
async function saveCronJobsToDatabase(server) {
|
|
9019
|
+
const cronJobManager = server.getEntityManager("sys_cron_job");
|
|
9020
|
+
for (const cronJobToSave of server.listCronJobs()) {
|
|
9021
|
+
const currentCronJob = await cronJobManager.findEntity({
|
|
9022
|
+
filters: [{ operator: "eq", field: "code", value: cronJobToSave.code }],
|
|
9023
|
+
});
|
|
9024
|
+
if (!currentCronJob) {
|
|
9025
|
+
await cronJobManager.createEntity({
|
|
9026
|
+
entity: lodash.pick(cronJobToSave, ["code", "description", "cronTime", "disabled"]),
|
|
9027
|
+
});
|
|
9028
|
+
}
|
|
8729
9029
|
}
|
|
8730
9030
|
}
|
|
8731
9031
|
|
|
@@ -9141,6 +9441,7 @@ exports.createJwt = createJwt;
|
|
|
9141
9441
|
exports.decodeJwt = decodeJwt;
|
|
9142
9442
|
exports.deleteCookie = deleteCookie;
|
|
9143
9443
|
exports.detectChangedFieldsOfEntity = detectChangedFieldsOfEntity;
|
|
9444
|
+
exports.formatDateTimeWithTimezone = formatDateTimeWithTimezone;
|
|
9144
9445
|
exports.generateJwtSecretKey = generateJwtSecretKey;
|
|
9145
9446
|
exports.getCookies = getCookies;
|
|
9146
9447
|
exports.getDateString = getDateString;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CronJob } from "cron";
|
|
2
|
+
import { CronJobConfiguration } from "../../types/cron-job-types";
|
|
2
3
|
export type RunCronJobActionHandlerOptions = {
|
|
3
4
|
code?: string;
|
|
4
5
|
};
|
|
@@ -10,3 +11,28 @@ export type NamedCronJobInstance = {
|
|
|
10
11
|
code: string;
|
|
11
12
|
instance: CronJob;
|
|
12
13
|
};
|
|
14
|
+
export type JobRunningResult = "success" | "failed" | "error";
|
|
15
|
+
export type SysCronJob = {
|
|
16
|
+
id: number;
|
|
17
|
+
code: string;
|
|
18
|
+
description: string;
|
|
19
|
+
cronTime: string;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
jobOptions: CronJobConfiguration["jobOptions"];
|
|
22
|
+
isRunning: boolean;
|
|
23
|
+
nextRunningTime: string;
|
|
24
|
+
lastRunningTime: string;
|
|
25
|
+
lastRunningResult?: JobRunningResult;
|
|
26
|
+
lastErrorMessage?: string;
|
|
27
|
+
lastErrorStack?: string;
|
|
28
|
+
actionHandlerCode?: string;
|
|
29
|
+
handler?: string;
|
|
30
|
+
handleOptions?: Record<string, any>;
|
|
31
|
+
onError?: string;
|
|
32
|
+
};
|
|
33
|
+
export type UpdateJobConfigOptions = {
|
|
34
|
+
code: string;
|
|
35
|
+
cronTime?: string;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
jobOptions?: CronJobConfiguration["jobOptions"];
|
|
38
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { IRpdServer } from "../../../core/server";
|
|
2
|
+
import { RouteContext } from "../../../core/routeContext";
|
|
3
|
+
import { UpdateJobConfigOptions } from "../CronJobPluginTypes";
|
|
2
4
|
import { CronJobConfiguration } from "../../../types/cron-job-types";
|
|
3
5
|
import { ActionHandlerContext } from "../../../core/actionHandler";
|
|
4
6
|
export default class CronJobService {
|
|
@@ -13,11 +15,12 @@ export default class CronJobService {
|
|
|
13
15
|
/**
|
|
14
16
|
* 重新加载定时任务
|
|
15
17
|
*/
|
|
16
|
-
reloadJobs(): void
|
|
18
|
+
reloadJobs(): Promise<void>;
|
|
17
19
|
tryExecuteJob(job: CronJobConfiguration): Promise<void>;
|
|
18
20
|
/**
|
|
19
21
|
* 执行指定任务
|
|
20
22
|
* @param job
|
|
21
23
|
*/
|
|
22
24
|
executeJob(handlerContext: ActionHandlerContext, job: CronJobConfiguration): Promise<void>;
|
|
25
|
+
updateJobConfig(routeContext: RouteContext, options: UpdateJobConfigOptions): Promise<void>;
|
|
23
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RpdApplicationConfig } from "~/types";
|
|
2
|
+
import pluginModels from "./models";
|
|
2
3
|
import pluginActionHandlers from "./actionHandlers";
|
|
3
4
|
import pluginRoutes from "./routes";
|
|
5
|
+
import pluginEntityWatchers from "./entityWatchers";
|
|
4
6
|
import {
|
|
5
7
|
IRpdServer,
|
|
6
8
|
RapidPlugin,
|
|
@@ -9,6 +11,8 @@ import {
|
|
|
9
11
|
RpdServerPluginExtendingAbilities,
|
|
10
12
|
} from "~/core/server";
|
|
11
13
|
import CronJobService from "./services/CronJobService";
|
|
14
|
+
import { SysCronJob } from "./CronJobPluginTypes";
|
|
15
|
+
import { pick } from "lodash";
|
|
12
16
|
|
|
13
17
|
class CronJobPlugin implements RapidPlugin {
|
|
14
18
|
#server: IRpdServer;
|
|
@@ -48,15 +52,18 @@ class CronJobPlugin implements RapidPlugin {
|
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
async registerEventHandlers(server: IRpdServer): Promise<any> {
|
|
52
|
-
|
|
55
|
+
async registerEventHandlers(server: IRpdServer): Promise<any> {
|
|
56
|
+
for (const entityWatcher of pluginEntityWatchers) server.registerEntityWatcher(entityWatcher);
|
|
57
|
+
}
|
|
53
58
|
async registerMessageHandlers(server: IRpdServer): Promise<any> {}
|
|
54
59
|
|
|
55
60
|
async registerTaskProcessors(server: IRpdServer): Promise<any> {}
|
|
56
61
|
|
|
57
62
|
async onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {}
|
|
58
63
|
|
|
59
|
-
async configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
64
|
+
async configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
65
|
+
server.appendApplicationConfig({ models: pluginModels });
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
async configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {}
|
|
62
69
|
|
|
@@ -69,11 +76,29 @@ class CronJobPlugin implements RapidPlugin {
|
|
|
69
76
|
server.appendApplicationConfig({ routes: pluginRoutes });
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
async onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
79
|
+
async onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
80
|
+
await saveCronJobsToDatabase(server);
|
|
81
|
+
}
|
|
73
82
|
|
|
74
83
|
async onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
75
|
-
this.#cronJobService.reloadJobs();
|
|
84
|
+
await this.#cronJobService.reloadJobs();
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
export default CronJobPlugin;
|
|
89
|
+
|
|
90
|
+
async function saveCronJobsToDatabase(server: IRpdServer) {
|
|
91
|
+
const cronJobManager = server.getEntityManager<SysCronJob>("sys_cron_job");
|
|
92
|
+
|
|
93
|
+
for (const cronJobToSave of server.listCronJobs()) {
|
|
94
|
+
const currentCronJob = await cronJobManager.findEntity({
|
|
95
|
+
filters: [{ operator: "eq", field: "code", value: cronJobToSave.code }],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!currentCronJob) {
|
|
99
|
+
await cronJobManager.createEntity({
|
|
100
|
+
entity: pick(cronJobToSave, ["code", "description", "cronTime", "disabled"]),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CronJob } from "cron";
|
|
2
|
+
import { CronJobConfiguration } from "~/types/cron-job-types";
|
|
2
3
|
|
|
3
4
|
export type RunCronJobActionHandlerOptions = {
|
|
4
5
|
code?: string;
|
|
@@ -13,3 +14,31 @@ export type NamedCronJobInstance = {
|
|
|
13
14
|
code: string;
|
|
14
15
|
instance: CronJob;
|
|
15
16
|
};
|
|
17
|
+
|
|
18
|
+
export type JobRunningResult = "success" | "failed" | "error";
|
|
19
|
+
|
|
20
|
+
export type SysCronJob = {
|
|
21
|
+
id: number;
|
|
22
|
+
code: string;
|
|
23
|
+
description: string;
|
|
24
|
+
cronTime: string;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
jobOptions: CronJobConfiguration["jobOptions"];
|
|
27
|
+
isRunning: boolean;
|
|
28
|
+
nextRunningTime: string;
|
|
29
|
+
lastRunningTime: string;
|
|
30
|
+
lastRunningResult?: JobRunningResult;
|
|
31
|
+
lastErrorMessage?: string;
|
|
32
|
+
lastErrorStack?: string;
|
|
33
|
+
actionHandlerCode?: string;
|
|
34
|
+
handler?: string;
|
|
35
|
+
handleOptions?: Record<string, any>;
|
|
36
|
+
onError?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type UpdateJobConfigOptions = {
|
|
40
|
+
code: string;
|
|
41
|
+
cronTime?: string;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
jobOptions?: CronJobConfiguration["jobOptions"];
|
|
44
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EntityWatcher, EntityWatchHandlerContext } from "~/types";
|
|
2
|
+
import CronJobService from "../services/CronJobService";
|
|
3
|
+
import { SysCronJob } from "../CronJobPluginTypes";
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
eventName: "entity.update",
|
|
8
|
+
modelSingularCode: "sys_cron_job",
|
|
9
|
+
handler: async (ctx: EntityWatchHandlerContext<"entity.update">) => {
|
|
10
|
+
const { server, payload, routerContext: routeContext } = ctx;
|
|
11
|
+
|
|
12
|
+
const cronJobService = server.getService<CronJobService>("cronJobService");
|
|
13
|
+
|
|
14
|
+
const changes: Partial<SysCronJob> = payload.changes;
|
|
15
|
+
const after: SysCronJob = payload.after;
|
|
16
|
+
await cronJobService.updateJobConfig(routeContext, {
|
|
17
|
+
code: after.code,
|
|
18
|
+
cronTime: changes.cronTime,
|
|
19
|
+
disabled: changes.disabled,
|
|
20
|
+
jobOptions: changes.jobOptions,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
] satisfies EntityWatcher<any>[];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { RpdDataModel } from "~/types";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
maintainedBy: "cronJob",
|
|
5
|
+
namespace: "sys",
|
|
6
|
+
name: "sys_cron_job",
|
|
7
|
+
singularCode: "sys_cron_job",
|
|
8
|
+
pluralCode: "sys_cron_jobs",
|
|
9
|
+
schema: "public",
|
|
10
|
+
tableName: "sys_cron_jobs",
|
|
11
|
+
properties: [
|
|
12
|
+
{
|
|
13
|
+
name: "id",
|
|
14
|
+
code: "id",
|
|
15
|
+
columnName: "id",
|
|
16
|
+
type: "integer",
|
|
17
|
+
required: true,
|
|
18
|
+
autoIncrement: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "code",
|
|
22
|
+
code: "code",
|
|
23
|
+
columnName: "code",
|
|
24
|
+
type: "text",
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "description",
|
|
29
|
+
code: "description",
|
|
30
|
+
columnName: "description",
|
|
31
|
+
type: "text",
|
|
32
|
+
required: false,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "cronTime",
|
|
36
|
+
code: "cronTime",
|
|
37
|
+
columnName: "cron_time",
|
|
38
|
+
type: "text",
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "disabled",
|
|
43
|
+
code: "disabled",
|
|
44
|
+
columnName: "disabled",
|
|
45
|
+
type: "boolean",
|
|
46
|
+
required: true,
|
|
47
|
+
defaultValue: "false",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "jobOptions",
|
|
51
|
+
code: "jobOptions",
|
|
52
|
+
columnName: "job_options",
|
|
53
|
+
type: "json",
|
|
54
|
+
required: false,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "isRunning",
|
|
58
|
+
code: "isRunning",
|
|
59
|
+
columnName: "is_running",
|
|
60
|
+
type: "boolean",
|
|
61
|
+
required: true,
|
|
62
|
+
defaultValue: "false",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "nextRunningTime",
|
|
66
|
+
code: "nextRunningTime",
|
|
67
|
+
columnName: "next_running_time",
|
|
68
|
+
type: "datetime",
|
|
69
|
+
required: false,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "lastRunningTime",
|
|
73
|
+
code: "lastRunningTime",
|
|
74
|
+
columnName: "last_running_time",
|
|
75
|
+
type: "datetime",
|
|
76
|
+
required: false,
|
|
77
|
+
},
|
|
78
|
+
// success, failed, error
|
|
79
|
+
{
|
|
80
|
+
name: "lastRunningResult",
|
|
81
|
+
code: "lastRunningResult",
|
|
82
|
+
columnName: "last_running_result",
|
|
83
|
+
type: "text",
|
|
84
|
+
required: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "lastErrorMessage",
|
|
88
|
+
code: "lastErrorMessage",
|
|
89
|
+
columnName: "last_error_message",
|
|
90
|
+
type: "text",
|
|
91
|
+
required: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "lastErrorStack",
|
|
95
|
+
code: "lastErrorStack",
|
|
96
|
+
columnName: "last_error_stack",
|
|
97
|
+
type: "text",
|
|
98
|
+
required: false,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "actionHandlerCode",
|
|
102
|
+
code: "actionHandlerCode",
|
|
103
|
+
columnName: "action_handler_code",
|
|
104
|
+
type: "text",
|
|
105
|
+
required: false,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "handler",
|
|
109
|
+
code: "handler",
|
|
110
|
+
columnName: "handler",
|
|
111
|
+
type: "text",
|
|
112
|
+
required: false,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "handleOptions",
|
|
116
|
+
code: "handleOptions",
|
|
117
|
+
columnName: "handle_options",
|
|
118
|
+
type: "json",
|
|
119
|
+
required: false,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "onError",
|
|
123
|
+
code: "onError",
|
|
124
|
+
columnName: "on_error",
|
|
125
|
+
type: "text",
|
|
126
|
+
required: false,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
} as RpdDataModel;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { CronJob } from "cron";
|
|
2
2
|
import { IRpdServer } from "~/core/server";
|
|
3
|
-
import { find } from "lodash";
|
|
3
|
+
import { find, isNil, isString, Many } from "lodash";
|
|
4
4
|
import { RouteContext } from "~/core/routeContext";
|
|
5
5
|
import { validateLicense } from "~/helpers/licenseHelper";
|
|
6
|
-
import { NamedCronJobInstance } from "../CronJobPluginTypes";
|
|
6
|
+
import { JobRunningResult, NamedCronJobInstance, SysCronJob, UpdateJobConfigOptions } from "../CronJobPluginTypes";
|
|
7
7
|
import { CronJobConfiguration } from "~/types/cron-job-types";
|
|
8
8
|
import { ActionHandlerContext } from "~/core/actionHandler";
|
|
9
|
+
import { formatDateTimeWithTimezone, getNowStringWithTimezone } from "~/utilities/timeUtility";
|
|
9
10
|
|
|
10
11
|
export default class CronJobService {
|
|
11
12
|
#server: IRpdServer;
|
|
12
|
-
#
|
|
13
|
+
#namedJobInstances: NamedCronJobInstance[];
|
|
13
14
|
|
|
14
15
|
constructor(server: IRpdServer) {
|
|
15
16
|
this.#server = server;
|
|
@@ -24,37 +25,93 @@ export default class CronJobService {
|
|
|
24
25
|
return find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
#createJobInstance(job: CronJobConfiguration) {
|
|
29
|
+
return CronJob.from({
|
|
30
|
+
...(job.jobOptions || {}),
|
|
31
|
+
cronTime: job.cronTime,
|
|
32
|
+
onTick: async () => {
|
|
33
|
+
await this.tryExecuteJob(job);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async #startJobInstance(routeContext: RouteContext, jobConfiguration: CronJobConfiguration, jobInstance: CronJob) {
|
|
39
|
+
const server = this.#server;
|
|
40
|
+
const jobCode = jobConfiguration.code;
|
|
41
|
+
const cronJobManager = server.getEntityManager<SysCronJob>("sys_cron_job");
|
|
42
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
43
|
+
routeContext,
|
|
44
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (cronJobInDb) {
|
|
48
|
+
let nextRunningTime: string | null;
|
|
49
|
+
nextRunningTime = formatDateTimeWithTimezone(jobInstance.nextDate().toISO());
|
|
50
|
+
|
|
51
|
+
await cronJobManager.updateEntityById({
|
|
52
|
+
routeContext,
|
|
53
|
+
id: cronJobInDb.id,
|
|
54
|
+
entityToSave: {
|
|
55
|
+
nextRunningTime,
|
|
56
|
+
} as Partial<SysCronJob>,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
jobInstance.start();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async #setJobNextRunningTime(routeContext: RouteContext, jobCode: string, nextRunningTime: string | null) {
|
|
64
|
+
const server = this.#server;
|
|
65
|
+
const cronJobManager = server.getEntityManager<SysCronJob>("sys_cron_job");
|
|
66
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
67
|
+
routeContext,
|
|
68
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
69
|
+
});
|
|
70
|
+
await cronJobManager.updateEntityById({
|
|
71
|
+
routeContext,
|
|
72
|
+
id: cronJobInDb.id,
|
|
73
|
+
entityToSave: {
|
|
74
|
+
nextRunningTime,
|
|
75
|
+
} as Partial<SysCronJob>,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
27
79
|
/**
|
|
28
80
|
* 重新加载定时任务
|
|
29
81
|
*/
|
|
30
|
-
reloadJobs() {
|
|
82
|
+
async reloadJobs() {
|
|
31
83
|
const server = this.#server;
|
|
84
|
+
const routeContext = RouteContext.newSystemOperationContext(server);
|
|
32
85
|
|
|
33
|
-
if (this.#
|
|
34
|
-
for (const job of this.#
|
|
86
|
+
if (this.#namedJobInstances) {
|
|
87
|
+
for (const job of this.#namedJobInstances) {
|
|
35
88
|
job.instance.stop();
|
|
36
89
|
}
|
|
37
90
|
}
|
|
38
91
|
|
|
39
|
-
this.#
|
|
92
|
+
this.#namedJobInstances = [];
|
|
40
93
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
94
|
+
const cronJobManager = server.getEntityManager<SysCronJob>("sys_cron_job");
|
|
95
|
+
const cronJobConfigurationsInDb = await cronJobManager.findEntities({ routeContext });
|
|
96
|
+
|
|
97
|
+
const cronJobConfigurations = server.listCronJobs();
|
|
98
|
+
for (const cronJobConfig of cronJobConfigurations) {
|
|
99
|
+
const jobCode = cronJobConfig.code;
|
|
100
|
+
const jobConfigInDb = find(cronJobConfigurationsInDb, { code: jobCode });
|
|
101
|
+
if (jobConfigInDb) {
|
|
102
|
+
overrideJobConfig(cronJobConfig, jobConfigInDb);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (cronJobConfig.disabled) {
|
|
106
|
+
await this.#setJobNextRunningTime(routeContext, jobCode, null);
|
|
44
107
|
continue;
|
|
45
108
|
}
|
|
46
109
|
|
|
47
|
-
const jobInstance =
|
|
48
|
-
|
|
49
|
-
cronTime: job.cronTime,
|
|
50
|
-
onTick: async () => {
|
|
51
|
-
await this.tryExecuteJob(job);
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
jobInstance.start();
|
|
110
|
+
const jobInstance = this.#createJobInstance(cronJobConfig);
|
|
111
|
+
await this.#startJobInstance(routeContext, cronJobConfig, jobInstance);
|
|
55
112
|
|
|
56
|
-
this.#
|
|
57
|
-
code:
|
|
113
|
+
this.#namedJobInstances.push({
|
|
114
|
+
code: jobCode,
|
|
58
115
|
instance: jobInstance,
|
|
59
116
|
});
|
|
60
117
|
}
|
|
@@ -63,7 +120,9 @@ export default class CronJobService {
|
|
|
63
120
|
async tryExecuteJob(job: CronJobConfiguration) {
|
|
64
121
|
const server = this.#server;
|
|
65
122
|
const logger = server.getLogger();
|
|
66
|
-
|
|
123
|
+
|
|
124
|
+
const jobCode = job.code;
|
|
125
|
+
logger.info(`Executing cron job '${jobCode}'...`);
|
|
67
126
|
|
|
68
127
|
let handlerContext: ActionHandlerContext = {
|
|
69
128
|
logger,
|
|
@@ -74,20 +133,55 @@ export default class CronJobService {
|
|
|
74
133
|
input: null,
|
|
75
134
|
};
|
|
76
135
|
|
|
136
|
+
let result: JobRunningResult;
|
|
137
|
+
let lastErrorMessage: string | null;
|
|
138
|
+
let lastErrorStack: string | null;
|
|
77
139
|
try {
|
|
78
140
|
await this.executeJob(handlerContext, job);
|
|
79
|
-
|
|
141
|
+
result = "success";
|
|
142
|
+
logger.info(`Completed cron job '${jobCode}'...`);
|
|
80
143
|
} catch (ex: any) {
|
|
81
|
-
logger.error('Cron job "%s" execution error: %s',
|
|
144
|
+
logger.error('Cron job "%s" execution error: %s', jobCode, ex.message);
|
|
145
|
+
if (isString(ex)) {
|
|
146
|
+
lastErrorMessage = ex;
|
|
147
|
+
} else {
|
|
148
|
+
lastErrorMessage = ex.message;
|
|
149
|
+
lastErrorStack = ex.stack;
|
|
150
|
+
}
|
|
151
|
+
result = "failed";
|
|
82
152
|
|
|
83
153
|
if (job.onError) {
|
|
84
154
|
try {
|
|
85
155
|
await job.onError(handlerContext, ex);
|
|
86
156
|
} catch (ex) {
|
|
87
|
-
logger.error('Error handler of cron job "%s" execution failed: %s',
|
|
157
|
+
logger.error('Error handler of cron job "%s" execution failed: %s', jobCode, ex.message);
|
|
88
158
|
}
|
|
89
159
|
}
|
|
90
160
|
}
|
|
161
|
+
|
|
162
|
+
const cronJobManager = server.getEntityManager<SysCronJob>("sys_cron_job");
|
|
163
|
+
const cronJobInDb = await cronJobManager.findEntity({
|
|
164
|
+
filters: [{ operator: "eq", field: "code", value: jobCode }],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (cronJobInDb) {
|
|
168
|
+
let nextRunningTime: string | null;
|
|
169
|
+
const namedJobInstance = find(this.#namedJobInstances, { code: jobCode });
|
|
170
|
+
if (namedJobInstance && namedJobInstance.instance) {
|
|
171
|
+
nextRunningTime = formatDateTimeWithTimezone(namedJobInstance.instance.nextDate().toISO());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await cronJobManager.updateEntityById({
|
|
175
|
+
id: cronJobInDb.id,
|
|
176
|
+
entityToSave: {
|
|
177
|
+
nextRunningTime,
|
|
178
|
+
lastRunningResult: result,
|
|
179
|
+
lastRunningTime: getNowStringWithTimezone(),
|
|
180
|
+
lastErrorMessage,
|
|
181
|
+
lastErrorStack,
|
|
182
|
+
} as Partial<SysCronJob>,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
91
185
|
}
|
|
92
186
|
|
|
93
187
|
/**
|
|
@@ -105,4 +199,54 @@ export default class CronJobService {
|
|
|
105
199
|
await job.handler(handlerContext, job.handleOptions);
|
|
106
200
|
}
|
|
107
201
|
}
|
|
202
|
+
|
|
203
|
+
async updateJobConfig(routeContext: RouteContext, options: UpdateJobConfigOptions) {
|
|
204
|
+
const server = this.#server;
|
|
205
|
+
const cronJobs = server.listCronJobs();
|
|
206
|
+
const jobCode = options.code;
|
|
207
|
+
if (!jobCode) {
|
|
208
|
+
throw new Error(`options.code is required.`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const cronJobConfig = find(cronJobs, { code: jobCode });
|
|
212
|
+
if (!cronJobConfig) {
|
|
213
|
+
throw new Error(`Cron job with code "${jobCode}" not found.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!(["cronTime", "disabled", "jobOptions"] as (keyof typeof options)[]).some((field) => !isNil(options[field]))) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
overrideJobConfig(cronJobConfig, options);
|
|
221
|
+
|
|
222
|
+
const namedJobInstance = find(this.#namedJobInstances, { code: jobCode });
|
|
223
|
+
if (namedJobInstance && namedJobInstance.instance) {
|
|
224
|
+
namedJobInstance.instance.stop();
|
|
225
|
+
namedJobInstance.instance = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (cronJobConfig.disabled) {
|
|
229
|
+
await this.#setJobNextRunningTime(routeContext, jobCode, null);
|
|
230
|
+
} else {
|
|
231
|
+
const jobInstance = this.#createJobInstance(cronJobConfig);
|
|
232
|
+
await this.#startJobInstance(routeContext, cronJobConfig, jobInstance);
|
|
233
|
+
|
|
234
|
+
if (namedJobInstance) {
|
|
235
|
+
namedJobInstance.instance = jobInstance;
|
|
236
|
+
} else {
|
|
237
|
+
this.#namedJobInstances.push({
|
|
238
|
+
code: cronJobConfig.code,
|
|
239
|
+
instance: jobInstance,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function overrideJobConfig(original: Partial<SysCronJob> | CronJobConfiguration, overrides: Partial<SysCronJob>) {
|
|
247
|
+
(["cronTime", "disabled", "jobOptions"] as (keyof typeof overrides)[]).forEach((field: string) => {
|
|
248
|
+
if (!isNil(overrides[field])) {
|
|
249
|
+
original[field] = overrides[field];
|
|
250
|
+
}
|
|
251
|
+
});
|
|
108
252
|
}
|
|
@@ -11,3 +11,7 @@ export function getNowStringWithTimezone() {
|
|
|
11
11
|
export function getDateString(timeString) {
|
|
12
12
|
return dayjs(timeString).format("YYYY-MM-DD");
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
export function formatDateTimeWithTimezone(source: any) {
|
|
16
|
+
return dayjs(source).format("YYYY-MM-DD HH:mm:ss.SSSZ");
|
|
17
|
+
}
|