mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
修改任务队列执行日志
This commit is contained in:
parent
9d55cb108c
commit
ec5b885476
|
@ -65,7 +65,7 @@ export default (app: Router) => {
|
||||||
};
|
};
|
||||||
const filePath = join(config.logPath, path, filename);
|
const filePath = join(config.logPath, path, filename);
|
||||||
if (type === 'directory') {
|
if (type === 'directory') {
|
||||||
emptyDir(filePath);
|
await emptyDir(filePath);
|
||||||
} else {
|
} else {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ export default (app: Router) => {
|
||||||
};
|
};
|
||||||
const filePath = join(config.scriptPath, path, filename);
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
if (type === 'directory') {
|
if (type === 'directory') {
|
||||||
emptyDir(filePath);
|
await emptyDir(filePath);
|
||||||
} else {
|
} else {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
|
@ -260,7 +260,6 @@ export default (app: Router) => {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger: Logger = Container.get('logger');
|
|
||||||
try {
|
try {
|
||||||
let { filename, path, pid } = req.body;
|
let { filename, path, pid } = req.body;
|
||||||
const { name, ext } = parse(filename);
|
const { name, ext } = parse(filename);
|
||||||
|
@ -269,7 +268,9 @@ export default (app: Router) => {
|
||||||
|
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.stopScript(filePath, pid);
|
const result = await scriptService.stopScript(filePath, pid);
|
||||||
emptyDir(logPath);
|
setTimeout(() => {
|
||||||
|
emptyDir(logPath);
|
||||||
|
}, 3000);
|
||||||
res.send(result);
|
res.send(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(e);
|
return next(e);
|
||||||
|
|
|
@ -298,13 +298,17 @@ export function readDir(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyDir(path: string) {
|
export async function emptyDir(path: string) {
|
||||||
|
const pathExist = await fileExist(path);
|
||||||
|
if (!pathExist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const files = fs.readdirSync(path);
|
const files = fs.readdirSync(path);
|
||||||
files.forEach((file) => {
|
files.forEach(async (file) => {
|
||||||
const filePath = `${path}/${file}`;
|
const filePath = `${path}/${file}`;
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
emptyDir(filePath);
|
await emptyDir(filePath);
|
||||||
} else {
|
} else {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ message ICron {
|
||||||
string schedule = 2;
|
string schedule = 2;
|
||||||
string command = 3;
|
string command = 3;
|
||||||
repeated ISchedule extra_schedules = 4;
|
repeated ISchedule extra_schedules = 4;
|
||||||
|
string name = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddCronRequest { repeated ICron crons = 1; }
|
message AddCronRequest { repeated ICron crons = 1; }
|
||||||
|
|
|
@ -24,6 +24,7 @@ export interface ICron {
|
||||||
schedule: string;
|
schedule: string;
|
||||||
command: string;
|
command: string;
|
||||||
extraSchedules: ISchedule[];
|
extraSchedules: ISchedule[];
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddCronRequest {
|
export interface AddCronRequest {
|
||||||
|
@ -97,7 +98,7 @@ export const ISchedule = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function createBaseICron(): ICron {
|
function createBaseICron(): ICron {
|
||||||
return { id: "", schedule: "", command: "", extraSchedules: [] };
|
return { id: "", schedule: "", command: "", extraSchedules: [], name: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ICron = {
|
export const ICron = {
|
||||||
|
@ -114,6 +115,9 @@ export const ICron = {
|
||||||
for (const v of message.extraSchedules) {
|
for (const v of message.extraSchedules) {
|
||||||
ISchedule.encode(v!, writer.uint32(34).fork()).ldelim();
|
ISchedule.encode(v!, writer.uint32(34).fork()).ldelim();
|
||||||
}
|
}
|
||||||
|
if (message.name !== "") {
|
||||||
|
writer.uint32(42).string(message.name);
|
||||||
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -152,6 +156,13 @@ export const ICron = {
|
||||||
|
|
||||||
message.extraSchedules.push(ISchedule.decode(reader, reader.uint32()));
|
message.extraSchedules.push(ISchedule.decode(reader, reader.uint32()));
|
||||||
continue;
|
continue;
|
||||||
|
case 5:
|
||||||
|
if (tag !== 42) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.name = reader.string();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if ((tag & 7) === 4 || tag === 0) {
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
break;
|
break;
|
||||||
|
@ -169,6 +180,7 @@ export const ICron = {
|
||||||
extraSchedules: Array.isArray(object?.extraSchedules)
|
extraSchedules: Array.isArray(object?.extraSchedules)
|
||||||
? object.extraSchedules.map((e: any) => ISchedule.fromJSON(e))
|
? object.extraSchedules.map((e: any) => ISchedule.fromJSON(e))
|
||||||
: [],
|
: [],
|
||||||
|
name: isSet(object.name) ? String(object.name) : "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -182,6 +194,7 @@ export const ICron = {
|
||||||
} else {
|
} else {
|
||||||
obj.extraSchedules = [];
|
obj.extraSchedules = [];
|
||||||
}
|
}
|
||||||
|
message.name !== undefined && (obj.name = message.name);
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -195,6 +208,7 @@ export const ICron = {
|
||||||
message.schedule = object.schedule ?? "";
|
message.schedule = object.schedule ?? "";
|
||||||
message.command = object.command ?? "";
|
message.command = object.command ?? "";
|
||||||
message.extraSchedules = object.extraSchedules?.map((e) => ISchedule.fromPartial(e)) || [];
|
message.extraSchedules = object.extraSchedules?.map((e) => ISchedule.fromPartial(e)) || [];
|
||||||
|
message.name = object.name ?? "";
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { AddCronRequest, AddCronResponse } from '../protos/cron';
|
||||||
import nodeSchedule from 'node-schedule';
|
import nodeSchedule from 'node-schedule';
|
||||||
import { scheduleStacks } from './data';
|
import { scheduleStacks } from './data';
|
||||||
import { runCron } from '../shared/runCron';
|
import { runCron } from '../shared/runCron';
|
||||||
import { QL_PREFIX, TASK_PREFIX } from '../config/const';
|
|
||||||
import Logger from '../loaders/logger';
|
import Logger from '../loaders/logger';
|
||||||
|
|
||||||
const addCron = (
|
const addCron = (
|
||||||
|
@ -11,14 +10,15 @@ const addCron = (
|
||||||
callback: sendUnaryData<AddCronResponse>,
|
callback: sendUnaryData<AddCronResponse>,
|
||||||
) => {
|
) => {
|
||||||
for (const item of call.request.crons) {
|
for (const item of call.request.crons) {
|
||||||
const { id, schedule, command, extraSchedules } = item;
|
const { id, schedule, command, extraSchedules, name } = item;
|
||||||
if (scheduleStacks.has(id)) {
|
if (scheduleStacks.has(id)) {
|
||||||
scheduleStacks.get(id)?.forEach((x) => x.cancel());
|
scheduleStacks.get(id)?.forEach((x) => x.cancel());
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(
|
Logger.info(
|
||||||
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
|
'[schedule][创建定时任务], 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s',
|
||||||
id,
|
id,
|
||||||
|
name,
|
||||||
schedule,
|
schedule,
|
||||||
command,
|
command,
|
||||||
);
|
);
|
||||||
|
@ -26,8 +26,9 @@ const addCron = (
|
||||||
if (extraSchedules?.length) {
|
if (extraSchedules?.length) {
|
||||||
extraSchedules.forEach(x => {
|
extraSchedules.forEach(x => {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
'[schedule][创建定时任务], 任务ID: %s, cron: %s, 执行命令: %s',
|
'[schedule][创建定时任务], 任务ID: %s, 名称: %s, cron: %s, 执行命令: %s',
|
||||||
id,
|
id,
|
||||||
|
name,
|
||||||
x.schedule,
|
x.schedule,
|
||||||
command,
|
command,
|
||||||
);
|
);
|
||||||
|
@ -37,13 +38,13 @@ const addCron = (
|
||||||
scheduleStacks.set(id, [
|
scheduleStacks.set(id, [
|
||||||
nodeSchedule.scheduleJob(id, schedule, async () => {
|
nodeSchedule.scheduleJob(id, schedule, async () => {
|
||||||
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
|
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
|
||||||
runCron(command);
|
runCron(command, { name, schedule, extraSchedules });
|
||||||
}),
|
}),
|
||||||
...(extraSchedules?.length
|
...(extraSchedules?.length
|
||||||
? extraSchedules.map((x) =>
|
? extraSchedules.map((x) =>
|
||||||
nodeSchedule.scheduleJob(id, x.schedule, async () => {
|
nodeSchedule.scheduleJob(id, x.schedule, async () => {
|
||||||
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
|
Logger.info(`[schedule][准备运行任务] 命令: ${command}`);
|
||||||
runCron(command);
|
runCron(command, { name, schedule, extraSchedules });
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
: []),
|
: []),
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default class CronService {
|
||||||
const doc = await this.insert(tab);
|
const doc = await this.insert(tab);
|
||||||
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
|
if (this.isSixCron(doc) || doc.extra_schedules?.length) {
|
||||||
await cronClient.addCron([
|
await cronClient.addCron([
|
||||||
{ id: String(doc.id), schedule: doc.schedule!, command: this.makeCommand(doc), extraSchedules: doc.extra_schedules || [] },
|
{ name: doc.name || '', id: String(doc.id), schedule: doc.schedule!, command: this.makeCommand(doc), extraSchedules: doc.extra_schedules || [] },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await this.set_crontab();
|
await this.set_crontab();
|
||||||
|
@ -60,6 +60,7 @@ export default class CronService {
|
||||||
if (this.isSixCron(newDoc) || newDoc.extra_schedules?.length) {
|
if (this.isSixCron(newDoc) || newDoc.extra_schedules?.length) {
|
||||||
await cronClient.addCron([
|
await cronClient.addCron([
|
||||||
{
|
{
|
||||||
|
name: doc.name || '',
|
||||||
id: String(newDoc.id),
|
id: String(newDoc.id),
|
||||||
schedule: newDoc.schedule!,
|
schedule: newDoc.schedule!,
|
||||||
command: this.makeCommand(newDoc),
|
command: this.makeCommand(newDoc),
|
||||||
|
@ -391,15 +392,18 @@ export default class CronService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runSingle(cronId: number): Promise<number> {
|
private async runSingle(cronId: number): Promise<number | void> {
|
||||||
return taskLimit.runWithCpuLimit(() => {
|
return taskLimit.runWithCronLimit(() => {
|
||||||
return new Promise(async (resolve: any) => {
|
return new Promise(async (resolve: any) => {
|
||||||
const cron = await this.getDb({ id: cronId });
|
const cron = await this.getDb({ id: cronId });
|
||||||
|
const params = { name: cron.name, command: cron.command, schedule: cron.schedule, extraSchedules: cron.extra_schedules };
|
||||||
if (cron.status !== CrontabStatus.queued) {
|
if (cron.status !== CrontabStatus.queued) {
|
||||||
resolve();
|
resolve(params);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.info(`[panel][开始执行任务] 参数 ${JSON.stringify(params)}`);
|
||||||
|
|
||||||
let { id, command, log_path } = cron;
|
let { id, command, log_path } = cron;
|
||||||
const uniqPath = await getUniqPath(command, `${id}`);
|
const uniqPath = await getUniqPath(command, `${id}`);
|
||||||
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
|
||||||
|
@ -410,10 +414,6 @@ export default class CronService {
|
||||||
const logPath = `${uniqPath}/${logTime}.log`;
|
const logPath = `${uniqPath}/${logTime}.log`;
|
||||||
const absolutePath = path.resolve(config.logPath, `${logPath}`);
|
const absolutePath = path.resolve(config.logPath, `${logPath}`);
|
||||||
|
|
||||||
this.logger.silly('Running job');
|
|
||||||
this.logger.silly('ID: ' + id);
|
|
||||||
this.logger.silly('Original command: ' + command);
|
|
||||||
|
|
||||||
const cp = spawn(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron)}`, { shell: '/bin/bash' });
|
const cp = spawn(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron)}`, { shell: '/bin/bash' });
|
||||||
|
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
|
@ -427,17 +427,12 @@ export default class CronService {
|
||||||
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
|
fs.appendFileSync(`${absolutePath}`, `${JSON.stringify(err)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('exit', async (code, signal) => {
|
|
||||||
this.logger.info(
|
|
||||||
`[panel][任务退出] 任务 ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
{ status: CrontabStatus.idle, pid: undefined },
|
{ status: CrontabStatus.idle, pid: undefined },
|
||||||
{ where: { id } },
|
{ where: { id } },
|
||||||
);
|
);
|
||||||
resolve();
|
resolve({ ...params, pid: cp.pid, code });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -455,6 +450,7 @@ export default class CronService {
|
||||||
const sixCron = docs
|
const sixCron = docs
|
||||||
.filter((x) => this.isSixCron(x))
|
.filter((x) => this.isSixCron(x))
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
|
name: doc.name || '',
|
||||||
id: String(doc.id),
|
id: String(doc.id),
|
||||||
schedule: doc.schedule!,
|
schedule: doc.schedule!,
|
||||||
command: this.makeCommand(doc),
|
command: this.makeCommand(doc),
|
||||||
|
@ -586,6 +582,7 @@ export default class CronService {
|
||||||
const sixCron = tabs.data
|
const sixCron = tabs.data
|
||||||
.filter((x) => this.isSixCron(x) && x.isDisabled !== 1)
|
.filter((x) => this.isSixCron(x) && x.isDisabled !== 1)
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
|
name: doc.name || '',
|
||||||
id: String(doc.id),
|
id: String(doc.id),
|
||||||
schedule: doc.schedule!,
|
schedule: doc.schedule!,
|
||||||
command: this.makeCommand(doc),
|
command: this.makeCommand(doc),
|
||||||
|
|
|
@ -42,15 +42,22 @@ export default class ScheduleService {
|
||||||
|
|
||||||
private maxBuffer = 200 * 1024 * 1024;
|
private maxBuffer = 200 * 1024 * 1024;
|
||||||
|
|
||||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||||
|
|
||||||
async runTask(
|
async runTask(
|
||||||
command: string,
|
command: string,
|
||||||
callbacks: TaskCallbacks = {},
|
callbacks: TaskCallbacks = {},
|
||||||
|
params: {
|
||||||
|
schedule?: string;
|
||||||
|
name?: string;
|
||||||
|
command?: string;
|
||||||
|
},
|
||||||
completionTime: 'start' | 'end' = 'end',
|
completionTime: 'start' | 'end' = 'end',
|
||||||
) {
|
) {
|
||||||
return taskLimit.runWithCpuLimit(() => {
|
return taskLimit.runWithCronLimit(() => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
this.logger.info(`[panel][开始执行任务] 参数 ${JSON.stringify({ ...params, command })}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const startTime = dayjs();
|
const startTime = dayjs();
|
||||||
await callbacks.onBefore?.(startTime);
|
await callbacks.onBefore?.(startTime);
|
||||||
|
@ -82,12 +89,6 @@ export default class ScheduleService {
|
||||||
await callbacks.onError?.(JSON.stringify(err));
|
await callbacks.onError?.(JSON.stringify(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('exit', async (code, signal) => {
|
|
||||||
this.logger.info(
|
|
||||||
`[panel][任务退出] ${command} 进程id: ${cp.pid}, 退出码 ${code}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
const endTime = dayjs();
|
const endTime = dayjs();
|
||||||
await callbacks.onEnd?.(
|
await callbacks.onEnd?.(
|
||||||
|
@ -95,7 +96,7 @@ export default class ScheduleService {
|
||||||
endTime,
|
endTime,
|
||||||
endTime.diff(startTime, 'seconds'),
|
endTime.diff(startTime, 'seconds'),
|
||||||
);
|
);
|
||||||
resolve(null);
|
resolve({ ...params, pid: cp.pid, code });
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
@ -126,12 +127,20 @@ export default class ScheduleService {
|
||||||
this.scheduleStacks.set(
|
this.scheduleStacks.set(
|
||||||
_id,
|
_id,
|
||||||
nodeSchedule.scheduleJob(_id, schedule, async () => {
|
nodeSchedule.scheduleJob(_id, schedule, async () => {
|
||||||
this.runTask(command, callbacks);
|
this.runTask(command, callbacks, {
|
||||||
|
name,
|
||||||
|
schedule,
|
||||||
|
command,
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (runImmediately) {
|
if (runImmediately) {
|
||||||
this.runTask(command, callbacks);
|
this.runTask(command, callbacks, {
|
||||||
|
name,
|
||||||
|
schedule,
|
||||||
|
command,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +169,10 @@ export default class ScheduleService {
|
||||||
const task = new Task(
|
const task = new Task(
|
||||||
name,
|
name,
|
||||||
() => {
|
() => {
|
||||||
this.runTask(command, callbacks);
|
this.runTask(command, callbacks, {
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
@ -180,7 +192,10 @@ export default class ScheduleService {
|
||||||
this.intervalSchedule.addIntervalJob(job);
|
this.intervalSchedule.addIntervalJob(job);
|
||||||
|
|
||||||
if (runImmediately) {
|
if (runImmediately) {
|
||||||
this.runTask(command, callbacks);
|
this.runTask(command, callbacks, {
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,14 @@ export default class ScriptService {
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
private cronService: CronService,
|
private cronService: CronService,
|
||||||
private scheduleService: ScheduleService,
|
private scheduleService: ScheduleService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
private taskCallbacks(filePath: string): TaskCallbacks {
|
private taskCallbacks(filePath: string): TaskCallbacks {
|
||||||
return {
|
return {
|
||||||
onEnd: async (cp, endTime, diff) => {
|
onEnd: async (cp, endTime, diff) => {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
},
|
},
|
||||||
onError: async (message: string) => {
|
onError: async (message: string) => {
|
||||||
this.sockService.sendMessage({
|
this.sockService.sendMessage({
|
||||||
|
@ -46,6 +46,7 @@ export default class ScriptService {
|
||||||
const pid = await this.scheduleService.runTask(
|
const pid = await this.scheduleService.runTask(
|
||||||
command,
|
command,
|
||||||
this.taskCallbacks(filePath),
|
this.taskCallbacks(filePath),
|
||||||
|
{ command },
|
||||||
'start',
|
'start',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ export default class ScriptService {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await killTask(pid);
|
await killTask(pid);
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
|
|
||||||
return { code: 200 };
|
return { code: 200 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,7 +320,11 @@ export default class SubscriptionService {
|
||||||
|
|
||||||
const command = formatCommand(subscription);
|
const command = formatCommand(subscription);
|
||||||
|
|
||||||
this.scheduleService.runTask(command, this.taskCallbacks(subscription));
|
this.scheduleService.runTask(command, this.taskCallbacks(subscription), {
|
||||||
|
name: subscription.name,
|
||||||
|
schedule: subscription.schedule,
|
||||||
|
command
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disabled(ids: number[]) {
|
public async disabled(ids: number[]) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default class SystemService {
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private scheduleService: ScheduleService,
|
private scheduleService: ScheduleService,
|
||||||
private sockService: SockService,
|
private sockService: SockService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
public async getSystemConfig() {
|
public async getSystemConfig() {
|
||||||
const doc = await this.getDb({ type: AuthDataType.systemConfig });
|
const doc = await this.getDb({ type: AuthDataType.systemConfig });
|
||||||
|
@ -114,7 +114,7 @@ export default class SystemService {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
lastVersionContent = await parseContentVersion(result.body);
|
lastVersionContent = await parseContentVersion(result.body);
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
|
|
||||||
if (!lastVersionContent) {
|
if (!lastVersionContent) {
|
||||||
lastVersionContent = currentVersionContent;
|
lastVersionContent = currentVersionContent;
|
||||||
|
@ -232,6 +232,9 @@ export default class SystemService {
|
||||||
this.scheduleService.runTask(
|
this.scheduleService.runTask(
|
||||||
`real_log_path=${logPath} real_time=true ${command}`,
|
`real_log_path=${logPath} real_time=true ${command}`,
|
||||||
callback,
|
callback,
|
||||||
|
{
|
||||||
|
command,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,57 @@
|
||||||
import pLimit from 'p-limit';
|
import PQueue, { QueueAddOptions } from 'p-queue-cjs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { AuthDataType, AuthModel } from '../data/auth';
|
import { AuthDataType, AuthModel } from '../data/auth';
|
||||||
import Logger from '../loaders/logger';
|
import Logger from '../loaders/logger';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
class TaskLimit {
|
class TaskLimit {
|
||||||
private oneLimit = pLimit(1);
|
private oneLimit = new PQueue({ concurrency: 1 });
|
||||||
private updateLogLimit = pLimit(1);
|
private updateLogLimit = new PQueue({ concurrency: 1 });
|
||||||
private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
|
private cronLimit = new PQueue({ concurrency: Math.max(os.cpus().length, 4) });
|
||||||
|
|
||||||
get cpuLimitActiveCount() {
|
get cronLimitActiveCount() {
|
||||||
return this.cpuLimit.activeCount;
|
return this.cronLimit.pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
get cpuLimitPendingCount() {
|
get cronLimitPendingCount() {
|
||||||
return this.cpuLimit.pendingCount;
|
return this.cronLimit.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.setCustomLimit();
|
this.setCustomLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleEvents() {
|
||||||
|
this.cronLimit.on('add', () => {
|
||||||
|
Logger.info(
|
||||||
|
`[schedule][任务加入队列] 运行中任务数: ${this.cronLimitActiveCount}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
this.cronLimit.on('active', () => {
|
||||||
|
Logger.info(
|
||||||
|
`[schedule][开始处理任务] 运行中任务数: ${this.cronLimitActiveCount + 1}, 等待中任务数: ${this.cronLimitPendingCount}`,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
this.cronLimit.on('completed', (param) => {
|
||||||
|
Logger.info(
|
||||||
|
`[schedule][任务处理完成] 运行中任务数: ${this.cronLimitActiveCount - 1}, 等待中任务数: ${this.cronLimitPendingCount}, 参数 ${JSON.stringify(param)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.cronLimit.on('error', error => {
|
||||||
|
Logger.error(
|
||||||
|
`[schedule][处理任务错误] 运行中任务数: ${this.cronLimitActiveCount}, 等待中任务数: ${this.cronLimitPendingCount}, 参数 ${JSON.stringify(error)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.cronLimit.on('idle', () => {
|
||||||
|
Logger.info(
|
||||||
|
`[schedule][任务队列] 空闲中...`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async setCustomLimit(limit?: number) {
|
public async setCustomLimit(limit?: number) {
|
||||||
if (limit) {
|
if (limit) {
|
||||||
this.cpuLimit = pLimit(limit);
|
this.cronLimit = new PQueue({ concurrency: limit });;
|
||||||
|
this.handleEvents();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await AuthModel.sync();
|
await AuthModel.sync();
|
||||||
|
@ -31,23 +59,21 @@ class TaskLimit {
|
||||||
where: { type: AuthDataType.systemConfig },
|
where: { type: AuthDataType.systemConfig },
|
||||||
});
|
});
|
||||||
if (doc?.info?.cronConcurrency) {
|
if (doc?.info?.cronConcurrency) {
|
||||||
this.cpuLimit = pLimit(doc?.info?.cronConcurrency);
|
this.cronLimit = new PQueue({ concurrency: doc?.info?.cronConcurrency });
|
||||||
|
this.handleEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
|
public async runWithCronLimit<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||||
Logger.info(
|
return this.cronLimit.add(fn, options);
|
||||||
`[schedule][任务加入队列] 运行中任务数: ${this.cpuLimitActiveCount}, 等待中任务数: ${this.cpuLimitPendingCount}`,
|
|
||||||
);
|
|
||||||
return this.cpuLimit(fn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
|
public runOneByOne<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||||
return this.oneLimit(fn);
|
return this.oneLimit.add(fn, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDepLog<T>(fn: () => Promise<T>): Promise<T> {
|
public updateDepLog<T>(fn: () => Promise<T>, options?: Partial<QueueAddOptions>): Promise<T | void> {
|
||||||
return this.updateLogLimit(fn);
|
return this.updateLogLimit.add(fn, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,10 @@ import { spawn } from 'cross-spawn';
|
||||||
import taskLimit from './pLimit';
|
import taskLimit from './pLimit';
|
||||||
import Logger from '../loaders/logger';
|
import Logger from '../loaders/logger';
|
||||||
|
|
||||||
export function runCron(cmd: string): Promise<number> {
|
export function runCron(cmd: string, options?: { schedule: string; extraSchedules: Array<{ schedule: string }>; name: string }): Promise<number | void> {
|
||||||
return taskLimit.runWithCpuLimit(() => {
|
return taskLimit.runWithCronLimit(() => {
|
||||||
return new Promise(async (resolve: any) => {
|
return new Promise(async (resolve: any) => {
|
||||||
Logger.info(`[schedule][开始执行任务] 运行命令: ${cmd}`);
|
Logger.info(`[schedule][开始执行任务] 参数 ${JSON.stringify({ ...options, command: cmd })}`);
|
||||||
|
|
||||||
const cp = spawn(cmd, { shell: '/bin/bash' });
|
const cp = spawn(cmd, { shell: '/bin/bash' });
|
||||||
|
|
||||||
cp.stderr.on('data', (data) => {
|
cp.stderr.on('data', (data) => {
|
||||||
|
@ -25,8 +24,7 @@ export function runCron(cmd: string): Promise<number> {
|
||||||
});
|
});
|
||||||
|
|
||||||
cp.on('close', async (code) => {
|
cp.on('close', async (code) => {
|
||||||
Logger.info(`[schedule][任务退出] ${cmd} 进程id: ${cp.pid} 退出, 退出码 ${code}`);
|
resolve({ ...options, command: cmd, pid: cp.pid, code });
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"node-schedule": "^2.1.0",
|
"node-schedule": "^2.1.0",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"p-limit": "3.1.0",
|
"p-queue-cjs": "7.3.4",
|
||||||
"protobufjs": "^7.2.3",
|
"protobufjs": "^7.2.3",
|
||||||
"pstree.remy": "^1.1.8",
|
"pstree.remy": "^1.1.8",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
|
|
@ -85,9 +85,9 @@ dependencies:
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: ^6.7.2
|
specifier: ^6.7.2
|
||||||
version: 6.9.3
|
version: 6.9.3
|
||||||
p-limit:
|
p-queue-cjs:
|
||||||
specifier: 3.1.0
|
specifier: 7.3.4
|
||||||
version: 3.1.0
|
version: 7.3.4
|
||||||
protobufjs:
|
protobufjs:
|
||||||
specifier: ^7.2.3
|
specifier: ^7.2.3
|
||||||
version: 7.2.3
|
version: 7.2.3
|
||||||
|
@ -11633,6 +11633,7 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/p-locate@4.1.0:
|
/p-locate@4.1.0:
|
||||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
|
@ -11654,6 +11655,19 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
aggregate-error: 3.1.0
|
aggregate-error: 3.1.0
|
||||||
|
|
||||||
|
/p-queue-cjs@7.3.4:
|
||||||
|
resolution: {integrity: sha512-vP0BvEAgmUEShxWBCETvxiUDnwSiCLfBRqmhdKlNvcXF/7x2yemtYLcxT1pfYELZLlyGL3grvGnq+KF5OwNZfA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
p-timeout-cjs: 5.0.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/p-timeout-cjs@5.0.5:
|
||||||
|
resolution: {integrity: sha512-tjXKZjvzLUGerHxY0+hf0XROyF2XtuZpLNtUlkOOW+nVjDIoH0pKi3hh4X4+MXLqYavcIITLjNC0GZHUCRrwpA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/p-try@2.2.0:
|
/p-try@2.2.0:
|
||||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
@ -16299,6 +16313,7 @@ packages:
|
||||||
/yocto-queue@0.1.0:
|
/yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/yorkie@2.0.0:
|
/yorkie@2.0.0:
|
||||||
resolution: {integrity: sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw==}
|
resolution: {integrity: sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw==}
|
||||||
|
|
|
@ -74,7 +74,11 @@ const CronModal = ({
|
||||||
name="form_in_modal"
|
name="form_in_modal"
|
||||||
initialValues={cron}
|
initialValues={cron}
|
||||||
>
|
>
|
||||||
<Form.Item name="name" label={intl.get('名称')}>
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label={intl.get('名称')}
|
||||||
|
rules={[{ required: true, whitespace: true }]}
|
||||||
|
>
|
||||||
<Input placeholder={intl.get('请输入任务名称')} />
|
<Input placeholder={intl.get('请输入任务名称')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|
Loading…
Reference in New Issue
Block a user