mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-22 21:05:48 +08:00
fix: #906
This commit is contained in:
parent
1b0f652de3
commit
299e2202fd
@ -11,10 +11,10 @@
|
||||
"workflow_node.start.form.trigger.placeholder": "Please select trigger",
|
||||
"workflow_node.start.form.trigger.option.scheduled.label": "Scheduled",
|
||||
"workflow_node.start.form.trigger.option.manual.label": "Manual",
|
||||
"workflow_node.start.form.trigger_cron.label": "Cron expression",
|
||||
"workflow_node.start.form.trigger_cron.placeholder": "Please enter cron expression",
|
||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "Please enter a valid cron expression",
|
||||
"workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments.",
|
||||
"workflow_node.start.form.trigger_cron.label": "CRON expression",
|
||||
"workflow_node.start.form.trigger_cron.placeholder": "Please enter CRON expression",
|
||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "Please enter a valid CRON expression",
|
||||
"workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments, in standard <em>crontab</em> rules.",
|
||||
"workflow_node.start.form.trigger_cron.help": "Expected execution time for the last 5 times (the actual time zone is based on the server):",
|
||||
"workflow_node.start.form.trigger_cron.guide": "If you have multiple workflows, it is recommended to set them to run at different times of the day instead of always running at a specific time. And please don't always set it to midnight every day to avoid spikes in traffic. <br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Let’s Encrypt (ACME) client run at a random time?</a>",
|
||||
|
||||
|
||||
@ -11,10 +11,10 @@
|
||||
"workflow_node.start.form.trigger.placeholder": "请选择触发方式",
|
||||
"workflow_node.start.form.trigger.option.scheduled.label": "定时触发",
|
||||
"workflow_node.start.form.trigger.option.manual.label": "手动触发",
|
||||
"workflow_node.start.form.trigger_cron.label": "Cron 表达式",
|
||||
"workflow_node.start.form.trigger_cron.placeholder": "请输入 Cron 表达式",
|
||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "请输入正确的 Cron 表达式",
|
||||
"workflow_node.start.form.trigger_cron.tooltip": "五段式表达式。<br>支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式。",
|
||||
"workflow_node.start.form.trigger_cron.label": "CRON 表达式",
|
||||
"workflow_node.start.form.trigger_cron.placeholder": "请输入 CRON 表达式",
|
||||
"workflow_node.start.form.trigger_cron.errmsg.invalid": "请输入正确的 CRON 表达式",
|
||||
"workflow_node.start.form.trigger_cron.tooltip": "五段式表达式,使用 <em>crontab</em> 标准语法规则。<br>支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式。",
|
||||
"workflow_node.start.form.trigger_cron.help": "预计最近 5 次运行时间(实际时区以服务器设置为准):",
|
||||
"workflow_node.start.form.trigger_cron.guide": "如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。也不要总是设置为每日零时,以免遭遇证书颁发机构的流量高峰。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Let’s Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?</a>",
|
||||
|
||||
|
||||
@ -4,7 +4,16 @@ export const validCronExpression = (expr: string): boolean => {
|
||||
try {
|
||||
CronExpressionParser.parse(expr);
|
||||
|
||||
if (expr.trim().split(" ").length !== 5) return false; // pocketbase 后端仅支持五段式的表达式
|
||||
// pocketbase 后端仅支持标准 crontab 形式的表达式
|
||||
// 这里转译了来自 pocketbase 的 golang 代码来验证
|
||||
const segments = expr.trim().split(" ");
|
||||
if (segments.length !== 5) return false;
|
||||
parseCronSegment(segments[0], 0, 59);
|
||||
parseCronSegment(segments[1], 0, 23);
|
||||
parseCronSegment(segments[2], 1, 31);
|
||||
parseCronSegment(segments[3], 1, 12);
|
||||
parseCronSegment(segments[4], 0, 6);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@ -19,3 +28,83 @@ export const getNextCronExecutions = (expr: string, times = 1): Date[] => {
|
||||
|
||||
return cron.take(times).map((date) => date.toDate());
|
||||
};
|
||||
|
||||
// transpile from:
|
||||
// https://github.com/pocketbase/pocketbase/blob/5d964c1b1d020f425299b32df03ecf44e0a0502e/tools/cron/schedule.go#L141-L218
|
||||
function parseCronSegment(segment: string, min: number, max: number): Set<number> {
|
||||
const slots = new Set<number>();
|
||||
|
||||
const list = segment.split(",");
|
||||
for (const p of list) {
|
||||
const stepParts = p.split("/");
|
||||
|
||||
let step: number;
|
||||
switch (stepParts.length) {
|
||||
case 1:
|
||||
{
|
||||
step = 1;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
const parsedStep = parseInt(stepParts[1], 10);
|
||||
if (isNaN(parsedStep) || parsedStep < 1 || parsedStep > max) {
|
||||
throw new Error(`invalid segment step boundary - the step must be between 1 and the ${max}`);
|
||||
}
|
||||
step = parsedStep;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("invalid segment step format - must be in the format */n or 1-30/n");
|
||||
}
|
||||
|
||||
let rangeMin: number, rangeMax: number;
|
||||
if (stepParts[0] === "*") {
|
||||
rangeMin = min;
|
||||
rangeMax = max;
|
||||
} else {
|
||||
const rangeParts = stepParts[0].split("-");
|
||||
switch (rangeParts.length) {
|
||||
case 1:
|
||||
{
|
||||
if (step !== 1) {
|
||||
throw new Error("invalid segment step - step > 1 could be used only with the wildcard or range format");
|
||||
}
|
||||
const parsed = parseInt(rangeParts[0], 10);
|
||||
if (isNaN(parsed) || parsed < min || parsed > max) {
|
||||
throw new Error("invalid segment value - must be between the min and max of the segment");
|
||||
}
|
||||
rangeMin = parsed;
|
||||
rangeMax = rangeMin;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
const parsedMin = parseInt(rangeParts[0], 10);
|
||||
if (isNaN(parsedMin) || parsedMin < min || parsedMin > max) {
|
||||
throw new Error(`invalid segment range minimum - must be between ${min} and ${max}`);
|
||||
}
|
||||
rangeMin = parsedMin;
|
||||
|
||||
const parsedMax = parseInt(rangeParts[1], 10);
|
||||
if (isNaN(parsedMax) || parsedMax < rangeMin || parsedMax > max) {
|
||||
throw new Error(`invalid segment range maximum - must be between ${rangeMin} and ${max}`);
|
||||
}
|
||||
rangeMax = parsedMax;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("invalid segment range format - the range must have 1 or 2 parts");
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = rangeMin; i <= rangeMax; i += step) {
|
||||
slots.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user