完善订阅接口

This commit is contained in:
whyour 2022-05-14 21:38:26 +08:00
parent 5a21247cbb
commit 5523d537dc
5 changed files with 90 additions and 23 deletions

View File

@ -28,7 +28,8 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
type: Joi.string().required(),
schedule: Joi.string().required(),
schedule: Joi.string().optional(),
intervalSchedule: Joi.object().optional(),
name: Joi.string().optional(),
url: Joi.string().required(),
whitelist: Joi.string().optional(),
@ -38,8 +39,8 @@ export default (app: Router) => {
status: Joi.number().optional(),
pull_type: Joi.string().optional(),
pull_option: Joi.object().optional(),
schedule_type: Joi.number().optional(),
alias: Joi.number().required(),
schedule_type: Joi.string().required(),
alias: Joi.string().required(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@ -156,7 +157,8 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
type: Joi.string().required(),
schedule: Joi.string().required(),
schedule: Joi.string().optional(),
intervalSchedule: Joi.object().optional(),
name: Joi.string().optional(),
url: Joi.string().required(),
whitelist: Joi.string().optional(),
@ -166,8 +168,8 @@ export default (app: Router) => {
status: Joi.number().optional(),
pull_type: Joi.string().optional(),
pull_option: Joi.object().optional(),
schedule_type: Joi.number().optional(),
alias: Joi.number().required(),
schedule_type: Joi.string().optional(),
alias: Joi.string().required(),
id: Joi.number().required(),
}),
}),
@ -176,6 +178,7 @@ export default (app: Router) => {
try {
if (
!req.body.schedule ||
typeof req.body.schedule === 'object' ||
cron_parser.parseExpression(req.body.schedule).hasNext()
) {
const subscriptionService = Container.get(SubscriptionService);

View File

@ -7,7 +7,8 @@ export class Subscription {
name?: string;
type?: 'public-repo' | 'private-repo' | 'file';
schedule_type?: 'crontab' | 'interval';
schedule?: string | SimpleIntervalSchedule;
schedule?: string;
intervalSchedule?: SimpleIntervalSchedule;
url?: string;
whitelist?: string;
blacklist?: string;
@ -22,6 +23,7 @@ export class Subscription {
isDisabled?: 1 | 0;
log_path?: string;
alias: string;
command?: string;
constructor(options: Subscription) {
this.id = options.id;
@ -41,6 +43,7 @@ export class Subscription {
this.log_path = options.log_path;
this.schedule_type = options.schedule_type;
this.alias = options.alias;
this.intervalSchedule = options.intervalSchedule;
}
}
@ -69,6 +72,11 @@ export const SubscriptionModel = sequelize.define<SubscriptionInstance>(
unique: 'compositeIndex',
type: DataTypes.STRING,
},
intervalSchedule: {
unique: 'compositeIndex',
type: DataTypes.JSON,
},
type: DataTypes.STRING,
whitelist: DataTypes.STRING,
blacklist: DataTypes.STRING,
status: DataTypes.NUMBER,

View File

@ -1,7 +1,6 @@
import { Service, Inject } from 'typedi';
import winston from 'winston';
import nodeSchedule from 'node-schedule';
import { Crontab } from '../data/cron';
import { exec } from 'child_process';
import {
ToadScheduler,
@ -10,6 +9,13 @@ import {
SimpleIntervalSchedule,
} from 'toad-scheduler';
interface ScheduleTaskType {
id: number;
command: string;
name?: string;
schedule?: string;
}
@Service()
export default class ScheduleService {
private scheduleStacks = new Map<string, nodeSchedule.Job>();
@ -20,7 +26,12 @@ export default class ScheduleService {
constructor(@Inject('logger') private logger: winston.Logger) {}
async createCronTask({ id = 0, command, name, schedule = '' }: Crontab) {
async createCronTask({
id = 0,
command,
name,
schedule = '',
}: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info(
'[创建cron任务]任务ID: %scron: %s任务名: %s执行命令: %s',
@ -32,7 +43,7 @@ export default class ScheduleService {
this.scheduleStacks.set(
_id,
nodeSchedule.scheduleJob(id + '', schedule, async () => {
nodeSchedule.scheduleJob(_id, schedule, async () => {
try {
exec(
command,
@ -70,15 +81,16 @@ export default class ScheduleService {
);
}
async cancelCronTask({ id = 0, name }: Crontab) {
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消定时任务],任务名:%s', name);
this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel();
}
async createIntervalTask(
{ id = 0, command, name = '' }: Crontab,
{ id = 0, command, name = '' }: ScheduleTaskType,
schedule: SimpleIntervalSchedule,
runImmediately = true,
) {
const _id = this.formatId(id);
this.logger.info(
@ -131,12 +143,12 @@ export default class ScheduleService {
},
);
const job = new LongIntervalJob({ ...schedule }, task, _id);
const job = new LongIntervalJob({ ...schedule, runImmediately }, task, _id);
this.intervalSchedule.addIntervalJob(job);
}
async cancelIntervalTask({ id = 0, name }: Crontab) {
async cancelIntervalTask({ id = 0, name }: ScheduleTaskType) {
const _id = this.formatId(id);
this.logger.info('[取消interval任务]任务ID: %s任务名%s', _id, name);
this.intervalSchedule.removeById(_id);

View File

@ -14,10 +14,15 @@ import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import ScheduleService from './schedule';
import { SimpleIntervalSchedule } from 'toad-scheduler';
@Service()
export default class SubscriptionService {
constructor(@Inject('logger') private logger: winston.Logger) {}
constructor(
@Inject('logger') private logger: winston.Logger,
private scheduleService: ScheduleService,
) {}
public async list(searchText?: string): Promise<Subscription[]> {
let query = {};
@ -75,9 +80,38 @@ export default class SubscriptionService {
}
}
private formatCommand(doc: Subscription) {
let command = 'ql ';
const { type, url, whitelist, blacklist, dependences, branch } = doc;
if (type === 'file') {
command += `raw ${url}`;
} else {
command += `repo ${url} ${whitelist || ''} ${blacklist || ''} ${
dependences || ''
} ${branch || ''}`;
}
return command;
}
private handleTask(doc: Subscription, needCreate = true) {
doc.command = this.formatCommand(doc);
if (doc.schedule_type === 'crontab') {
this.scheduleService.cancelCronTask(doc as any);
needCreate && this.scheduleService.createCronTask(doc as any);
} else {
this.scheduleService.cancelIntervalTask(doc as any);
needCreate &&
this.scheduleService.createIntervalTask(
doc as any,
doc.intervalSchedule as SimpleIntervalSchedule,
);
}
}
public async create(payload: Subscription): Promise<Subscription> {
const tab = new Subscription(payload);
const doc = await this.insert(tab);
this.handleTask(doc);
return doc;
}
@ -87,6 +121,7 @@ export default class SubscriptionService {
public async update(payload: Subscription): Promise<Subscription> {
const newDoc = await this.updateDb(payload);
this.handleTask(newDoc);
return newDoc;
}
@ -156,7 +191,9 @@ export default class SubscriptionService {
this.logger.silly(error);
}
}
const err = await this.killTask('');
this.handleTask(doc, false);
const command = this.formatCommand(doc);
const err = await this.killTask(command);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
if (logFileExist) {
@ -219,17 +256,16 @@ export default class SubscriptionService {
return;
}
let { id, log_path } = cron;
let { id, log_path, name } = cron;
const command = this.formatCommand(cron);
const absolutePath = path.resolve(config.logPath, `${log_path}`);
const logFileExist = log_path && (await fileExist(absolutePath));
this.logger.silly('Running job');
this.logger.silly('Running job' + name);
this.logger.silly('ID: ' + id);
this.logger.silly('Original command: ');
this.logger.silly('Original command: ' + command);
let cmdStr = '';
const cp = spawn(cmdStr, { shell: '/bin/bash' });
const cp = spawn(command, { shell: '/bin/bash' });
await SubscriptionModel.update(
{ status: SubscriptionStatus.running, pid: cp.pid },
@ -266,10 +302,18 @@ export default class SubscriptionService {
}
public async disabled(ids: number[]) {
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
this.handleTask(doc, false);
}
await SubscriptionModel.update({ isDisabled: 1 }, { where: { id: ids } });
}
public async enabled(ids: number[]) {
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
this.handleTask(doc);
}
await SubscriptionModel.update({ isDisabled: 0 }, { where: { id: ids } });
}

View File

@ -281,7 +281,7 @@ const SubscriptionModal = ({
</Radio.Group>
</Form.Item>
<Form.Item
name="schedule"
name={scheduleType === 'crontab' ? 'schedule' : 'intervalSchedule'}
label="定时规则"
rules={[
{ required: true },