mirror of
https://github.com/whyour/qinglong.git
synced 2026-07-01 04:40:38 +08:00
定时任务支持 @once 和 @boot 任务
This commit is contained in:
+11
-37
@@ -4,7 +4,8 @@ import { Logger } from 'winston';
|
||||
import CronService from '../services/cron';
|
||||
import CronViewService from '../services/cronView';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import cron_parser from 'cron-parser';
|
||||
import { commonCronSchema } from '../validation/schedule';
|
||||
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
@@ -170,27 +171,14 @@ export default (app: Router) => {
|
||||
route.post(
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string().optional(),
|
||||
labels: Joi.array().optional(),
|
||||
sub_id: Joi.number().optional().allow(null),
|
||||
extra_schedules: Joi.array().optional().allow(null),
|
||||
task_before: Joi.string().optional().allow('').allow(null),
|
||||
task_after: Joi.string().optional().allow('').allow(null),
|
||||
}),
|
||||
body: Joi.object(commonCronSchema),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
if (cron_parser.parseExpression(req.body.schedule).hasNext()) {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.create(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} else {
|
||||
return res.send({ code: 400, message: 'param schedule error' });
|
||||
}
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.create(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
@@ -331,30 +319,16 @@ export default (app: Router) => {
|
||||
'/',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
labels: Joi.array().optional().allow(null),
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string().optional().allow(null),
|
||||
sub_id: Joi.number().optional().allow(null),
|
||||
extra_schedules: Joi.array().optional().allow(null),
|
||||
task_before: Joi.string().optional().allow('').allow(null),
|
||||
task_after: Joi.string().optional().allow('').allow(null),
|
||||
...commonCronSchema,
|
||||
id: Joi.number().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
if (
|
||||
!req.body.schedule ||
|
||||
cron_parser.parseExpression(req.body.schedule).hasNext()
|
||||
) {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.update(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} else {
|
||||
return res.send({ code: 400, message: 'param schedule error' });
|
||||
}
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.update(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
@@ -418,7 +392,7 @@ export default (app: Router) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.import_crontab();
|
||||
const data = await cronService.importCrontab();
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
|
||||
@@ -17,6 +17,7 @@ async function startServer() {
|
||||
Logger.debug(`✌️ 后端服务启动成功!`);
|
||||
console.debug(`✌️ 后端服务启动成功!`);
|
||||
process.send?.('ready');
|
||||
require('./loaders/bootAfter').default();
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import Container from 'typedi';
|
||||
import CronService from '../services/cron';
|
||||
|
||||
export default async () => {
|
||||
const cronService = Container.get(CronService);
|
||||
|
||||
await cronService.bootTask();
|
||||
};
|
||||
+55
-15
@@ -35,11 +35,24 @@ export default class CronService {
|
||||
return false;
|
||||
}
|
||||
|
||||
private isOnceSchedule(schedule?: string) {
|
||||
return schedule?.startsWith('@once');
|
||||
}
|
||||
|
||||
private isBootSchedule(schedule?: string) {
|
||||
return schedule?.startsWith('@boot');
|
||||
}
|
||||
|
||||
private isSpecialSchedule(schedule?: string) {
|
||||
return this.isOnceSchedule(schedule) || this.isBootSchedule(schedule);
|
||||
}
|
||||
|
||||
public async create(payload: Crontab): Promise<Crontab> {
|
||||
const tab = new Crontab(payload);
|
||||
tab.saved = false;
|
||||
const doc = await this.insert(tab);
|
||||
if (this.isNodeCron(doc)) {
|
||||
|
||||
if (this.isNodeCron(doc) && !this.isSpecialSchedule(doc.schedule)) {
|
||||
await cronClient.addCron([
|
||||
{
|
||||
name: doc.name || '',
|
||||
@@ -50,7 +63,8 @@ export default class CronService {
|
||||
},
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
|
||||
await this.setCrontab();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -63,13 +77,16 @@ export default class CronService {
|
||||
const tab = new Crontab({ ...doc, ...payload });
|
||||
tab.saved = false;
|
||||
const newDoc = await this.updateDb(tab);
|
||||
|
||||
if (doc.isDisabled === 1) {
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
if (this.isNodeCron(doc)) {
|
||||
await cronClient.delCron([String(doc.id)]);
|
||||
}
|
||||
if (this.isNodeCron(newDoc)) {
|
||||
|
||||
if (this.isNodeCron(newDoc) && !this.isSpecialSchedule(newDoc.schedule)) {
|
||||
await cronClient.addCron([
|
||||
{
|
||||
name: doc.name || '',
|
||||
@@ -80,7 +97,8 @@ export default class CronService {
|
||||
},
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
|
||||
await this.setCrontab();
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -135,7 +153,7 @@ export default class CronService {
|
||||
public async remove(ids: number[]) {
|
||||
await CrontabModel.destroy({ where: { id: ids } });
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
await this.setCrontab();
|
||||
}
|
||||
|
||||
public async pin(ids: number[]) {
|
||||
@@ -381,7 +399,7 @@ export default class CronService {
|
||||
try {
|
||||
const result = await CrontabModel.findAll(condition);
|
||||
const count = await CrontabModel.count({ where: query });
|
||||
return { data: result, total: count };
|
||||
return { data: result.map((x) => x.get({ plain: true })), total: count };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -502,7 +520,7 @@ export default class CronService {
|
||||
public async disabled(ids: number[]) {
|
||||
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
await this.setCrontab();
|
||||
}
|
||||
|
||||
public async enabled(ids: number[]) {
|
||||
@@ -518,7 +536,7 @@ export default class CronService {
|
||||
extra_schedules: doc.extra_schedules || [],
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
await this.set_crontab();
|
||||
await this.setCrontab();
|
||||
}
|
||||
|
||||
public async log(id: number) {
|
||||
@@ -586,7 +604,7 @@ export default class CronService {
|
||||
return crontab_job_string;
|
||||
}
|
||||
|
||||
private async set_crontab(data?: { data: Crontab[]; total: number }) {
|
||||
private async setCrontab(data?: { data: Crontab[]; total: number }) {
|
||||
const tabs = data ?? (await this.crontabs());
|
||||
var crontab_string = '';
|
||||
tabs.data.forEach((tab) => {
|
||||
@@ -594,7 +612,8 @@ export default class CronService {
|
||||
if (
|
||||
tab.isDisabled === 1 ||
|
||||
_schedule!.length !== 5 ||
|
||||
tab.extra_schedules?.length
|
||||
tab.extra_schedules?.length ||
|
||||
this.isSpecialSchedule(tab.schedule)
|
||||
) {
|
||||
crontab_string += '# ';
|
||||
crontab_string += tab.schedule;
|
||||
@@ -615,7 +634,7 @@ export default class CronService {
|
||||
await CrontabModel.update({ saved: true }, { where: {} });
|
||||
}
|
||||
|
||||
public import_crontab() {
|
||||
public importCrontab() {
|
||||
exec('crontab -l', (error, stdout, stderr) => {
|
||||
const lines = stdout.split('\n');
|
||||
const namePrefix = new Date().getTime();
|
||||
@@ -651,10 +670,15 @@ export default class CronService {
|
||||
|
||||
public async autosave_crontab() {
|
||||
const tabs = await this.crontabs();
|
||||
this.set_crontab(tabs);
|
||||
this.setCrontab(tabs);
|
||||
|
||||
const sixCron = tabs.data
|
||||
.filter((x) => this.isNodeCron(x) && x.isDisabled !== 1)
|
||||
const regularCrons = tabs.data
|
||||
.filter(
|
||||
(x) =>
|
||||
this.isNodeCron(x) &&
|
||||
x.isDisabled !== 1 &&
|
||||
!this.isSpecialSchedule(x.schedule),
|
||||
)
|
||||
.map((doc) => ({
|
||||
name: doc.name || '',
|
||||
id: String(doc.id),
|
||||
@@ -662,6 +686,22 @@ export default class CronService {
|
||||
command: this.makeCommand(doc),
|
||||
extra_schedules: doc.extra_schedules || [],
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
await cronClient.addCron(regularCrons);
|
||||
}
|
||||
|
||||
public async bootTask() {
|
||||
const tabs = await this.crontabs();
|
||||
const bootTasks = tabs.data.filter(
|
||||
(x) => !x.isDisabled && this.isBootSchedule(x.schedule),
|
||||
);
|
||||
if (bootTasks.length > 0) {
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.queued },
|
||||
{ where: { id: bootTasks.map((t) => t.id!) } },
|
||||
);
|
||||
for (const task of bootTasks) {
|
||||
await this.runSingle(task.id!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Joi } from 'celebrate';
|
||||
import cron_parser from 'cron-parser';
|
||||
|
||||
const validateSchedule = (value: string, helpers: any) => {
|
||||
if (value.startsWith('@once') || value.startsWith('@boot')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
if (cron_parser.parseExpression(value).hasNext()) {
|
||||
return value;
|
||||
}
|
||||
} catch (e) {
|
||||
return helpers.error('any.invalid');
|
||||
}
|
||||
return helpers.error('any.invalid');
|
||||
};
|
||||
|
||||
export const scheduleSchema = Joi.string()
|
||||
.required()
|
||||
.custom(validateSchedule)
|
||||
.messages({
|
||||
'any.invalid': '无效的定时规则',
|
||||
'string.empty': '定时规则不能为空',
|
||||
});
|
||||
|
||||
export const commonCronSchema = {
|
||||
name: Joi.string().optional(),
|
||||
command: Joi.string().required(),
|
||||
schedule: scheduleSchema,
|
||||
labels: Joi.array().optional(),
|
||||
sub_id: Joi.number().optional().allow(null),
|
||||
extra_schedules: Joi.array().optional().allow(null),
|
||||
task_before: Joi.string().optional().allow('').allow(null),
|
||||
task_after: Joi.string().optional().allow('').allow(null),
|
||||
};
|
||||
Reference in New Issue
Block a user