@ruiapp/rapid-core 0.8.4 → 0.8.6
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/dataAccess/entityValidator.d.ts +3 -0
- package/dist/helpers/metaHelper.d.ts +7 -0
- package/dist/index.js +155 -73
- package/dist/plugins/cronJob/CronJobPlugin.d.ts +1 -4
- package/dist/plugins/cronJob/CronJobPluginTypes.d.ts +6 -0
- package/dist/plugins/cronJob/services/CronJobService.d.ts +23 -0
- package/dist/types/cron-job-types.d.ts +11 -0
- package/dist/utilities/timeUtility.d.ts +1 -0
- package/package.json +1 -1
- package/src/core/actionHandler.ts +1 -0
- package/src/dataAccess/entityManager.ts +7 -2
- package/src/dataAccess/entityMapper.ts +12 -1
- package/src/dataAccess/entityValidator.ts +26 -0
- package/src/helpers/metaHelper.ts +7 -0
- package/src/plugins/cronJob/CronJobPlugin.ts +8 -54
- package/src/plugins/cronJob/CronJobPluginTypes.ts +8 -0
- package/src/plugins/cronJob/actionHandlers/runCronJob.ts +12 -2
- package/src/plugins/cronJob/services/CronJobService.ts +108 -0
- package/src/types/cron-job-types.ts +13 -0
- package/src/utilities/timeUtility.ts +4 -0
|
@@ -5,6 +5,13 @@ export declare function isOneRelationProperty(property: RpdDataModelProperty): b
|
|
|
5
5
|
export declare function isManyRelationProperty(property: RpdDataModelProperty): boolean;
|
|
6
6
|
export declare function getEntityProperties(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty[];
|
|
7
7
|
export declare function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel, predicate?: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty[];
|
|
8
|
+
/**
|
|
9
|
+
* 根据 code 获取实体属性信息。如实体为派生实体,可能会返回基础实体中的属性信息。
|
|
10
|
+
* @param server
|
|
11
|
+
* @param model
|
|
12
|
+
* @param propertyCode
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
8
15
|
export declare function getEntityPropertyByCode(server: IRpdServer, model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
|
|
9
16
|
export declare function getEntityProperty(server: IRpdServer, model: RpdDataModel, predicate: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty | undefined;
|
|
10
17
|
export declare function getEntityOwnPropertyByCode(model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
|
package/dist/index.js
CHANGED
|
@@ -19,24 +19,6 @@ var xstate = require('xstate');
|
|
|
19
19
|
|
|
20
20
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
21
21
|
|
|
22
|
-
function _interopNamespace(e) {
|
|
23
|
-
if (e && e.__esModule) return e;
|
|
24
|
-
var n = Object.create(null);
|
|
25
|
-
if (e) {
|
|
26
|
-
Object.keys(e).forEach(function (k) {
|
|
27
|
-
if (k !== 'default') {
|
|
28
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
29
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
30
|
-
enumerable: true,
|
|
31
|
-
get: function () { return e[k]; }
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
n["default"] = e;
|
|
37
|
-
return Object.freeze(n);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
22
|
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
41
23
|
var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
|
|
42
24
|
var dayjs__default = /*#__PURE__*/_interopDefaultLegacy(dayjs);
|
|
@@ -45,7 +27,6 @@ var bcrypt__default = /*#__PURE__*/_interopDefaultLegacy(bcrypt);
|
|
|
45
27
|
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
46
28
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
47
29
|
var nodemailer__default = /*#__PURE__*/_interopDefaultLegacy(nodemailer);
|
|
48
|
-
var cron__namespace = /*#__PURE__*/_interopNamespace(cron);
|
|
49
30
|
|
|
50
31
|
function fixBigIntJSONSerialize() {
|
|
51
32
|
BigInt.prototype.toJSON = function () {
|
|
@@ -2444,6 +2425,13 @@ function getEntityPropertiesIncludingBase(server, model, predicate) {
|
|
|
2444
2425
|
}
|
|
2445
2426
|
return [...baseProperties, ...properties];
|
|
2446
2427
|
}
|
|
2428
|
+
/**
|
|
2429
|
+
* 根据 code 获取实体属性信息。如实体为派生实体,可能会返回基础实体中的属性信息。
|
|
2430
|
+
* @param server
|
|
2431
|
+
* @param model
|
|
2432
|
+
* @param propertyCode
|
|
2433
|
+
* @returns
|
|
2434
|
+
*/
|
|
2447
2435
|
function getEntityPropertyByCode(server, model, propertyCode) {
|
|
2448
2436
|
return getEntityProperty(server, model, (e) => e.code === propertyCode);
|
|
2449
2437
|
}
|
|
@@ -2482,6 +2470,16 @@ function getEntityPropertyByFieldName(server, model, fieldName) {
|
|
|
2482
2470
|
return property;
|
|
2483
2471
|
}
|
|
2484
2472
|
|
|
2473
|
+
function getNowString() {
|
|
2474
|
+
return dayjs__default["default"]().format("YYYY-MM-DD HH:mm:ss.SSS");
|
|
2475
|
+
}
|
|
2476
|
+
function getNowStringWithTimezone() {
|
|
2477
|
+
return dayjs__default["default"]().format("YYYY-MM-DD HH:mm:ss.SSSZ");
|
|
2478
|
+
}
|
|
2479
|
+
function getDateString(timeString) {
|
|
2480
|
+
return dayjs__default["default"](timeString).format("YYYY-MM-DD");
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2485
2483
|
// TODO Generate mapper and cache it.
|
|
2486
2484
|
function mapDbRowToEntity(server, model, row, keepNonPropertyFields) {
|
|
2487
2485
|
if (!row) {
|
|
@@ -2524,8 +2522,16 @@ function mapDbRowToEntity(server, model, row, keepNonPropertyFields) {
|
|
|
2524
2522
|
}
|
|
2525
2523
|
}
|
|
2526
2524
|
else {
|
|
2525
|
+
let fieldValue = row[columnName];
|
|
2526
|
+
if (property) {
|
|
2527
|
+
if (property.type === "date") {
|
|
2528
|
+
if (fieldValue) {
|
|
2529
|
+
fieldValue = getDateString(fieldValue);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2527
2533
|
if (!result[propertyName]) {
|
|
2528
|
-
result[propertyName] =
|
|
2534
|
+
result[propertyName] = fieldValue;
|
|
2529
2535
|
}
|
|
2530
2536
|
}
|
|
2531
2537
|
});
|
|
@@ -2661,11 +2667,23 @@ function getEntityPartChanges(server, model, before, after) {
|
|
|
2661
2667
|
return null;
|
|
2662
2668
|
}
|
|
2663
2669
|
|
|
2664
|
-
function
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2670
|
+
async function validateEntity(server, model, entity) {
|
|
2671
|
+
for (const propCode in entity) {
|
|
2672
|
+
let prop = getEntityPropertyByCode(server, model, propCode);
|
|
2673
|
+
if (!prop) {
|
|
2674
|
+
getEntityPropertyByFieldName(server, model, propCode);
|
|
2675
|
+
}
|
|
2676
|
+
if (!prop) {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
if (prop.type === "date") {
|
|
2680
|
+
const originValue = entity[propCode];
|
|
2681
|
+
if (originValue) {
|
|
2682
|
+
entity[propCode] = getDateString(originValue);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
return entity;
|
|
2669
2687
|
}
|
|
2670
2688
|
|
|
2671
2689
|
function convertEntityOrderByToRowOrderBy(server, model, baseModel, orderByList) {
|
|
@@ -3305,7 +3323,9 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
3305
3323
|
if (model.derivedTypePropertyCode) {
|
|
3306
3324
|
throw newEntityOperationError("Create base entity directly is not allowed.");
|
|
3307
3325
|
}
|
|
3308
|
-
|
|
3326
|
+
let { entity } = options;
|
|
3327
|
+
entity = await validateEntity(server, model, entity);
|
|
3328
|
+
const { routeContext } = options;
|
|
3309
3329
|
const userId = options.routeContext?.state?.userId;
|
|
3310
3330
|
if (userId) {
|
|
3311
3331
|
const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
|
|
@@ -3556,6 +3576,8 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3556
3576
|
if (!id) {
|
|
3557
3577
|
throw new Error("Id is required when updating an entity.");
|
|
3558
3578
|
}
|
|
3579
|
+
let { entityToSave } = options;
|
|
3580
|
+
entityToSave = await validateEntity(server, model, entityToSave);
|
|
3559
3581
|
const entity = await findById(server, dataAccessor, {
|
|
3560
3582
|
routeContext,
|
|
3561
3583
|
id,
|
|
@@ -3564,7 +3586,6 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3564
3586
|
if (!entity) {
|
|
3565
3587
|
throw new Error(`${model.namespace}.${model.singularCode} with id "${id}" was not found.`);
|
|
3566
3588
|
}
|
|
3567
|
-
let { entityToSave } = options;
|
|
3568
3589
|
let changes = getEntityPartChanges(server, model, entity, entityToSave);
|
|
3569
3590
|
if (!changes && !options.operation) {
|
|
3570
3591
|
return entity;
|
|
@@ -8486,11 +8507,20 @@ async function handler$1(plugin, ctx, options) {
|
|
|
8486
8507
|
if (!input.code) {
|
|
8487
8508
|
throw new Error(`Cron job code is required.`);
|
|
8488
8509
|
}
|
|
8489
|
-
const
|
|
8510
|
+
const cronJobService = server.getService("cronJobService");
|
|
8511
|
+
const job = cronJobService.getJobConfigurationByCode(input.code);
|
|
8490
8512
|
if (!job) {
|
|
8491
8513
|
throw new Error(`Cron job with code '${input.code}' was not found.`);
|
|
8492
8514
|
}
|
|
8493
|
-
|
|
8515
|
+
let jobExecutionContext = {
|
|
8516
|
+
logger: server.getLogger(),
|
|
8517
|
+
routerContext,
|
|
8518
|
+
next: null,
|
|
8519
|
+
server,
|
|
8520
|
+
applicationConfig: null,
|
|
8521
|
+
input: input.input,
|
|
8522
|
+
};
|
|
8523
|
+
await cronJobService.executeJob(jobExecutionContext, job);
|
|
8494
8524
|
response.json({});
|
|
8495
8525
|
}
|
|
8496
8526
|
|
|
@@ -8518,8 +8548,98 @@ var runCronJob = {
|
|
|
8518
8548
|
|
|
8519
8549
|
var pluginRoutes$1 = [runCronJob];
|
|
8520
8550
|
|
|
8551
|
+
class CronJobService {
|
|
8552
|
+
#server;
|
|
8553
|
+
#jobInstances;
|
|
8554
|
+
constructor(server) {
|
|
8555
|
+
this.#server = server;
|
|
8556
|
+
}
|
|
8557
|
+
/**
|
|
8558
|
+
* 根据编号获取定时任务配置信息
|
|
8559
|
+
* @param code
|
|
8560
|
+
* @returns
|
|
8561
|
+
*/
|
|
8562
|
+
getJobConfigurationByCode(code) {
|
|
8563
|
+
return lodash.find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
8564
|
+
}
|
|
8565
|
+
/**
|
|
8566
|
+
* 重新加载定时任务
|
|
8567
|
+
*/
|
|
8568
|
+
reloadJobs() {
|
|
8569
|
+
const server = this.#server;
|
|
8570
|
+
if (this.#jobInstances) {
|
|
8571
|
+
for (const job of this.#jobInstances) {
|
|
8572
|
+
job.instance.stop();
|
|
8573
|
+
}
|
|
8574
|
+
}
|
|
8575
|
+
this.#jobInstances = [];
|
|
8576
|
+
const cronJobs = server.listCronJobs();
|
|
8577
|
+
for (const job of cronJobs) {
|
|
8578
|
+
if (job.disabled) {
|
|
8579
|
+
continue;
|
|
8580
|
+
}
|
|
8581
|
+
const jobInstance = cron.CronJob.from({
|
|
8582
|
+
...(job.jobOptions || {}),
|
|
8583
|
+
cronTime: job.cronTime,
|
|
8584
|
+
onTick: async () => {
|
|
8585
|
+
await this.tryExecuteJob(job);
|
|
8586
|
+
},
|
|
8587
|
+
});
|
|
8588
|
+
jobInstance.start();
|
|
8589
|
+
this.#jobInstances.push({
|
|
8590
|
+
code: job.code,
|
|
8591
|
+
instance: jobInstance,
|
|
8592
|
+
});
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
async tryExecuteJob(job) {
|
|
8596
|
+
const server = this.#server;
|
|
8597
|
+
const logger = server.getLogger();
|
|
8598
|
+
logger.info(`Executing cron job '${job.code}'...`);
|
|
8599
|
+
let handlerContext = {
|
|
8600
|
+
logger,
|
|
8601
|
+
routerContext: RouteContext.newSystemOperationContext(server),
|
|
8602
|
+
next: null,
|
|
8603
|
+
server,
|
|
8604
|
+
applicationConfig: null,
|
|
8605
|
+
input: null,
|
|
8606
|
+
};
|
|
8607
|
+
try {
|
|
8608
|
+
await this.executeJob(handlerContext, job);
|
|
8609
|
+
logger.info(`Completed cron job '${job.code}'...`);
|
|
8610
|
+
}
|
|
8611
|
+
catch (ex) {
|
|
8612
|
+
logger.error('Cron job "%s" execution error: %s', job.code, ex.message);
|
|
8613
|
+
if (job.onError) {
|
|
8614
|
+
try {
|
|
8615
|
+
await job.onError(handlerContext, ex);
|
|
8616
|
+
}
|
|
8617
|
+
catch (ex) {
|
|
8618
|
+
logger.error('Error handler of cron job "%s" execution failed: %s', job.code, ex.message);
|
|
8619
|
+
}
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
/**
|
|
8624
|
+
* 执行指定任务
|
|
8625
|
+
* @param job
|
|
8626
|
+
*/
|
|
8627
|
+
async executeJob(handlerContext, job) {
|
|
8628
|
+
const server = this.#server;
|
|
8629
|
+
validateLicense(server);
|
|
8630
|
+
if (job.actionHandlerCode) {
|
|
8631
|
+
const actionHandler = server.getActionHandlerByCode(job.code);
|
|
8632
|
+
await actionHandler(handlerContext, job.handleOptions);
|
|
8633
|
+
}
|
|
8634
|
+
else {
|
|
8635
|
+
await job.handler(handlerContext, job.handleOptions);
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
|
|
8521
8640
|
class CronJobPlugin {
|
|
8522
8641
|
#server;
|
|
8642
|
+
#cronJobService;
|
|
8523
8643
|
constructor() { }
|
|
8524
8644
|
get code() {
|
|
8525
8645
|
return "cronJob";
|
|
@@ -8551,55 +8671,16 @@ class CronJobPlugin {
|
|
|
8551
8671
|
async onLoadingApplication(server, applicationConfig) { }
|
|
8552
8672
|
async configureModels(server, applicationConfig) { }
|
|
8553
8673
|
async configureModelProperties(server, applicationConfig) { }
|
|
8674
|
+
async configureServices(server, applicationConfig) {
|
|
8675
|
+
this.#cronJobService = new CronJobService(server);
|
|
8676
|
+
server.registerService("cronJobService", this.#cronJobService);
|
|
8677
|
+
}
|
|
8554
8678
|
async configureRoutes(server, applicationConfig) {
|
|
8555
8679
|
server.appendApplicationConfig({ routes: pluginRoutes$1 });
|
|
8556
8680
|
}
|
|
8557
8681
|
async onApplicationLoaded(server, applicationConfig) { }
|
|
8558
8682
|
async onApplicationReady(server, applicationConfig) {
|
|
8559
|
-
|
|
8560
|
-
for (const job of cronJobs) {
|
|
8561
|
-
const jobInstance = cron__namespace.CronJob.from({
|
|
8562
|
-
...(job.jobOptions || {}),
|
|
8563
|
-
cronTime: job.cronTime,
|
|
8564
|
-
onTick: async () => {
|
|
8565
|
-
await this.tryExecuteJob(server, job);
|
|
8566
|
-
},
|
|
8567
|
-
});
|
|
8568
|
-
jobInstance.start();
|
|
8569
|
-
}
|
|
8570
|
-
}
|
|
8571
|
-
getJobConfigurationByCode(code) {
|
|
8572
|
-
return lodash.find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
8573
|
-
}
|
|
8574
|
-
async tryExecuteJob(server, job) {
|
|
8575
|
-
const logger = server.getLogger();
|
|
8576
|
-
logger.info(`Executing cron job '${job.code}'...`);
|
|
8577
|
-
try {
|
|
8578
|
-
await this.executeJob(server, job);
|
|
8579
|
-
logger.info(`Completed cron job '${job.code}'...`);
|
|
8580
|
-
}
|
|
8581
|
-
catch (ex) {
|
|
8582
|
-
logger.error('Cron job "%s" execution error: %s', job.code, ex.message, { cronJobCode: job.code });
|
|
8583
|
-
}
|
|
8584
|
-
}
|
|
8585
|
-
async executeJob(server, job) {
|
|
8586
|
-
const logger = server.getLogger();
|
|
8587
|
-
validateLicense(server);
|
|
8588
|
-
let handlerContext = {
|
|
8589
|
-
logger,
|
|
8590
|
-
routerContext: RouteContext.newSystemOperationContext(server),
|
|
8591
|
-
next: null,
|
|
8592
|
-
server,
|
|
8593
|
-
applicationConfig: null,
|
|
8594
|
-
input: null,
|
|
8595
|
-
};
|
|
8596
|
-
if (job.actionHandlerCode) {
|
|
8597
|
-
const actionHandler = server.getActionHandlerByCode(job.code);
|
|
8598
|
-
await actionHandler(handlerContext, job.handleOptions);
|
|
8599
|
-
}
|
|
8600
|
-
else {
|
|
8601
|
-
await job.handler(handlerContext, job.handleOptions);
|
|
8602
|
-
}
|
|
8683
|
+
this.#cronJobService.reloadJobs();
|
|
8603
8684
|
}
|
|
8604
8685
|
}
|
|
8605
8686
|
|
|
@@ -9016,6 +9097,7 @@ exports.decodeJwt = decodeJwt;
|
|
|
9016
9097
|
exports.deleteCookie = deleteCookie;
|
|
9017
9098
|
exports.generateJwtSecretKey = generateJwtSecretKey;
|
|
9018
9099
|
exports.getCookies = getCookies;
|
|
9100
|
+
exports.getDateString = getDateString;
|
|
9019
9101
|
exports.getEntityRelationTargetId = getEntityRelationTargetId;
|
|
9020
9102
|
exports.getNowString = getNowString;
|
|
9021
9103
|
exports.getNowStringWithTimezone = getNowStringWithTimezone;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { RpdApplicationConfig } from "../../types";
|
|
2
|
-
import { CronJobConfiguration } from "../../types/cron-job-types";
|
|
3
2
|
import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "../../core/server";
|
|
4
3
|
declare class CronJobPlugin implements RapidPlugin {
|
|
5
4
|
#private;
|
|
@@ -18,11 +17,9 @@ declare class CronJobPlugin implements RapidPlugin {
|
|
|
18
17
|
onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
19
18
|
configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
20
19
|
configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
20
|
+
configureServices(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
21
21
|
configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
22
22
|
onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
23
23
|
onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
24
|
-
getJobConfigurationByCode(code: string): CronJobConfiguration;
|
|
25
|
-
tryExecuteJob(server: IRpdServer, job: CronJobConfiguration): Promise<void>;
|
|
26
|
-
executeJob(server: IRpdServer, job: CronJobConfiguration): Promise<void>;
|
|
27
24
|
}
|
|
28
25
|
export default CronJobPlugin;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { CronJob } from "cron";
|
|
1
2
|
export type RunCronJobActionHandlerOptions = {
|
|
2
3
|
code?: string;
|
|
3
4
|
};
|
|
4
5
|
export type RunCronJobInput = {
|
|
5
6
|
code?: string;
|
|
7
|
+
input?: any;
|
|
8
|
+
};
|
|
9
|
+
export type NamedCronJobInstance = {
|
|
10
|
+
code: string;
|
|
11
|
+
instance: CronJob;
|
|
6
12
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IRpdServer } from "../../../core/server";
|
|
2
|
+
import { CronJobConfiguration } from "../../../types/cron-job-types";
|
|
3
|
+
import { ActionHandlerContext } from "../../../core/actionHandler";
|
|
4
|
+
export default class CronJobService {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(server: IRpdServer);
|
|
7
|
+
/**
|
|
8
|
+
* 根据编号获取定时任务配置信息
|
|
9
|
+
* @param code
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
getJobConfigurationByCode(code: string): CronJobConfiguration;
|
|
13
|
+
/**
|
|
14
|
+
* 重新加载定时任务
|
|
15
|
+
*/
|
|
16
|
+
reloadJobs(): void;
|
|
17
|
+
tryExecuteJob(job: CronJobConfiguration): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* 执行指定任务
|
|
20
|
+
* @param job
|
|
21
|
+
*/
|
|
22
|
+
executeJob(handlerContext: ActionHandlerContext, job: CronJobConfiguration): Promise<void>;
|
|
23
|
+
}
|
|
@@ -8,6 +8,10 @@ export interface CronJobConfiguration {
|
|
|
8
8
|
* 定时任务描述
|
|
9
9
|
*/
|
|
10
10
|
description?: string;
|
|
11
|
+
/**
|
|
12
|
+
* 是否禁用
|
|
13
|
+
*/
|
|
14
|
+
disabled?: boolean;
|
|
11
15
|
/**
|
|
12
16
|
* crontab 表达式
|
|
13
17
|
*/
|
|
@@ -31,6 +35,13 @@ export interface CronJobConfiguration {
|
|
|
31
35
|
* 处理定时任务时的设置选项
|
|
32
36
|
*/
|
|
33
37
|
handleOptions?: any;
|
|
38
|
+
/**
|
|
39
|
+
* 定时任务执行失败时的处理
|
|
40
|
+
* @param ctx
|
|
41
|
+
* @param error
|
|
42
|
+
* @returns
|
|
43
|
+
*/
|
|
44
|
+
onError?: (ctx: ActionHandlerContext, error: any) => Promise<void>;
|
|
34
45
|
}
|
|
35
46
|
export interface CronJobOptions {
|
|
36
47
|
/**
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import { RpdApplicationConfig } from "~/types";
|
|
|
2
2
|
import { IRpdServer, RapidPlugin } from "./server";
|
|
3
3
|
import { Next, RouteContext } from "./routeContext";
|
|
4
4
|
import { Logger } from "~/facilities/log/LogFacility";
|
|
5
|
+
import { CronJobConfiguration } from "~/types/cron-job-types";
|
|
5
6
|
|
|
6
7
|
export interface ActionHandlerContext {
|
|
7
8
|
logger: Logger;
|
|
@@ -58,6 +58,7 @@ import { ColumnSelectOptions, CountRowOptions, FindRowOptions, FindRowOrderByOpt
|
|
|
58
58
|
import { newEntityOperationError } from "~/utilities/errorUtility";
|
|
59
59
|
import { getNowStringWithTimezone } from "~/utilities/timeUtility";
|
|
60
60
|
import { RouteContext } from "~/core/routeContext";
|
|
61
|
+
import { validateEntity } from "./entityValidator";
|
|
61
62
|
|
|
62
63
|
export type FindOneRelationEntitiesOptions = {
|
|
63
64
|
server: IRpdServer;
|
|
@@ -777,8 +778,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
777
778
|
throw newEntityOperationError("Create base entity directly is not allowed.");
|
|
778
779
|
}
|
|
779
780
|
|
|
780
|
-
|
|
781
|
+
let { entity } = options;
|
|
782
|
+
entity = await validateEntity(server, model, entity);
|
|
781
783
|
|
|
784
|
+
const { routeContext } = options;
|
|
782
785
|
const userId = options.routeContext?.state?.userId;
|
|
783
786
|
if (userId) {
|
|
784
787
|
const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
|
|
@@ -1051,6 +1054,9 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1051
1054
|
throw new Error("Id is required when updating an entity.");
|
|
1052
1055
|
}
|
|
1053
1056
|
|
|
1057
|
+
let { entityToSave } = options;
|
|
1058
|
+
entityToSave = await validateEntity(server, model, entityToSave);
|
|
1059
|
+
|
|
1054
1060
|
const entity = await findById(server, dataAccessor, {
|
|
1055
1061
|
routeContext,
|
|
1056
1062
|
id,
|
|
@@ -1060,7 +1066,6 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1060
1066
|
throw new Error(`${model.namespace}.${model.singularCode} with id "${id}" was not found.`);
|
|
1061
1067
|
}
|
|
1062
1068
|
|
|
1063
|
-
let { entityToSave } = options;
|
|
1064
1069
|
let changes = getEntityPartChanges(server, model, entity, entityToSave);
|
|
1065
1070
|
if (!changes && !options.operation) {
|
|
1066
1071
|
return entity;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IRpdServer } from "~/core/server";
|
|
2
2
|
import { RpdDataModel } from "~/types";
|
|
3
3
|
import { getEntityProperty, getEntityPropertyByCode, isRelationProperty } from "../helpers/metaHelper";
|
|
4
|
+
import { getDateString } from "~/utilities/timeUtility";
|
|
4
5
|
|
|
5
6
|
// TODO Generate mapper and cache it.
|
|
6
7
|
|
|
@@ -45,8 +46,18 @@ export function mapDbRowToEntity(server: IRpdServer, model: RpdDataModel, row: a
|
|
|
45
46
|
result[columnName] = row[columnName];
|
|
46
47
|
}
|
|
47
48
|
} else {
|
|
49
|
+
let fieldValue = row[columnName];
|
|
50
|
+
|
|
51
|
+
if (property) {
|
|
52
|
+
if (property.type === "date") {
|
|
53
|
+
if (fieldValue) {
|
|
54
|
+
fieldValue = getDateString(fieldValue);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
if (!result[propertyName]) {
|
|
49
|
-
result[propertyName] =
|
|
60
|
+
result[propertyName] = fieldValue;
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IRpdServer } from "~/core/server";
|
|
2
|
+
import { getEntityPropertyByCode, getEntityPropertyByFieldName } from "~/helpers/metaHelper";
|
|
3
|
+
import { RpdDataModel } from "~/types";
|
|
4
|
+
import { getDateString } from "~/utilities/timeUtility";
|
|
5
|
+
|
|
6
|
+
export async function validateEntity(server: IRpdServer, model: RpdDataModel, entity: any) {
|
|
7
|
+
for (const propCode in entity) {
|
|
8
|
+
let prop = getEntityPropertyByCode(server, model, propCode);
|
|
9
|
+
if (!prop) {
|
|
10
|
+
getEntityPropertyByFieldName(server, model, propCode);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!prop) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (prop.type === "date") {
|
|
18
|
+
const originValue = entity[propCode];
|
|
19
|
+
if (originValue) {
|
|
20
|
+
entity[propCode] = getDateString(originValue);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return entity;
|
|
26
|
+
}
|
|
@@ -53,6 +53,13 @@ export function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdD
|
|
|
53
53
|
return [...baseProperties, ...properties];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 根据 code 获取实体属性信息。如实体为派生实体,可能会返回基础实体中的属性信息。
|
|
58
|
+
* @param server
|
|
59
|
+
* @param model
|
|
60
|
+
* @param propertyCode
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
56
63
|
export function getEntityPropertyByCode(server: IRpdServer, model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined {
|
|
57
64
|
return getEntityProperty(server, model, (e) => e.code === propertyCode);
|
|
58
65
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import * as cron from "cron";
|
|
2
1
|
import type { RpdApplicationConfig } from "~/types";
|
|
3
2
|
import pluginActionHandlers from "./actionHandlers";
|
|
4
3
|
import pluginRoutes from "./routes";
|
|
5
|
-
import { CronJobConfiguration } from "~/types/cron-job-types";
|
|
6
4
|
import {
|
|
7
5
|
IRpdServer,
|
|
8
6
|
RapidPlugin,
|
|
@@ -10,13 +8,11 @@ import {
|
|
|
10
8
|
RpdServerPluginConfigurableTargetOptions,
|
|
11
9
|
RpdServerPluginExtendingAbilities,
|
|
12
10
|
} from "~/core/server";
|
|
13
|
-
import
|
|
14
|
-
import { find } from "lodash";
|
|
15
|
-
import { validateLicense } from "~/helpers/licenseHelper";
|
|
16
|
-
import { RouteContext } from "~/core/routeContext";
|
|
11
|
+
import CronJobService from "./services/CronJobService";
|
|
17
12
|
|
|
18
13
|
class CronJobPlugin implements RapidPlugin {
|
|
19
14
|
#server: IRpdServer;
|
|
15
|
+
#cronJobService!: CronJobService;
|
|
20
16
|
|
|
21
17
|
constructor() {}
|
|
22
18
|
|
|
@@ -64,6 +60,11 @@ class CronJobPlugin implements RapidPlugin {
|
|
|
64
60
|
|
|
65
61
|
async configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {}
|
|
66
62
|
|
|
63
|
+
async configureServices(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
64
|
+
this.#cronJobService = new CronJobService(server);
|
|
65
|
+
server.registerService("cronJobService", this.#cronJobService);
|
|
66
|
+
}
|
|
67
|
+
|
|
67
68
|
async configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
68
69
|
server.appendApplicationConfig({ routes: pluginRoutes });
|
|
69
70
|
}
|
|
@@ -71,54 +72,7 @@ class CronJobPlugin implements RapidPlugin {
|
|
|
71
72
|
async onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {}
|
|
72
73
|
|
|
73
74
|
async onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
74
|
-
|
|
75
|
-
for (const job of cronJobs) {
|
|
76
|
-
const jobInstance = cron.CronJob.from({
|
|
77
|
-
...(job.jobOptions || {}),
|
|
78
|
-
cronTime: job.cronTime,
|
|
79
|
-
onTick: async () => {
|
|
80
|
-
await this.tryExecuteJob(server, job);
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
jobInstance.start();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
getJobConfigurationByCode(code: string) {
|
|
88
|
-
return find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async tryExecuteJob(server: IRpdServer, job: CronJobConfiguration) {
|
|
92
|
-
const logger = server.getLogger();
|
|
93
|
-
logger.info(`Executing cron job '${job.code}'...`);
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
await this.executeJob(server, job);
|
|
97
|
-
logger.info(`Completed cron job '${job.code}'...`);
|
|
98
|
-
} catch (ex: any) {
|
|
99
|
-
logger.error('Cron job "%s" execution error: %s', job.code, ex.message, { cronJobCode: job.code });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async executeJob(server: IRpdServer, job: CronJobConfiguration) {
|
|
104
|
-
const logger = server.getLogger();
|
|
105
|
-
validateLicense(server);
|
|
106
|
-
|
|
107
|
-
let handlerContext: ActionHandlerContext = {
|
|
108
|
-
logger,
|
|
109
|
-
routerContext: RouteContext.newSystemOperationContext(server),
|
|
110
|
-
next: null,
|
|
111
|
-
server,
|
|
112
|
-
applicationConfig: null,
|
|
113
|
-
input: null,
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
if (job.actionHandlerCode) {
|
|
117
|
-
const actionHandler = server.getActionHandlerByCode(job.code);
|
|
118
|
-
await actionHandler(handlerContext, job.handleOptions);
|
|
119
|
-
} else {
|
|
120
|
-
await job.handler(handlerContext, job.handleOptions);
|
|
121
|
-
}
|
|
75
|
+
this.#cronJobService.reloadJobs();
|
|
122
76
|
}
|
|
123
77
|
}
|
|
124
78
|
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { CronJob } from "cron";
|
|
2
|
+
|
|
1
3
|
export type RunCronJobActionHandlerOptions = {
|
|
2
4
|
code?: string;
|
|
3
5
|
};
|
|
4
6
|
|
|
5
7
|
export type RunCronJobInput = {
|
|
6
8
|
code?: string;
|
|
9
|
+
input?: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type NamedCronJobInstance = {
|
|
13
|
+
code: string;
|
|
14
|
+
instance: CronJob;
|
|
7
15
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ActionHandlerContext } from "~/core/actionHandler";
|
|
2
2
|
import { RunCronJobActionHandlerOptions, RunCronJobInput } from "../CronJobPluginTypes";
|
|
3
3
|
import type CronJobPlugin from "../CronJobPlugin";
|
|
4
|
+
import CronJobService from "../services/CronJobService";
|
|
4
5
|
|
|
5
6
|
export const code = "runCronJob";
|
|
6
7
|
|
|
@@ -18,12 +19,21 @@ export async function handler(plugin: CronJobPlugin, ctx: ActionHandlerContext,
|
|
|
18
19
|
throw new Error(`Cron job code is required.`);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
+
const cronJobService = server.getService<CronJobService>("cronJobService");
|
|
23
|
+
const job = cronJobService.getJobConfigurationByCode(input.code);
|
|
22
24
|
if (!job) {
|
|
23
25
|
throw new Error(`Cron job with code '${input.code}' was not found.`);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
let jobExecutionContext: ActionHandlerContext = {
|
|
29
|
+
logger: server.getLogger(),
|
|
30
|
+
routerContext,
|
|
31
|
+
next: null,
|
|
32
|
+
server,
|
|
33
|
+
applicationConfig: null,
|
|
34
|
+
input: input.input,
|
|
35
|
+
};
|
|
36
|
+
await cronJobService.executeJob(jobExecutionContext, job);
|
|
27
37
|
|
|
28
38
|
response.json({});
|
|
29
39
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { CronJob } from "cron";
|
|
2
|
+
import { IRpdServer } from "~/core/server";
|
|
3
|
+
import { find } from "lodash";
|
|
4
|
+
import { RouteContext } from "~/core/routeContext";
|
|
5
|
+
import { validateLicense } from "~/helpers/licenseHelper";
|
|
6
|
+
import { NamedCronJobInstance } from "../CronJobPluginTypes";
|
|
7
|
+
import { CronJobConfiguration } from "~/types/cron-job-types";
|
|
8
|
+
import { ActionHandlerContext } from "~/core/actionHandler";
|
|
9
|
+
|
|
10
|
+
export default class CronJobService {
|
|
11
|
+
#server: IRpdServer;
|
|
12
|
+
#jobInstances: NamedCronJobInstance[];
|
|
13
|
+
|
|
14
|
+
constructor(server: IRpdServer) {
|
|
15
|
+
this.#server = server;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 根据编号获取定时任务配置信息
|
|
20
|
+
* @param code
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
getJobConfigurationByCode(code: string) {
|
|
24
|
+
return find(this.#server.listCronJobs(), (job) => job.code === code);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 重新加载定时任务
|
|
29
|
+
*/
|
|
30
|
+
reloadJobs() {
|
|
31
|
+
const server = this.#server;
|
|
32
|
+
|
|
33
|
+
if (this.#jobInstances) {
|
|
34
|
+
for (const job of this.#jobInstances) {
|
|
35
|
+
job.instance.stop();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.#jobInstances = [];
|
|
40
|
+
|
|
41
|
+
const cronJobs = server.listCronJobs();
|
|
42
|
+
for (const job of cronJobs) {
|
|
43
|
+
if (job.disabled) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const jobInstance = CronJob.from({
|
|
48
|
+
...(job.jobOptions || {}),
|
|
49
|
+
cronTime: job.cronTime,
|
|
50
|
+
onTick: async () => {
|
|
51
|
+
await this.tryExecuteJob(job);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
jobInstance.start();
|
|
55
|
+
|
|
56
|
+
this.#jobInstances.push({
|
|
57
|
+
code: job.code,
|
|
58
|
+
instance: jobInstance,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async tryExecuteJob(job: CronJobConfiguration) {
|
|
64
|
+
const server = this.#server;
|
|
65
|
+
const logger = server.getLogger();
|
|
66
|
+
logger.info(`Executing cron job '${job.code}'...`);
|
|
67
|
+
|
|
68
|
+
let handlerContext: ActionHandlerContext = {
|
|
69
|
+
logger,
|
|
70
|
+
routerContext: RouteContext.newSystemOperationContext(server),
|
|
71
|
+
next: null,
|
|
72
|
+
server,
|
|
73
|
+
applicationConfig: null,
|
|
74
|
+
input: null,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await this.executeJob(handlerContext, job);
|
|
79
|
+
logger.info(`Completed cron job '${job.code}'...`);
|
|
80
|
+
} catch (ex: any) {
|
|
81
|
+
logger.error('Cron job "%s" execution error: %s', job.code, ex.message);
|
|
82
|
+
|
|
83
|
+
if (job.onError) {
|
|
84
|
+
try {
|
|
85
|
+
await job.onError(handlerContext, ex);
|
|
86
|
+
} catch (ex) {
|
|
87
|
+
logger.error('Error handler of cron job "%s" execution failed: %s', job.code, ex.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 执行指定任务
|
|
95
|
+
* @param job
|
|
96
|
+
*/
|
|
97
|
+
async executeJob(handlerContext: ActionHandlerContext, job: CronJobConfiguration) {
|
|
98
|
+
const server = this.#server;
|
|
99
|
+
validateLicense(server);
|
|
100
|
+
|
|
101
|
+
if (job.actionHandlerCode) {
|
|
102
|
+
const actionHandler = server.getActionHandlerByCode(job.code);
|
|
103
|
+
await actionHandler(handlerContext, job.handleOptions);
|
|
104
|
+
} else {
|
|
105
|
+
await job.handler(handlerContext, job.handleOptions);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -11,6 +11,11 @@ export interface CronJobConfiguration {
|
|
|
11
11
|
*/
|
|
12
12
|
description?: string;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 是否禁用
|
|
16
|
+
*/
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* crontab 表达式
|
|
16
21
|
*/
|
|
@@ -38,6 +43,14 @@ export interface CronJobConfiguration {
|
|
|
38
43
|
* 处理定时任务时的设置选项
|
|
39
44
|
*/
|
|
40
45
|
handleOptions?: any;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 定时任务执行失败时的处理
|
|
49
|
+
* @param ctx
|
|
50
|
+
* @param error
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
onError?: (ctx: ActionHandlerContext, error: any) => Promise<void>;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
export interface CronJobOptions {
|