Skip to content

Commit eccc118

Browse files
committed
Merge branch 'main' into release/v1.4
# Conflicts: # src/pkg/utils/script.ts
2 parents 15c62d0 + 2f0e8d9 commit eccc118

File tree

4 files changed

+59
-22
lines changed

4 files changed

+59
-22
lines changed

src/app/service/sandbox/runtime.ts

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

263277
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)