runtime
Some checks failed
test / Run tests (push) Failing after 17s
build / Build (push) Failing after 24s

This commit is contained in:
2025-01-24 16:49:20 +08:00
parent af15d67cb3
commit 415f00a3d1
33 changed files with 1115 additions and 4118 deletions

View File

@@ -1,10 +1,18 @@
import LoggerCore from "@App/app/logger/core";
import Logger from "@App/app/logger/logger";
import { Script, SCRIPT_TYPE_BACKGROUND, ScriptRunResouce } from "@App/app/repo/scripts";
import {
SCRIPT_RUN_STATUS_COMPLETE,
SCRIPT_RUN_STATUS_ERROR,
SCRIPT_RUN_STATUS_RUNNING,
SCRIPT_TYPE_BACKGROUND,
ScriptRunResouce,
} from "@App/app/repo/scripts";
import ExecScript from "@App/runtime/content/exec_script";
import { BgExecScriptWarp, CATRetryError } from "@App/runtime/content/exec_warp";
import { Server } from "@Packages/message/server";
import { WindowMessage } from "@Packages/message/window_message";
import { CronJob } from "cron";
import { proxyUpdateRunStatus } from "../offscreen/client";
export class Runtime {
cronJob: Map<string, Array<CronJob>> = new Map();
@@ -55,34 +63,219 @@ export class Runtime {
}
}
removeRetryList(scriptId: number) {
removeRetryList(uuid: string) {
for (let i = 0; i < this.retryList.length; i += 1) {
if (this.retryList[i].script.id === scriptId) {
if (this.retryList[i].script.uuid === uuid) {
this.retryList.splice(i, 1);
i -= 1;
}
}
}
enableScript(data: Script) {
// 开启脚本, 判断脚本是后台脚本还是定时脚本
if (data.type === SCRIPT_TYPE_BACKGROUND) {
async enableScript(script: ScriptRunResouce) {
// 开启脚本
// 如果正在运行,先释放
if (this.execScripts.has(script.uuid)) {
await this.disableScript(script.uuid);
}
if (script.type === SCRIPT_TYPE_BACKGROUND) {
// 后台脚本直接运行起来
return this.execScript(script);
} else {
// 定时脚本加入定时任务
return this.crontabScript(script);
}
eval("console.log('hello')");
console.log("enableScript", data);
}
disableScript(data: Script) {
// 关闭脚本, 判断脚本是后台脚本还是定时脚本
if (data.type === SCRIPT_TYPE_BACKGROUND) {
// 后台脚本直接停止
} else {
// 定时脚本停止定时任务
disableScript(uuid: string) {
// 关闭脚本
// 停止定时任务
this.stopCronJob(uuid);
// 移除重试队列
this.removeRetryList(uuid);
// 发送运行状态变更
proxyUpdateRunStatus(this.windowMessage, { uuid, runStatus: SCRIPT_RUN_STATUS_COMPLETE });
// 停止脚本运行
return this.stopScript(uuid);
}
// 执行脚本
async execScript(script: ScriptRunResouce, execOnce?: boolean) {
const logger = this.logger.with({ script: script.uuid, name: script.name });
if (this.execScripts.has(script.uuid)) {
// 释放掉资源
// 暂未实现执行完成后立马释放,会在下一次执行时释放
await this.stopScript(script.uuid);
}
console.log("disableScript", data);
const exec = new BgExecScriptWarp(script);
this.execScripts.set(script.uuid, exec);
proxyUpdateRunStatus(this.windowMessage, { uuid: script.uuid, runStatus: SCRIPT_RUN_STATUS_RUNNING });
// 修改掉脚本掉最后运行时间, 数据库也需要修改
script.lastruntime = new Date().getTime();
const ret = exec.exec();
if (ret instanceof Promise) {
ret
.then((resp) => {
// 发送执行完成消息
proxyUpdateRunStatus(this.windowMessage, { uuid: script.uuid, runStatus: SCRIPT_RUN_STATUS_COMPLETE });
logger.info("exec script complete", {
value: resp,
});
})
.catch((err) => {
// 发送执行完成+错误消息
let errMsg;
let nextruntime = 0;
if (err instanceof CATRetryError) {
// @ts-ignore
errMsg = { error: err.msg };
if (!execOnce) {
// 下一次执行时间
// @ts-ignore
nextruntime = err.time.getTime();
script.nextruntime = nextruntime;
this.joinRetryList(script);
}
} else {
errMsg = Logger.E(err);
}
logger.error("exec script error", errMsg);
proxyUpdateRunStatus(this.windowMessage, {
uuid: script.uuid,
runStatus: SCRIPT_RUN_STATUS_ERROR,
error: errMsg,
nextruntime,
});
// 错误还是抛出,方便排查
throw err;
});
} else {
logger.warn("backscript return not promise");
}
return ret;
}
crontabScript(script: ScriptRunResouce) {
// 执行定时脚本 运行表达式
if (!script.metadata.crontab) {
throw new Error("错误的crontab表达式");
}
// 如果有nextruntime,则加入重试队列
this.joinRetryList(script);
let flag = false;
const cronJobList: Array<CronJob> = [];
script.metadata.crontab.forEach((val) => {
let oncePos = 0;
let crontab = val;
if (crontab.indexOf("once") !== -1) {
const vals = crontab.split(" ");
vals.forEach((item, index) => {
if (item === "once") {
oncePos = index;
}
});
if (vals.length === 5) {
oncePos += 1;
}
crontab = crontab.replace(/once/g, "*");
}
try {
const cron = new CronJob(crontab, this.crontabExec(script, oncePos));
cron.start();
cronJobList.push(cron);
} catch (e) {
flag = true;
this.logger.error(
"create cronjob failed",
{
uuid: script.uuid,
crontab: val,
},
Logger.E(e)
);
}
});
if (cronJobList.length !== script.metadata.crontab.length) {
// 有表达式失败了
cronJobList.forEach((crontab) => {
crontab.stop();
});
} else {
this.cronJob.set(script.uuid, cronJobList);
}
return Promise.resolve(!flag);
}
crontabExec(script: ScriptRunResouce, oncePos: number) {
if (oncePos) {
return () => {
// 没有最后一次执行时间表示之前都没执行过,直接执行
if (!script.lastruntime) {
this.execScript(script);
return;
}
const now = new Date();
const last = new Date(script.lastruntime);
let flag = false;
// 根据once所在的位置去判断执行
switch (oncePos) {
case 1: // 每分钟
flag = last.getMinutes() !== now.getMinutes();
break;
case 2: // 每小时
flag = last.getHours() !== now.getHours();
break;
case 3: // 每天
flag = last.getDay() !== now.getDay();
break;
case 4: // 每月
flag = last.getMonth() !== now.getMonth();
break;
case 5: // 每周
flag = this.getWeek(last) !== this.getWeek(now);
break;
default:
}
if (flag) {
this.execScript(script);
}
};
}
return () => {
this.execScript(script);
};
}
// 获取本周是第几周
getWeek(date: Date) {
const nowDate = new Date(date);
const firstDay = new Date(date);
firstDay.setMonth(0); // 设置1月
firstDay.setDate(1); // 设置1号
const diffDays = Math.ceil((nowDate.getTime() - firstDay.getTime()) / (24 * 60 * 60 * 1000));
const week = Math.ceil(diffDays / 7);
return week === 0 ? 1 : week;
}
// 停止计时器
stopCronJob(uuid: string) {
const list = this.cronJob.get(uuid);
if (list) {
list.forEach((val) => {
val.stop();
});
this.cronJob.delete(uuid);
}
}
stopScript(uuid: string) {
const exec = this.execScripts.get(uuid);
if (!exec) {
return Promise.resolve(false);
}
exec.stop();
this.execScripts.delete(uuid);
return Promise.resolve(true);
}
init() {