定时服务区分系统、订阅、脚本任务

This commit is contained in:
whyour 2024-08-23 23:06:50 +08:00
parent 8b8eae211b
commit 4e5ad6d5f3
8 changed files with 132 additions and 45 deletions

View File

@ -12,7 +12,10 @@ export default async () => {
const subscriptionService = Container.get(SubscriptionService); const subscriptionService = Container.get(SubscriptionService);
// 生成内置token // 生成内置token
let tokenCommand = `ts-node-transpile-only ${join(config.rootPath, 'back/token.ts')}`; let tokenCommand = `ts-node-transpile-only ${join(
config.rootPath,
'back/token.ts',
)}`;
const tokenFile = join(config.rootPath, 'static/build/token.js'); const tokenFile = join(config.rootPath, 'static/build/token.js');
if (await fileExist(tokenFile)) { if (await fileExist(tokenFile)) {
@ -22,11 +25,16 @@ export default async () => {
id: NaN, id: NaN,
name: '生成token', name: '生成token',
command: tokenCommand, command: tokenCommand,
runOrigin: 'system',
} as ScheduleTaskType; } as ScheduleTaskType;
await scheduleService.cancelIntervalTask(cron); await scheduleService.cancelIntervalTask(cron);
scheduleService.createIntervalTask(cron, { scheduleService.createIntervalTask(
days: 28, cron,
}); {
days: 28,
},
true,
);
// 运行删除日志任务 // 运行删除日志任务
const data = await systemService.getSystemConfig(); const data = await systemService.getSystemConfig();
@ -35,17 +43,17 @@ export default async () => {
id: data.id as number, id: data.id as number,
name: '删除日志', name: '删除日志',
command: `ql rmlog ${data.info.logRemoveFrequency}`, command: `ql rmlog ${data.info.logRemoveFrequency}`,
runOrigin: 'system' as const,
}; };
await scheduleService.cancelIntervalTask(rmlogCron); await scheduleService.cancelIntervalTask(rmlogCron);
scheduleService.createIntervalTask(rmlogCron, { scheduleService.createIntervalTask(
days: data.info.logRemoveFrequency, rmlogCron,
}); {
days: data.info.logRemoveFrequency,
},
true,
);
} }
// 运行所有订阅
await subscriptionService.setSshConfig(); await subscriptionService.setSshConfig();
const subs = await subscriptionService.list();
for (const sub of subs) {
subscriptionService.handleTask(sub, !sub.is_disabled, !sub.is_disabled);
}
}; };

View File

@ -17,6 +17,7 @@ export interface ScheduleTaskType {
command: string; command: string;
name?: string; name?: string;
schedule?: string; schedule?: string;
runOrigin: 'subscription' | 'system' | 'script';
} }
export interface TaskCallbacks { export interface TaskCallbacks {
@ -40,7 +41,11 @@ export default class ScheduleService {
private intervalSchedule = new ToadScheduler(); private intervalSchedule = new ToadScheduler();
private maxBuffer = 200 * 1024 * 1024; private taskLimitMap = {
system: 'runWithSystemLimit' as const,
script: 'runWithScriptLimit' as const,
subscription: 'runWithSubscriptionLimit' as const,
};
constructor(@Inject('logger') private logger: winston.Logger) {} constructor(@Inject('logger') private logger: winston.Logger) {}
@ -52,15 +57,17 @@ export default class ScheduleService {
name?: string; name?: string;
command?: string; command?: string;
id: string; id: string;
runOrigin: 'subscription' | 'system' | 'script';
}, },
completionTime: 'start' | 'end' = 'end', completionTime: 'start' | 'end' = 'end',
) { ) {
return taskLimit.runWithCronLimit(params, () => { const { runOrigin, ...others } = params;
return taskLimit[this.taskLimitMap[runOrigin]](others, () => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
taskLimit.removeQueuedCron(params.id);
this.logger.info( this.logger.info(
`[panel][开始执行任务] 参数 ${JSON.stringify({ `[panel][开始执行任务] 参数 ${JSON.stringify({
...params, ...others,
command, command,
})}`, })}`,
); );
@ -103,7 +110,7 @@ export default class ScheduleService {
endTime, endTime,
endTime.diff(startTime, 'seconds'), endTime.diff(startTime, 'seconds'),
); );
resolve({ ...params, pid: cp.pid, code }); resolve({ ...others, pid: cp.pid, code });
}); });
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
@ -118,7 +125,7 @@ export default class ScheduleService {
} }
async createCronTask( async createCronTask(
{ id = 0, command, name, schedule = '' }: ScheduleTaskType, { id = 0, command, name, schedule = '', runOrigin }: ScheduleTaskType,
callbacks?: TaskCallbacks, callbacks?: TaskCallbacks,
runImmediately = false, runImmediately = false,
) { ) {
@ -139,6 +146,7 @@ export default class ScheduleService {
schedule, schedule,
command, command,
id: _id, id: _id,
runOrigin,
}); });
}), }),
); );
@ -149,6 +157,7 @@ export default class ScheduleService {
schedule, schedule,
command, command,
id: _id, id: _id,
runOrigin,
}); });
} }
} }
@ -157,14 +166,13 @@ export default class ScheduleService {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info('[panel][取消定时任务], 任务名: %s', name); this.logger.info('[panel][取消定时任务], 任务名: %s', name);
if (this.scheduleStacks.has(_id)) { if (this.scheduleStacks.has(_id)) {
taskLimit.removeQueuedCron(_id);
this.scheduleStacks.get(_id)?.cancel(); this.scheduleStacks.get(_id)?.cancel();
this.scheduleStacks.delete(_id); this.scheduleStacks.delete(_id);
} }
} }
async createIntervalTask( async createIntervalTask(
{ id = 0, command, name = '' }: ScheduleTaskType, { id = 0, command, name = '', runOrigin }: ScheduleTaskType,
schedule: SimpleIntervalSchedule, schedule: SimpleIntervalSchedule,
runImmediately = true, runImmediately = true,
callbacks?: TaskCallbacks, callbacks?: TaskCallbacks,
@ -183,6 +191,7 @@ export default class ScheduleService {
name, name,
command, command,
id: _id, id: _id,
runOrigin,
}); });
}, },
(err) => { (err) => {
@ -207,6 +216,7 @@ export default class ScheduleService {
name, name,
command, command,
id: _id, id: _id,
runOrigin,
}); });
} }
} }
@ -214,7 +224,6 @@ export default class ScheduleService {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) { async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id); const _id = this.formatId(id);
this.logger.info('[取消interval任务], 任务ID: %s, 任务名: %s', _id, name); this.logger.info('[取消interval任务], 任务ID: %s, 任务名: %s', _id, name);
taskLimit.removeQueuedCron(_id);
this.intervalSchedule.removeById(_id); this.intervalSchedule.removeById(_id);
} }

