Skip to content

Commit 40e7eb0

Browse files
committed
Merge branch 'release/v1.4' into release/v1.4-agent
2 parents 6dbfaef + eccc118 commit 40e7eb0

4 files changed

Lines changed: 59 additions & 22 deletions

File tree

src/app/service/sandbox/runtime.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -230,35 +230,49 @@ export class Runtime {
230230
// 如果有nextruntime,则加入重试队列
231231
this.joinRetryList(script);
232232
this.crontabSripts.push(script);
233-
let flag = false;
233+
234+
const ERROR_MESSAGES: Record<number, string> = {
235+
0: "crontabScript: cron expression failed",
236+
2: "crontabScript: onTick creation failed",
237+
4: "crontabScript: create cronjob failed",
238+
6: "crontabScript: cronjob start failed",
239+
};
240+
241+
const logError = (ok: number, val: string, e: unknown) =>
242+
this.logger.error(
243+
ERROR_MESSAGES[ok] ?? "crontabScript: execution failed",
244+
{ uuid: script.uuid, crontab: val },
245+
Logger.E(e)
246+
);
247+
234248
const cronJobList: Array<CronJob> = [];
235249
script.metadata.crontab.forEach((val) => {
236-
const { cronExpr, oncePos } = extractCronExpr(val);
250+
let ok = 0;
237251
try {
238-
const cron = new CronJob(cronExpr, this.crontabExec(script, oncePos));
252+
const { cronExpr, oncePos } = extractCronExpr(val);
253+
ok = 2;
254+
const onTick = this.crontabExec(script, oncePos);
255+
ok = 4;
256+
const cron = new CronJob(cronExpr, onTick);
257+
ok = 6;
239258
cron.start();
259+
ok = 8;
240260
cronJobList.push(cron);
241261
} catch (e) {
242-
flag = true;
243-
this.logger.error(
244-
"create cronjob failed",
245-
{
246-
uuid: script.uuid,
247-
crontab: val,
248-
},
249-
Logger.E(e)
250-
);
262+
logError(ok, val, e);
251263
}
252264
});
253-
if (cronJobList.length !== script.metadata.crontab.length) {
265+
266+
const allSucceeded = cronJobList.length === script.metadata.crontab.length;
267+
if (allSucceeded) {
268+
this.cronJob.set(script.uuid, cronJobList);
269+
} else {
254270
// 有表达式失败了
255271
for (const crontab of cronJobList) {
256272
crontab.stop();
257273
}
258-
} else {
259-
this.cronJob.set(script.uuid, cronJobList);
260274
}
261-
return !flag;
275+
return allSucceeded;
262276
}
263277

264278
crontabExec(script: ScriptLoadInfo, oncePos: number) {

src/pkg/utils/cron.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,18 @@ type NextTimeResult = {
8181
* 对外展示用方法。
8282
*
8383
* - 若为 once cron,返回「下次在 xx 执行一次」的国际化文案
84+
* - 若表达式无效,返回本地化的错误提示文案
8485
* - 否则直接返回下一次执行时间字符串
8586
*/
8687
export const nextTimeDisplay = (crontab: string, date = new Date()): string => {
87-
const res = nextTimeInfo(crontab, date);
88-
const nextTimeFormatted = res.next.toFormat(res.format);
89-
return res.once ? t(`cron_oncetype.${res.once}`, { next: nextTimeFormatted }) : nextTimeFormatted;
88+
try {
89+
const res = nextTimeInfo(crontab, date);
90+
const nextTimeFormatted = res.next.toFormat(res.format);
91+
return res.once ? t(`cron_oncetype.${res.once}`, { next: nextTimeFormatted }) : nextTimeFormatted;
92+
} catch (e) {
93+
console.error(`nextTimeDisplay: Invalid cron expression "${crontab}"`, e);
94+
return t("cron_invalid_expr");
95+
}
9096
};
9197

9298
/**

src/pkg/utils/script.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from "@App/app/repo/scripts";
1313
import type { Subscribe } from "@App/app/repo/subscribe";
1414
import { SubscribeStatusType, SubscribeDAO } from "@App/app/repo/subscribe";
15-
import { nextTimeDisplay } from "./cron";
15+
import { extractCronExpr } from "./cron";
1616
import { parseUserConfig } from "./yaml";
1717
import { t as i18n_t } from "@App/locales/locales";
1818
import { readBlobContent } from "@App/pkg/utils/encoding";
@@ -82,7 +82,7 @@ export function parseScriptFromCode(code: string, origin: string, uuid?: string)
8282
if (metadata.crontab !== undefined) {
8383
type = SCRIPT_TYPE_CRONTAB;
8484
try {
85-
nextTimeDisplay(metadata.crontab[0]);
85+
extractCronExpr(metadata.crontab[0]);
8686
} catch {
8787
throw new Error(i18n_t("error_cron_invalid", { expr: metadata.crontab[0] }));
8888
}
@@ -137,7 +137,7 @@ export async function prepareScriptByCode(
137137
dao?: ScriptDAO,
138138
options?: {
139139
byEditor?: boolean; // 是否通过编辑器导入
140-
byWebRequest?: boolean; // 是否通过網頁連結安裝或更新
140+
byWebRequest?: boolean; // 是否通过网页连结安装或更新
141141
}
142142
): Promise<{ script: Script; oldScript?: Script; oldScriptCode?: string }> {
143143
dao = dao ?? new ScriptDAO();

src/pkg/utils/utils.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@ const assertNextTimeInfo = (expr: string, date: Date, expected: any) => {
6262
.toEqual(expected);
6363
};
6464

65+
describe.concurrent("nextTimeDisplay ERROR SAFE", () => {
66+
it.concurrent.each([
67+
["* * * once * once"],
68+
["* * once * once"],
69+
["* once(2,4) once(4-5) * *"],
70+
["* * 1 A *"],
71+
["* once 1.2 * *"],
72+
["* 3 1**2 * *"],
73+
["* 1^2 F * *"],
74+
["1 1 * *"],
75+
["* 3"],
76+
])("错误Cron表达式: %s", (expr) => {
77+
// 确保无效表达式不会抛出异常
78+
expect(() => nextTimeDisplay(expr)).not.toThrow();
79+
});
80+
});
81+
6582
describe.concurrent("nextTimeInfo1", () => {
6683
const date = new Date("2025-12-17T11:47:17.629"); // 2025-12-17 11:47:17.629 (本地时区)
6784

0 commit comments

Comments
 (0)