mirror of
https://github.com/whyour/qinglong.git
synced 2026-07-01 04:40:38 +08:00
全新青龙2.0 (#65)
* 重构shell (#17) * 更新正则 * 更新update命令 * 移除测试代码 * 重构删除日志命令 * 更新entrypoint * 更新dockerfile * 完善shell调用 * 修复share shell引用 * 修复entrypoint * 修复share shell * 修复share.sh * 修改依赖重装逻辑 * 更新docker entrypoint * curl 使用静默模式 * 更新ql raw * 修复添加单个任务 * 修复shell语法 * 添加定时任务进程 * 更新默认定时任务 * 更新定时任务重启schedule * 更新青龙重启逻辑 * 修复定时任务列表创建 * 修复schedule进程 * 修复entrypoint * 更新task命令 * pm2 restart替换成reload * 修复task命令参数引入 * 完善ql repo命令 * 修复update.sh * 更新ql repo命令 * ql repo添加登录验证,修复package.json示例 * 修复定时任务命令补全 * 修改默认cron端口 * 修复cron日志弹框异常 * 修改cron新建label * 修复ql repo命令 * 修复cron格式验证 * 修改日志目录格式 * 修改青龙remote url * 修复添加定时cron匹配 * 添加定时任务超时时间设置 * 暂时移除timeout命令 * 恢复定时任务timeout * 修复cookie.sh引用 * 修复shell变量自加 * 修复ck更新状态同步 * 增加tg bot测试,修改增删任务通知 * 修复shell函数返回值 * 修改添加任务日志打印 * 修改entrypoint日志 * 修复api日志打印 * 修改api日志打印 * 定时任务支持批量启用禁用删除运行 * 修改cron管理操作按钮响应样式 * 更新bot启动脚本 * 更新bot启动脚本 * 增加timeout默认值,修改session管理逻辑 * 更新config示例和通知日志 * 更新bot.sh * 更新启动bot命令 * 更新启动bot命令 * 修复task运行参数合并 * 增加停止定时任务功能 * 修复停止定时任务api * 更新停止定时任务日志 * 更新停止任务日志 * 修复删除cron api * 更新删除cron通知文本 * 更新命令提示 * 更新bot启动脚本
This commit is contained in:
@@ -26,10 +26,6 @@ export default (app: Router) => {
|
||||
case 'crontab':
|
||||
content = getFileContentByName(config.crontabFile);
|
||||
break;
|
||||
case 'shareCode':
|
||||
let shareCodeFile = getLastModifyFilePath(config.shareCodeDir);
|
||||
content = getFileContentByName(shareCodeFile);
|
||||
break;
|
||||
case 'extra':
|
||||
content = getFileContentByName(config.extraFile);
|
||||
break;
|
||||
|
||||
+48
-31
@@ -1,10 +1,9 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { Container } from 'typedi';
|
||||
import { Logger } from 'winston';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import CronService from '../services/cron';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
import cron_parser from 'cron-parser';
|
||||
const route = Router();
|
||||
|
||||
export default (app: Router) => {
|
||||
@@ -32,14 +31,36 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string(),
|
||||
name: Joi.string().optional(),
|
||||
}),
|
||||
}),
|
||||
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' });
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/crons/run',
|
||||
celebrate({
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.create(req.body);
|
||||
const data = await cronService.run(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
@@ -48,18 +69,16 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/:id/run',
|
||||
route.put(
|
||||
'/crons/stop',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
}),
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.run(req.params.id);
|
||||
const data = await cronService.stop(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
@@ -68,18 +87,16 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/:id/disable',
|
||||
route.put(
|
||||
'/crons/disable',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
}),
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.disabled(req.params.id);
|
||||
const data = await cronService.disabled(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
@@ -88,18 +105,16 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/crons/:id/enable',
|
||||
route.put(
|
||||
'/crons/enable',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
}),
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.enabled(req.params.id);
|
||||
const data = await cronService.enabled(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
@@ -134,16 +149,20 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
command: Joi.string().required(),
|
||||
schedule: Joi.string().required(),
|
||||
name: Joi.string(),
|
||||
name: Joi.string().optional(),
|
||||
_id: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.update(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
if (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' });
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
@@ -152,17 +171,15 @@ export default (app: Router) => {
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/crons/:id',
|
||||
'/crons',
|
||||
celebrate({
|
||||
params: Joi.object({
|
||||
id: Joi.string().required(),
|
||||
}),
|
||||
body: Joi.array().items(Joi.string().required()),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const cronService = Container.get(CronService);
|
||||
const data = await cronService.remove(req.params.id);
|
||||
const data = await cronService.remove(req.body);
|
||||
return res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
||||
@@ -7,11 +7,10 @@ const envFound = dotenv.config();
|
||||
const rootPath = path.resolve(__dirname, '../../');
|
||||
const cookieFile = path.join(rootPath, 'config/cookie.sh');
|
||||
const confFile = path.join(rootPath, 'config/config.sh');
|
||||
const sampleFile = path.join(rootPath, 'sample/config.sh.sample');
|
||||
const sampleFile = path.join(rootPath, 'sample/config.sample.sh');
|
||||
const crontabFile = path.join(rootPath, 'config/crontab.list');
|
||||
const confBakDir = path.join(rootPath, 'config/bak/');
|
||||
const authConfigFile = path.join(rootPath, 'config/auth.json');
|
||||
const shareCodeDir = path.join(rootPath, 'log/export_sharecodes/');
|
||||
const extraFile = path.join(rootPath, 'config/extra.sh');
|
||||
const logPath = path.join(rootPath, 'log/');
|
||||
const authError = '错误的用户名密码,请重试';
|
||||
@@ -28,6 +27,7 @@ if (envFound.error) {
|
||||
|
||||
export default {
|
||||
port: parseInt(process.env.PORT as string, 10),
|
||||
cronPort: parseInt(process.env.CRON_PORT as string, 10),
|
||||
secret: process.env.SECRET,
|
||||
logs: {
|
||||
level: process.env.LOG_LEVEL || 'silly',
|
||||
@@ -40,7 +40,6 @@ export default {
|
||||
authError,
|
||||
logPath,
|
||||
extraFile,
|
||||
shareCodeDir,
|
||||
authConfigFile,
|
||||
confBakDir,
|
||||
crontabFile,
|
||||
|
||||
@@ -8,6 +8,7 @@ export class Crontab {
|
||||
_id?: string;
|
||||
status?: CrontabStatus;
|
||||
isSystem?: 1 | 0;
|
||||
pid?: number;
|
||||
|
||||
constructor(options: Crontab) {
|
||||
this.name = options.name;
|
||||
@@ -19,6 +20,7 @@ export class Crontab {
|
||||
this.status = options.status || CrontabStatus.idle;
|
||||
this.timestamp = new Date().toString();
|
||||
this.isSystem = options.isSystem || 0;
|
||||
this.pid = options.pid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,10 @@ export default ({ app }: { app: Application }) => {
|
||||
next: NextFunction,
|
||||
) => {
|
||||
if (err.name === 'UnauthorizedError') {
|
||||
return res.status(err.status).send({ message: err.message }).end();
|
||||
return res
|
||||
.status(err.status)
|
||||
.send({ code: 401, message: err.message })
|
||||
.end();
|
||||
}
|
||||
return next(err);
|
||||
},
|
||||
@@ -64,6 +67,7 @@ export default ({ app }: { app: Application }) => {
|
||||
) => {
|
||||
res.status(err.status || 500);
|
||||
res.json({
|
||||
code: err.status || 500,
|
||||
message: err.message,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -5,10 +5,7 @@ import CronService from '../services/cron';
|
||||
const initData = [
|
||||
{
|
||||
name: '更新面板',
|
||||
command: `sleep ${randomSchedule(
|
||||
60,
|
||||
1,
|
||||
)} && git_pull >> $QL_DIR/log/git_pull.log 2>&1`,
|
||||
command: `sleep ${randomSchedule(60, 1)} && ql update`,
|
||||
schedule: `${randomSchedule(60, 1)} ${randomSchedule(
|
||||
24,
|
||||
7,
|
||||
@@ -16,46 +13,22 @@ const initData = [
|
||||
status: CrontabStatus.idle,
|
||||
},
|
||||
{
|
||||
name: 'build面板',
|
||||
command: 'rebuild >> ${QL_DIR}/log/rebuild.log 2>&1',
|
||||
name: '重启并编译面板',
|
||||
command: 'ql restart',
|
||||
schedule: '30 7 */7 * *',
|
||||
status: CrontabStatus.disabled,
|
||||
},
|
||||
{
|
||||
name: '自定义仓库',
|
||||
command: `sleep ${randomSchedule(
|
||||
60,
|
||||
1,
|
||||
)} && diy https://ghproxy.com/https://github.com/whyour/hundun.git "quanx/jx|quanx/jd" tokens >> $QL_DIR/log/diy_pull.log 2>&1`,
|
||||
schedule: `${randomSchedule(60, 1)} ${randomSchedule(
|
||||
24,
|
||||
6,
|
||||
).toString()} * * *`,
|
||||
status: CrontabStatus.idle,
|
||||
},
|
||||
{
|
||||
name: '互助码导出',
|
||||
command: 'export_sharecodes',
|
||||
schedule: '48 5 * * *',
|
||||
status: CrontabStatus.idle,
|
||||
},
|
||||
{
|
||||
name: '删除日志',
|
||||
command: 'rm_log >/dev/null 2>&1',
|
||||
command: 'ql rmlog 7',
|
||||
schedule: '30 7 */7 * *',
|
||||
status: CrontabStatus.disabled,
|
||||
status: CrontabStatus.idle,
|
||||
},
|
||||
{
|
||||
name: '重置密码',
|
||||
command: 'js resetpwd',
|
||||
schedule: '33 6 */7 * *',
|
||||
status: CrontabStatus.disabled,
|
||||
},
|
||||
{
|
||||
name: '运行所有脚本(慎用)',
|
||||
command: 'js runall',
|
||||
schedule: '33 6 */7 * *',
|
||||
status: CrontabStatus.disabled,
|
||||
name: '互助码',
|
||||
command: 'ql code',
|
||||
schedule: '30 7 * * *',
|
||||
status: CrontabStatus.idle,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import schedule from 'node-schedule';
|
||||
import express from 'express';
|
||||
import { exec } from 'child_process';
|
||||
import Logger from './loaders/logger';
|
||||
import { Container } from 'typedi';
|
||||
import CronService from './services/cron';
|
||||
import { CrontabStatus } from './data/cron';
|
||||
import config from './config';
|
||||
|
||||
const app = express();
|
||||
|
||||
const run = async () => {
|
||||
const cronService = Container.get(CronService);
|
||||
const cronDb = cronService.getDb();
|
||||
|
||||
cronDb
|
||||
.find({})
|
||||
.sort({ created: 1 })
|
||||
.exec((err, docs) => {
|
||||
if (err) {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (docs && docs.length > 0) {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const task = docs[i];
|
||||
const _schedule = task.schedule && task.schedule.split(' ');
|
||||
if (
|
||||
_schedule &&
|
||||
_schedule.length > 5 &&
|
||||
task.status !== CrontabStatus.disabled
|
||||
) {
|
||||
schedule.scheduleJob(task.schedule, function () {
|
||||
let command = task.command as string;
|
||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
||||
command = `task ${command}`;
|
||||
}
|
||||
exec(command);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
app
|
||||
.listen(config.cronPort, () => {
|
||||
run();
|
||||
Logger.info(`
|
||||
################################################
|
||||
🛡️ Schedule listening on port: ${config.cronPort} 🛡️
|
||||
################################################
|
||||
`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
+21
-5
@@ -121,6 +121,16 @@ export default class CookieService {
|
||||
});
|
||||
}
|
||||
|
||||
private async formatCookies(cookies: Cookie[]) {
|
||||
const result = [];
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i];
|
||||
const { status, nickname } = await this.getJdInfo(cookie.value);
|
||||
result.push({ ...cookie, status, nickname });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async create(payload: string[]): Promise<Cookie[]> {
|
||||
const cookies = await this.cookies('');
|
||||
let position = initCookiePosition;
|
||||
@@ -135,7 +145,7 @@ export default class CookieService {
|
||||
});
|
||||
const docs = await this.insert(tabs);
|
||||
await this.set_cookies();
|
||||
return docs;
|
||||
return await this.formatCookies(docs);
|
||||
}
|
||||
|
||||
public async insert(payload: Cookie[]): Promise<Cookie[]> {
|
||||
@@ -156,20 +166,21 @@ export default class CookieService {
|
||||
const tab = new Cookie({ ...doc, ...other });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
await this.set_cookies();
|
||||
return newDoc;
|
||||
const [newCookie] = await this.formatCookies([newDoc]);
|
||||
return newCookie;
|
||||
}
|
||||
|
||||
public async updateDb(payload: Cookie): Promise<Cookie> {
|
||||
private async updateDb(payload: Cookie): Promise<Cookie> {
|
||||
return new Promise((resolve) => {
|
||||
this.cronDb.update(
|
||||
{ _id: payload._id },
|
||||
payload,
|
||||
{ returnUpdatedDocs: true },
|
||||
(err, docs) => {
|
||||
(err, num, doc) => {
|
||||
if (err) {
|
||||
this.logger.error(err);
|
||||
} else {
|
||||
resolve(docs as Cookie);
|
||||
resolve(doc as Cookie);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -228,6 +239,11 @@ export default class CookieService {
|
||||
],
|
||||
};
|
||||
}
|
||||
const newDocs = await this.find(query, sort);
|
||||
return await this.formatCookies(newDocs);
|
||||
}
|
||||
|
||||
private async find(query: any, sort: any): Promise<Cookie[]> {
|
||||
return new Promise((resolve) => {
|
||||
this.cronDb
|
||||
.find(query)
|
||||
|
||||
+108
-66
@@ -22,12 +22,20 @@ export default class CronService {
|
||||
return this.cronDb;
|
||||
}
|
||||
|
||||
private isSixCron(cron: Crontab) {
|
||||
const { schedule } = cron;
|
||||
if (schedule.split(' ').length === 6) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async create(payload: Crontab): Promise<Crontab> {
|
||||
const tab = new Crontab(payload);
|
||||
tab.created = new Date().valueOf();
|
||||
tab.saved = false;
|
||||
const doc = await this.insert(tab);
|
||||
await this.set_crontab();
|
||||
await this.set_crontab(this.isSixCron(doc));
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -74,9 +82,9 @@ export default class CronService {
|
||||
this.cronDb.update({ _id }, { $set: { stopped, saved: false } });
|
||||
}
|
||||
|
||||
public async remove(_id: string) {
|
||||
this.cronDb.remove({ _id }, {});
|
||||
await this.set_crontab();
|
||||
public async remove(ids: string[]) {
|
||||
this.cronDb.remove({ _id: { $in: ids } }, { multi: true });
|
||||
await this.set_crontab(true);
|
||||
}
|
||||
|
||||
public async crontabs(searchText?: string): Promise<Crontab[]> {
|
||||
@@ -112,68 +120,98 @@ export default class CronService {
|
||||
});
|
||||
}
|
||||
|
||||
public async run(_id: string) {
|
||||
this.cronDb.find({ _id }).exec((err, docs: Crontab[]) => {
|
||||
let res = docs[0];
|
||||
|
||||
this.logger.silly('Running job');
|
||||
this.logger.silly('ID: ' + _id);
|
||||
this.logger.silly('Original command: ' + res.command);
|
||||
|
||||
let logFile = `${config.manualLogPath}${res._id}.log`;
|
||||
fs.writeFileSync(logFile, `开始执行...\n\n${new Date().toString()}\n`);
|
||||
|
||||
let cmdStr = res.command;
|
||||
if (res.command.startsWith('js') && !res.command.endsWith('now')) {
|
||||
cmdStr = `${res.command} now`;
|
||||
} else if (/&& (.*) >>/.test(res.command)) {
|
||||
cmdStr = res.command.match(/&& (.*) >>/)[1];
|
||||
public async run(ids: string[]) {
|
||||
this.cronDb.find({ _id: { $in: ids } }).exec((err, docs: Crontab[]) => {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const doc = docs[i];
|
||||
this.runSingle(doc);
|
||||
}
|
||||
const cmd = spawn(cmdStr, { shell: true });
|
||||
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.running } });
|
||||
|
||||
cmd.stdout.on('data', (data) => {
|
||||
this.logger.silly(`stdout: ${data}`);
|
||||
fs.appendFileSync(logFile, data);
|
||||
});
|
||||
|
||||
cmd.stderr.on('data', (data) => {
|
||||
this.logger.error(`stderr: ${data}`);
|
||||
fs.appendFileSync(logFile, data);
|
||||
});
|
||||
|
||||
cmd.on('close', (code) => {
|
||||
this.logger.silly(`child process exited with code ${code}`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
});
|
||||
|
||||
cmd.on('error', (err) => {
|
||||
this.logger.silly(err);
|
||||
fs.appendFileSync(logFile, err.stack);
|
||||
});
|
||||
|
||||
cmd.on('exit', (code: number, signal: any) => {
|
||||
this.logger.silly(`cmd exit ${code}`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
fs.appendFileSync(logFile, `\n\n执行结束...`);
|
||||
});
|
||||
|
||||
cmd.on('disconnect', () => {
|
||||
this.logger.silly(`cmd disconnect`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
fs.appendFileSync(logFile, `\n\n连接断开...`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async disabled(_id: string) {
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.disabled } });
|
||||
public async stop(ids: string[]) {
|
||||
this.cronDb.find({ _id: { $in: ids } }).exec((err, docs: Crontab[]) => {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const doc = docs[i];
|
||||
if (doc.pid) {
|
||||
exec(`kill -9 ${doc.pid}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async runSingle(cron: Crontab) {
|
||||
let { _id, command } = cron;
|
||||
|
||||
this.logger.silly('Running job');
|
||||
this.logger.silly('ID: ' + _id);
|
||||
this.logger.silly('Original command: ' + command);
|
||||
|
||||
let logFile = `${config.manualLogPath}${_id}.log`;
|
||||
fs.writeFileSync(logFile, `开始执行...\n\n${new Date().toString()}\n`);
|
||||
|
||||
let cmdStr = command;
|
||||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
||||
cmdStr = `task ${cmdStr}`;
|
||||
}
|
||||
if (cmdStr.endsWith('.js')) {
|
||||
cmdStr = `${cmdStr} now`;
|
||||
}
|
||||
const cmd = spawn(cmdStr, { shell: true });
|
||||
|
||||
this.cronDb.update(
|
||||
{ _id },
|
||||
{ $set: { status: CrontabStatus.running, pid: cmd.pid } },
|
||||
);
|
||||
|
||||
cmd.stdout.on('data', (data) => {
|
||||
this.logger.silly(`stdout: ${data}`);
|
||||
fs.appendFileSync(logFile, data);
|
||||
});
|
||||
|
||||
cmd.stderr.on('data', (data) => {
|
||||
this.logger.error(`stderr: ${data}`);
|
||||
fs.appendFileSync(logFile, data);
|
||||
});
|
||||
|
||||
cmd.on('close', (code) => {
|
||||
this.logger.silly(`child process exited with code ${code}`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
});
|
||||
|
||||
cmd.on('error', (err) => {
|
||||
this.logger.silly(err);
|
||||
fs.appendFileSync(logFile, err.stack);
|
||||
});
|
||||
|
||||
cmd.on('exit', (code: number, signal: any) => {
|
||||
this.logger.silly(`cmd exit ${code}`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
fs.appendFileSync(logFile, `\n\n执行结束...`);
|
||||
});
|
||||
|
||||
cmd.on('disconnect', () => {
|
||||
this.logger.silly(`cmd disconnect`);
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
fs.appendFileSync(logFile, `\n\n连接断开...`);
|
||||
});
|
||||
}
|
||||
|
||||
public async disabled(ids: string[]) {
|
||||
this.cronDb.update(
|
||||
{ _id: { $in: ids } },
|
||||
{ $set: { status: CrontabStatus.disabled } },
|
||||
{ multi: true },
|
||||
);
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
public async enabled(_id: string) {
|
||||
this.cronDb.update({ _id }, { $set: { status: CrontabStatus.idle } });
|
||||
public async enabled(ids: string[]) {
|
||||
this.cronDb.update(
|
||||
{ _id: { $in: ids } },
|
||||
{ $set: { status: CrontabStatus.idle } },
|
||||
{ multi: true },
|
||||
);
|
||||
}
|
||||
|
||||
public async log(_id: string) {
|
||||
@@ -186,11 +224,12 @@ export default class CronService {
|
||||
return crontab_job_string;
|
||||
}
|
||||
|
||||
private async set_crontab() {
|
||||
private async set_crontab(needReloadSchedule: boolean = false) {
|
||||
const tabs = await this.crontabs();
|
||||
var crontab_string = '';
|
||||
tabs.forEach((tab) => {
|
||||
if (tab.status === CrontabStatus.disabled) {
|
||||
const _schedule = tab.schedule && tab.schedule.split(' ');
|
||||
if (tab.status === CrontabStatus.disabled || _schedule.length !== 5) {
|
||||
crontab_string += '# ';
|
||||
crontab_string += tab.schedule;
|
||||
crontab_string += ' ';
|
||||
@@ -208,6 +247,9 @@ export default class CronService {
|
||||
fs.writeFileSync(config.crontabFile, crontab_string);
|
||||
|
||||
execSync(`crontab ${config.crontabFile}`);
|
||||
if (needReloadSchedule) {
|
||||
exec(`pm2 reload schedule`);
|
||||
}
|
||||
this.cronDb.update({}, { $set: { saved: true } }, { multi: true });
|
||||
}
|
||||
|
||||
@@ -226,11 +268,11 @@ export default class CronService {
|
||||
var command = line.replace(regex, '').trim();
|
||||
var schedule = line.replace(command, '').trim();
|
||||
|
||||
var is_valid = false;
|
||||
try {
|
||||
is_valid = cron_parser.parseString(line).expressions.length > 0;
|
||||
} catch (e) {}
|
||||
if (command && schedule && is_valid) {
|
||||
if (
|
||||
command &&
|
||||
schedule &&
|
||||
cron_parser.parseExpression(schedule).hasNext()
|
||||
) {
|
||||
var name = namePrefix + '_' + index;
|
||||
|
||||
this.cronDb.findOne({ command, schedule }, (err, doc) => {
|
||||
|
||||
Reference in New Issue
Block a user