chore: udpate cron expr limit

This commit is contained in:
engigu
2026-03-24 11:48:11 +08:00
parent 271c284b29
commit c85322bcb0
2 changed files with 45 additions and 8 deletions
+18 -1
View File
@@ -2,6 +2,8 @@ package executor
import (
"math/rand"
"strings"
"fmt"
"sync"
"time"
@@ -61,6 +63,7 @@ func (m *CronManager) Stop() {
m.logger.Infof("[CronManager] 调度管理服务已停止")
}
// AddTask 添加或更新计划任务
func (m *CronManager) AddTask(task CronTask) error {
m.mu.Lock()
@@ -83,7 +86,8 @@ func (m *CronManager) AddTask(task CronTask) error {
languages := task.GetLanguages()
useMise := task.UseMise()
entryID, err := m.cron.AddFunc(task.GetSchedule(), func() {
schedule := strings.TrimSpace(task.GetSchedule())
entryID, err := m.cron.AddFunc(schedule, func() {
defer func() {
if r := recover(); r != nil {
m.logger.Errorf("[CronManager] 任务 #%s 执行过程中发生 Panic: %v", taskID, r)
@@ -182,6 +186,19 @@ func (m *CronManager) triggerNextRunEvent(taskID string, req *ExecutionRequest)
// ValidateCron 校验 Cron 表达式
func (m *CronManager) ValidateCron(expression string) error {
expression = strings.TrimSpace(expression)
if expression == "" {
return fmt.Errorf("cron 表达式不能为空")
}
// 如果不是以 @ 开头的描述符,检查位数
if !strings.HasPrefix(expression, "@") {
fields := strings.Fields(expression)
if len(fields) != 6 {
return fmt.Errorf("cron 表达式必须为 6 位 (秒 分 时 日 月 周)")
}
}
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
_, err := parser.Parse(expression)
return err
+27 -7
View File
@@ -62,12 +62,21 @@ const cronDescription = computed(() => {
return getCronDescription(form.value.schedule, (navigator as any).language)
})
const isCronValid = computed(() => {
if (!form.value.schedule) return true
const s = form.value.schedule.trim()
if (s.startsWith('@')) return true
const fields = s.split(/\s+/).filter(Boolean)
return fields.length === 6
})
// 监听 concurrencyEnabled 的变化,同步到 concurrency
watch(concurrencyEnabled, (val) => {
concurrency.value = val ? 1 : 0
})
function onConcurrencyChange(val: boolean) {
concurrencyEnabled.value = val
}
@@ -426,8 +435,10 @@ async function save() {
}
emit('update:open', false)
emit('saved')
} catch (error) {
toast.error('保存失败')
} catch (error: any) {
toast.error('保存失败', {
description: error.message || '未知错误'
})
}
}
</script>
@@ -445,6 +456,7 @@ async function save() {
</DialogHeader>
<ScrollArea class="flex-1 min-h-0 px-6">
<div class="space-y-8 py-4 pb-8">
<!-- 基本信息 Section -->
<section class="space-y-4">
@@ -657,7 +669,7 @@ async function save() {
<Label for="all-envs" class="text-[11px] font-medium cursor-pointer">使用全量环境变量</Label>
</div>
<Popover>
<PopoverTrigger as-child>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" class="h-6 w-6 opacity-40 hover:opacity-100 hover:text-primary transition-all">
<AlertCircle class="h-3.5 w-3.5" />
</Button>
@@ -749,14 +761,22 @@ async function save() {
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-3">
<Label class="sm:text-right text-xs text-muted-foreground uppercase tracking-wider font-semibold">定时规则</Label>
<div class="sm:col-span-3">
<Input v-model="form.schedule" placeholder="* * * * * *" class="h-9 font-mono text-[13px] bg-muted/30 border-muted-foreground/20 focus:ring-1 focus:ring-primary/50" />
<div v-if="cronDescription" class="mt-2.5 p-2 rounded-lg bg-primary/5 border border-primary/10 text-[11px] text-primary font-medium flex items-center gap-2 animate-in fade-in slide-in-from-top-1 duration-300">
<Zap class="h-3 w-3" />
<Input v-model="form.schedule" placeholder="秒 分 时 日 月 周 (必须 6 位)"
:class="cn('h-9 bg-muted/20 border-muted-foreground/15 transition-all focus:ring-1 focus:ring-primary/40 focus:border-primary/40', form.schedule ? 'font-mono text-sm tracking-[0.1em] font-medium' : 'text-[11px] font-normal')" />
<div v-if="form.schedule && !isCronValid" class="mt-2 text-[10px] text-destructive flex items-center gap-1.5 font-medium animate-in fade-in slide-in-from-top-1 duration-300">
<AlertCircle class="h-3 w-3" /> 表达式位数错误: 必须为 6 ( )
</div>
<div v-if="cronDescription" class="mt-2.5 p-2 px-3 rounded-xl bg-primary/5 border border-primary/10 text-[11px] text-primary/80 font-medium flex items-center gap-2.5 animate-in fade-in slide-in-from-top-1 duration-300 shadow-sm shadow-primary/5">
<div class="p-1 rounded-full bg-primary/10">
<Zap class="h-3 w-3 text-primary animate-pulse" />
</div>
{{ cronDescription }}
</div>
<div class="mt-2.5 space-y-2">
<div class="flex items-center gap-1.5 text-[10px] text-muted-foreground/70 uppercase font-bold tracking-tighter">
<Clock class="h-3 w-3" /> 格式指导:
<Clock class="h-3 w-3" /> 格式指导: (必须使用 6 位表达式)
</div>
<div class="flex flex-wrap gap-1.5">
<button v-for="preset in cronPresets" :key="preset.value"