View File

@ -44,7 +44,7 @@ export default class ScriptService {
const pid = await this.scheduleService.runTask( const pid = await this.scheduleService.runTask(
`real_time=true ${command}`, `real_time=true ${command}`,
this.taskCallbacks(filePath), this.taskCallbacks(filePath),
{ command, id: relativePath.replace(/ /g, '-') }, { command, id: relativePath.replace(/ /g, '-'), runOrigin: 'script' },
'start', 'start',
); );

View File

@ -88,7 +88,7 @@ export default class SubscriptionService {
this.scheduleService.cancelCronTask(doc as any); this.scheduleService.cancelCronTask(doc as any);
needCreate && needCreate &&
(await this.scheduleService.createCronTask( (await this.scheduleService.createCronTask(
doc as any, { ...doc, runOrigin: 'subscription' } as any,
this.taskCallbacks(doc), this.taskCallbacks(doc),
runImmediately, runImmediately,
)); ));
@ -97,7 +97,7 @@ export default class SubscriptionService {
const { type, value } = doc.interval_schedule; const { type, value } = doc.interval_schedule;
needCreate && needCreate &&
(await this.scheduleService.createIntervalTask( (await this.scheduleService.createIntervalTask(
doc as any, { ...doc, runOrigin: 'subscription' } as any,
{ [type]: value } as SimpleIntervalSchedule, { [type]: value } as SimpleIntervalSchedule,
runImmediately, runImmediately,
this.taskCallbacks(doc), this.taskCallbacks(doc),
@ -329,6 +329,7 @@ export default class SubscriptionService {
schedule: subscription.schedule, schedule: subscription.schedule,
command, command,
id: String(subscription.id), id: String(subscription.id),
runOrigin: 'subscription',
}); });
} }

View File

@ -92,17 +92,22 @@ export default class SystemService {
info: { ...oDoc.info, ...info }, info: { ...oDoc.info, ...info },
}); });
const cron = { const cron = {
id: result.id || NaN, id: result.id as number,
name: '删除日志', name: '删除日志',
command: `ql rmlog ${info.logRemoveFrequency}`, command: `ql rmlog ${info.logRemoveFrequency}`,
runOrigin: 'system' as const,
}; };
if (oDoc.info?.logRemoveFrequency) { if (oDoc.info?.logRemoveFrequency) {
await this.scheduleService.cancelIntervalTask(cron); await this.scheduleService.cancelIntervalTask(cron);
} }
if (info.logRemoveFrequency && info.logRemoveFrequency > 0) { if (info.logRemoveFrequency && info.logRemoveFrequency > 0) {
this.scheduleService.createIntervalTask(cron, { this.scheduleService.createIntervalTask(
days: info.logRemoveFrequency, cron,
}); {
days: info.logRemoveFrequency,
},
true,
);
} }
return { code: 200, data: info }; return { code: 200, data: info };
} }
@ -179,6 +184,7 @@ export default class SystemService {
{ {
command, command,
id: 'update-node-mirror', id: 'update-node-mirror',
runOrigin: 'system',
}, },
); );
} }
@ -254,6 +260,7 @@ export default class SystemService {
{ {
command, command,
id: 'update-linux-mirror', id: 'update-linux-mirror',
runOrigin: 'system',
}, },
); );
} }
@ -366,6 +373,7 @@ export default class SystemService {
this.scheduleService.runTask(`real_time=true ${command}`, callback, { this.scheduleService.runTask(`real_time=true ${command}`, callback, {
command, command,
id: command.replace(/ /g, '-'), id: command.replace(/ /g, '-'),
runOrigin: 'system',
}); });
} }

