feat(application): cron job in application (#2730)

This commit is contained in:
ChengLei Shao 2023-09-27 19:31:14 +08:00 committed by GitHub
parent ae988d00b0
commit ce879d2dda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 5 deletions

View File

@ -44,7 +44,8 @@
"semver": "^7.3.7", "semver": "^7.3.7",
"serve-handler": "^6.1.5", "serve-handler": "^6.1.5",
"ws": "^8.13.0", "ws": "^8.13.0",
"xpipe": "^1.0.5" "xpipe": "^1.0.5",
"cron": "^2.4.4"
}, },
"devDependencies": { "devDependencies": {
"@types/semver": "^7.3.9", "@types/semver": "^7.3.9",

View File

@ -0,0 +1,58 @@
import { mockServer, MockServer, waitSecond } from '@nocobase/test';
import { CronJobManager } from '../cron/cron-job-manager';
describe('cron service', () => {
let app: MockServer;
beforeEach(async () => {
app = mockServer();
});
afterEach(async () => {
await app.destroy();
});
it('should get cron job manager', async () => {
const cron = app.cronJobManager;
expect(cron).toBeInstanceOf(CronJobManager);
});
it('should get new cron instance when app reload', async () => {
const cron1 = app.cronJobManager;
expect(cron1).toBeDefined();
cron1.start();
expect(cron1.started).toBeTruthy();
await app.reload();
expect(cron1.started).toBeFalsy();
const cron2 = app.cronJobManager;
expect(cron2).toBeDefined();
expect(cron1).not.toBe(cron2);
});
it('should add cron job', async () => {
const cronManager = app.cronJobManager;
const jestFn = jest.fn();
cronManager.addJob({
cronTime: '* * * * * *',
onTick: jestFn,
});
expect(jestFn).not.toBeCalled();
cronManager.start();
await waitSecond(2000);
expect(jestFn).toBeCalledTimes(2);
});
it('should remove cron job', async () => {
const cronManager = app.cronJobManager;
const jestFn = jest.fn();
const job = cronManager.addJob({
cronTime: '* * * * * *',
onTick: jestFn,
});
expect(cronManager.jobs.size).toBe(1);
cronManager.removeJob(job);
expect(cronManager.jobs.size).toBe(0);
});
});

View File

@ -23,6 +23,8 @@ import { ApplicationVersion } from './helpers/application-version';
import { Locale } from './locale'; import { Locale } from './locale';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { InstallOptions, PluginManager } from './plugin-manager'; import { InstallOptions, PluginManager } from './plugin-manager';
import { CronJob } from 'cron';
import { CronJobManager } from './cron/cron-job-manager';
const packageJson = require('../package.json'); const packageJson = require('../package.json');
@ -141,6 +143,12 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
return this._maintainingMessage; return this._maintainingMessage;
} }
protected _cronJobManager: CronJobManager;
get cronJobManager() {
return this._cronJobManager;
}
protected _db: Database; protected _db: Database;
get db() { get db() {
@ -331,6 +339,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this._loaded = false; this._loaded = false;
await this.emitAsync('beforeReload', this, options);
await this.load({ await this.load({
...options, ...options,
reload: true, reload: true,
@ -672,6 +682,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this.plugins = new Map<string, Plugin>(); this.plugins = new Map<string, Plugin>();
this._acl = createACL(); this._acl = createACL();
this._cronJobManager = new CronJobManager(this);
this.use(logger.middleware, { tag: 'logger' }); this.use(logger.middleware, { tag: 'logger' });
if (this._db) { if (this._db) {

View File

@ -0,0 +1,56 @@
import { CronJob, CronJobParameters } from 'cron';
import Application from '../application';
export class CronJobManager {
private _jobs: Set<CronJob> = new Set();
private _started = false;
constructor(private app: Application) {
app.on('beforeStop', async () => {
this.stop();
});
app.on('afterStart', async () => {
this.start();
});
app.on('beforeReload', async () => {
this.stop();
});
}
public get started() {
return this._started;
}
public get jobs() {
return this._jobs;
}
public addJob(options: CronJobParameters) {
const cronJob = new CronJob(options);
this._jobs.add(cronJob);
return cronJob;
}
public removeJob(job: CronJob) {
job.stop();
this._jobs.delete(job);
}
public start() {
this._jobs.forEach((job) => {
job.start();
});
this._started = true;
}
public stop() {
this._jobs.forEach((job) => {
job.stop();
});
this._started = false;
}
}

View File

@ -11,8 +11,8 @@ export function randomStr() {
return Math.random().toString(36).substring(2); return Math.random().toString(36).substring(2);
} }
export const waitSecond = async () => { export const waitSecond = async (timeout = 1000) => {
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, timeout));
}; };
export const startServerWithRandomPort = async (startServer) => { export const startServerWithRandomPort = async (startServer) => {

View File

@ -5906,6 +5906,11 @@
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.3.0.tgz#a61043a62c0a72696c73a0a305c544c96501e006" resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.3.0.tgz#a61043a62c0a72696c73a0a305c544c96501e006"
"@types/luxon@~3.3.0":
version "3.3.2"
resolved "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz#f6e3524c2486b949a4db445e85d93c8e9886dfe2"
integrity sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==
"@types/markdown-it-highlightjs@3.3.1": "@types/markdown-it-highlightjs@3.3.1":
version "3.3.1" version "3.3.1"
resolved "https://registry.npmmirror.com/@types/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#1e051211e7754ba478449fea7faeab3d177ca892" resolved "https://registry.npmmirror.com/@types/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#1e051211e7754ba478449fea7faeab3d177ca892"
@ -9433,6 +9438,14 @@ cron@^2.3.1:
dependencies: dependencies:
luxon "^3.2.1" luxon "^3.2.1"
cron@^2.4.4:
version "2.4.4"
resolved "https://registry.npmjs.org/cron/-/cron-2.4.4.tgz#988c1757b3f288d1dfcc360ee6d80087448916dc"
integrity sha512-MHlPImXJj3K7x7lyUHjtKEOl69CSlTOWxS89jiFgNkzXfvhVjhMz/nc7/EIfN9vgooZp8XTtXJ1FREdmbyXOiQ==
dependencies:
"@types/luxon" "~3.3.0"
luxon "~3.3.0"
croner@~4.1.92: croner@~4.1.92:
version "4.1.97" version "4.1.97"
resolved "https://registry.npmmirror.com/croner/-/croner-4.1.97.tgz#6e373dc7bb3026fab2deb0d82685feef20796766" resolved "https://registry.npmmirror.com/croner/-/croner-4.1.97.tgz#6e373dc7bb3026fab2deb0d82685feef20796766"
@ -15884,7 +15897,7 @@ luxon@^1.28.0:
version "1.28.1" version "1.28.1"
resolved "https://registry.npmmirror.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0" resolved "https://registry.npmmirror.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0"
luxon@^3.2.1: luxon@^3.2.1, luxon@~3.3.0:
version "3.3.0" version "3.3.0"
resolved "https://registry.npmmirror.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48" resolved "https://registry.npmmirror.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
@ -19211,7 +19224,7 @@ qrcode.react@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.npmmirror.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" resolved "https://registry.npmmirror.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8"
qs@^6.10.1, qs@^6.11.0, qs@^6.4.0, qs@^6.5.2, qs@^6.9.4: qs@^6.10.1, qs@^6.11.0, qs@^6.11.2, qs@^6.4.0, qs@^6.5.2, qs@^6.9.4:
version "6.11.2" version "6.11.2"
resolved "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" resolved "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
dependencies: dependencies: