diff --git a/back/api/system.ts b/back/api/system.ts index 15c6e059..457ff413 100644 --- a/back/api/system.ts +++ b/back/api/system.ts @@ -7,7 +7,12 @@ import SystemService from '../services/system'; import { celebrate, Joi } from 'celebrate'; import UserService from '../services/user'; import { EnvModel } from '../data/env'; -import { parseVersion, promiseExec } from '../config/util'; +import { + getUniqPath, + handleLogPath, + parseVersion, + promiseExec, +} from '../config/util'; import dayjs from 'dayjs'; const route = Router(); @@ -147,4 +152,40 @@ export default (app: Router) => { } }, ); + + route.put( + '/command-run', + celebrate({ + body: Joi.object({ + command: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const systemService = Container.get(SystemService); + const uniqPath = await getUniqPath(req.body.command); + const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss'); + const logPath = `${uniqPath}/${logTime}.log`; + res.setHeader('Content-type', 'application/octet-stream'); + await systemService.run(req.body, { + onEnd: async (cp, endTime, diff) => { + res.end(); + }, + onError: async (message: string) => { + res.write(`\n${message}`); + const absolutePath = await handleLogPath(logPath); + fs.appendFileSync(absolutePath, `\n${message}`); + }, + onLog: async (message: string) => { + res.write(`\n${message}`); + const absolutePath = await handleLogPath(logPath); + fs.appendFileSync(absolutePath, `\n${message}`); + }, + }); + } catch (e) { + return next(e); + } + }, + ); }; diff --git a/back/config/util.ts b/back/config/util.ts index d943bdbe..48f41ada 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -7,6 +7,8 @@ import FormData from 'form-data'; import psTreeFun from 'pstree.remy'; import { promisify } from 'util'; import { load } from 'js-yaml'; +import config from './index'; +import { TASK_COMMAND } from './const'; export function getFileContentByName(fileName: string) { if (fs.existsSync(fileName)) { @@ -245,6 +247,18 @@ export async function createFile(file: string, data: string = '') { }); } +export async function handleLogPath( + logPath: string, + data: string = '', +): Promise { + const absolutePath = path.resolve(config.logPath, logPath); + const logFileExist = await fileExist(absolutePath); + if (!logFileExist) { + await createFile(absolutePath, data); + } + return absolutePath; +} + export async function concurrentRun( fnList: Array<() => Promise> = [], max = 5, @@ -501,3 +515,40 @@ export async function parseVersion(path: string): Promise { export async function parseContentVersion(content: string): Promise { return load(content) as IVersion; } + +export async function getUniqPath(command: string): Promise { + const idStr = `cat ${config.crontabFile} | grep -E "${command}" | perl -pe "s|.*ID=(.*) ${command}.*|\\1|" | head -1 | awk -F " " '{print $1}' | xargs echo -n`; + let id = await promiseExec(idStr); + + if (/^\d\d*\d$/.test(id)) { + id = `_${id}`; + } else { + id = ''; + } + + const items = command.split(/ +/); + let str = items[0]; + if (items[0] === TASK_COMMAND) { + str = items[1]; + } + + const dotIndex = str.lastIndexOf('.'); + + if (dotIndex !== -1) { + str = str.slice(0, dotIndex); + } + + const slashIndex = str.lastIndexOf('/'); + + let tempStr = ''; + if (slashIndex !== -1) { + tempStr = str.slice(0, slashIndex); + const _slashIndex = tempStr.lastIndexOf('/'); + if (_slashIndex !== -1) { + tempStr = tempStr.slice(_slashIndex + 1); + } + str = `${tempStr}_${str.slice(slashIndex + 1)}`; + } + + return `${str}${id}`; +} diff --git a/back/services/subscription.ts b/back/services/subscription.ts index c270b088..6cb3dfa7 100644 --- a/back/services/subscription.ts +++ b/back/services/subscription.ts @@ -19,6 +19,7 @@ import { fileExist, createFile, killTask, + handleLogPath, } from '../config/util'; import { promises, existsSync } from 'fs'; import { FindOptions, Op } from 'sequelize'; @@ -121,18 +122,6 @@ export default class SubscriptionService { }); } - private async handleLogPath( - logPath: string, - data: string = '', - ): Promise { - const absolutePath = path.resolve(config.logPath, logPath); - const logFileExist = await fileExist(absolutePath); - if (!logFileExist) { - await createFile(absolutePath, data); - } - return absolutePath; - } - private taskCallbacks(doc: Subscription): TaskCallbacks { return { onBefore: async (startTime) => { @@ -145,7 +134,7 @@ export default class SubscriptionService { }, { where: { id: doc.id } }, ); - const absolutePath = await this.handleLogPath( + const absolutePath = await handleLogPath( logPath as string, `## 开始执行... ${startTime.format('YYYY-MM-DD HH:mm:ss')}\n`, ); @@ -175,7 +164,7 @@ export default class SubscriptionService { }, onEnd: async (cp, endTime, diff) => { const sub = await this.getDb({ id: doc.id }); - const absolutePath = await this.handleLogPath(sub.log_path as string); + const absolutePath = await handleLogPath(sub.log_path as string); // 执行 sub_after let afterStr = ''; @@ -212,12 +201,12 @@ export default class SubscriptionService { }, onError: async (message: string) => { const sub = await this.getDb({ id: doc.id }); - const absolutePath = await this.handleLogPath(sub.log_path as string); + const absolutePath = await handleLogPath(sub.log_path as string); fs.appendFileSync(absolutePath, `\n${message}`); }, onLog: async (message: string) => { const sub = await this.getDb({ id: doc.id }); - const absolutePath = await this.handleLogPath(sub.log_path as string); + const absolutePath = await handleLogPath(sub.log_path as string); fs.appendFileSync(absolutePath, `\n${message}`); }, }; @@ -236,7 +225,7 @@ export default class SubscriptionService { } public async update(payload: Subscription): Promise { - const doc = await this.getDb({ id: payload.id }) + const doc = await this.getDb({ id: payload.id }); const tab = new Subscription({ ...doc, ...payload }); const newDoc = await this.updateDb(tab); await this.handleTask(newDoc, !newDoc.is_disabled); @@ -289,7 +278,9 @@ export default class SubscriptionService { await this.setSshConfig(); } - public async getDb(query: FindOptions['where']): Promise { + public async getDb( + query: FindOptions['where'], + ): Promise { const doc: any = await SubscriptionModel.findOne({ where: { ...query } }); return doc && (doc.get({ plain: true }) as Subscription); } @@ -315,7 +306,7 @@ export default class SubscriptionService { this.logger.silly(error); } } - const absolutePath = await this.handleLogPath(doc.log_path as string); + const absolutePath = await handleLogPath(doc.log_path as string); fs.appendFileSync( `${absolutePath}`, @@ -369,7 +360,7 @@ export default class SubscriptionService { return ''; } - const absolutePath = await this.handleLogPath(doc.log_path as string); + const absolutePath = await handleLogPath(doc.log_path as string); return getFileContentByName(absolutePath); } diff --git a/back/services/system.ts b/back/services/system.ts index 4d5aaa51..d7895f3c 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -5,11 +5,12 @@ import * as fs from 'fs'; import { AuthDataType, AuthInfo, AuthModel, LoginStatus } from '../data/auth'; import { NotificationInfo } from '../data/notify'; import NotificationService from './notify'; -import ScheduleService from './schedule'; +import ScheduleService, { TaskCallbacks } from './schedule'; import { spawn } from 'child_process'; import SockService from './sock'; import got from 'got'; import { parseContentVersion, parseVersion } from '../config/util'; +import { TASK_COMMAND } from '../config/const'; @Service() export default class SystemService { @@ -170,4 +171,11 @@ export default class SystemService { return { code: 400, message: '通知发送失败,请检查系统设置/通知配置' }; } } + + public async run({ command }: { command: string }, callback: TaskCallbacks) { + if (!command.startsWith(TASK_COMMAND)) { + command = `${TASK_COMMAND} ${command}`; + } + this.scheduleService.runTask(`real_time=true ${command}`, callback); + } } diff --git a/shell/api.sh b/shell/api.sh index 0f2a9d58..99d1da9f 100755 --- a/shell/api.sh +++ b/shell/api.sh @@ -178,7 +178,7 @@ update_cron() { code=$(echo "$api" | jq -r .code) message=$(echo "$api" | jq -r .message) if [[ $code != 200 ]]; then - echo -e "\n## 更新任务状态失败(${message})\n" >>$dir_log/$log_path + echo -e "\n## 更新任务状态失败(${message})\n" fi } diff --git a/shell/otask.sh b/shell/otask.sh index 9635b7b4..4fa68ba3 100755 --- a/shell/otask.sh +++ b/shell/otask.sh @@ -89,7 +89,7 @@ check_server() { ## 正常运行单个脚本,$1:传入参数 run_normal() { local file_param=$1 - if [[ $# -eq 1 ]]; then + if [[ $# -eq 1 ]] && [[ "$real_time" != "true" ]]; then random_delay "$file_param" fi diff --git a/shell/task.sh b/shell/task.sh index 5d459690..57dfcf1d 100755 --- a/shell/task.sh +++ b/shell/task.sh @@ -43,7 +43,11 @@ handle_log_path() { fi local suffix="" if [[ ! -z $ID ]]; then - suffix="_${ID}" + if [[ "$ID" -gt 0 ]] 2>/dev/null; then + suffix="_${ID}" + else + ID="" + fi fi time=$(date "+$mtime_format") @@ -66,6 +70,10 @@ handle_log_path() { if [[ "$show_log" == "true" ]]; then cmd="2>&1 | tee -a $dir_log/$log_path" fi + + if [[ "$real_time" == "true" ]]; then + cmd="" + fi } format_params() {