33
back/shared/interface.ts Normal file
View File

@ -0,0 +1,33 @@
import { Dependence } from '../data/dependence';
import { ICron } from '../protos/cron';
export type Override<
T,
K extends Partial<{ [P in keyof T]: any }> | string,
> = K extends string
? Omit<T, K> & { [P in keyof T]: T[P] | unknown }
: Omit<T, keyof K> & K;
export type TCron = Override<Partial<ICron>, { id: string }>;
export interface IDependencyFn<T> {
(): Promise<T>;
dependency?: Dependence;
}
export interface ICronFn<T> {
(): Promise<T>;
cron?: TCron;
}
export interface ISchedule {
schedule?: string;
name?: string;
command?: string;
id: string;
}
export interface IScheduleFn<T> {
(): Promise<T>;
schedule?: ISchedule;
}

View File

@ -6,22 +6,14 @@ import { Dependence } from '../data/dependence';
import { ICron } from '../protos/cron'; import { ICron } from '../protos/cron';
import NotificationService from '../services/notify'; import NotificationService from '../services/notify';
import { Inject } from 'typedi'; import { Inject } from 'typedi';
import {
ICronFn,
IDependencyFn,
ISchedule,
IScheduleFn,
TCron,
} from './interface';
export type Override<
T,
K extends Partial<{ [P in keyof T]: any }> | string,
> = K extends string
? Omit<T, K> & { [P in keyof T]: T[P] | unknown }
: Omit<T, keyof K> & K;
type TCron = Override<Partial<ICron>, { id: string }>;
interface IDependencyFn<T> {
(): Promise<T>;
dependency?: Dependence;
}
interface ICronFn<T> {
(): Promise<T>;
cron?: TCron;
}
class TaskLimit { class TaskLimit {
private dependenyLimit = new PQueue({ concurrency: 1 }); private dependenyLimit = new PQueue({ concurrency: 1 });
private queuedDependencyIds = new Set<number>([]); private queuedDependencyIds = new Set<number>([]);
@ -33,6 +25,15 @@ class TaskLimit {
private manualCronoLimit = new PQueue({ private manualCronoLimit = new PQueue({
concurrency: Math.max(os.cpus().length, 4), concurrency: Math.max(os.cpus().length, 4),
}); });
private subscriptionLimit = new PQueue({
concurrency: Math.max(os.cpus().length, 4),
});
private scriptLimit = new PQueue({
concurrency: Math.max(os.cpus().length, 4),
});
private systemLimit = new PQueue({
concurrency: Math.max(os.cpus().length, 4),
});
@Inject((type) => NotificationService) @Inject((type) => NotificationService)
private notificationService!: NotificationService; private notificationService!: NotificationService;
@ -143,6 +144,33 @@ class TaskLimit {
return this.manualCronoLimit.add(fn, options); return this.manualCronoLimit.add(fn, options);
} }
public async runWithSubscriptionLimit<T>(
schedule: TCron,
fn: IScheduleFn<T>,
options?: Partial<QueueAddOptions>,
): Promise<T | void> {
fn.schedule = schedule;
return this.subscriptionLimit.add(fn, options);
}
public async runWithSystemLimit<T>(
schedule: TCron,
fn: IScheduleFn<T>,
options?: Partial<QueueAddOptions>,
): Promise<T | void> {
fn.schedule = schedule;
return this.systemLimit.add(fn, options);
}
public async runWithScriptLimit<T>(
schedule: ISchedule,
fn: IScheduleFn<T>,
options?: Partial<QueueAddOptions>,
): Promise<T | void> {
fn.schedule = schedule;
return this.scriptLimit.add(fn, options);
}
public runDependeny<T>( public runDependeny<T>(
dependency: Dependence, dependency: Dependence,
fn: IDependencyFn<T>, fn: IDependencyFn<T>,

View File

@ -34,7 +34,7 @@ const EditModal = ({
handleCancel: () => void; handleCancel: () => void;
}) => { }) => {
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [language, setLanguage] = useState<string>('javascript'); const [language, setLanguage] = useState<string>();
const [cNode, setCNode] = useState<any>(); const [cNode, setCNode] = useState<any>();
const [selectedKey, setSelectedKey] = useState<string>(); const [selectedKey, setSelectedKey] = useState<string>();
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false); const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
@ -242,7 +242,7 @@ const EditModal = ({
minimap: { enabled: false }, minimap: { enabled: false },
lineNumbersMinChars: 3, lineNumbersMinChars: 3,
glyphMargin: false, glyphMargin: false,
accessibilitySupport: 'off' accessibilitySupport: 'off',
}} }}
onMount={(editor) => { onMount={(editor) => {
editorRef.current = editor; editorRef.current = editor;