mirror of https://github.com/usual2970/certimate
				
				
				
			fix: different cronexpr rules between ui and pocketbase
							parent
							
								
									a74ec95a6a
								
							
						
					
					
						commit
						b8513eb0b6
					
				| 
						 | 
				
			
			@ -24,7 +24,7 @@ func (r *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]*domain.Wor
 | 
			
		|||
		"enabled={:enabled} && trigger={:trigger}",
 | 
			
		||||
		"-created",
 | 
			
		||||
		0, 0,
 | 
			
		||||
		dbx.Params{"enabled": true, "trigger": domain.WorkflowTriggerTypeAuto},
 | 
			
		||||
		dbx.Params{"enabled": true, "trigger": string(domain.WorkflowTriggerTypeAuto)},
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,6 @@ type certificateService interface {
 | 
			
		|||
	InitSchedule(ctx context.Context) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCertificateScheduler(service certificateService) error {
 | 
			
		||||
func InitCertificateScheduler(service certificateService) error {
 | 
			
		||||
	return service.InitSchedule(context.Background())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package scheduler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/usual2970/certimate/internal/app"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/certificate"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/repository"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/workflow"
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +15,11 @@ func Register() {
 | 
			
		|||
	certificateRepo := repository.NewCertificateRepository()
 | 
			
		||||
	certificateSvc := certificate.NewCertificateService(certificateRepo)
 | 
			
		||||
 | 
			
		||||
	NewCertificateScheduler(certificateSvc)
 | 
			
		||||
	if err := InitWorkflowScheduler(workflowSvc); err != nil {
 | 
			
		||||
		app.GetLogger().Error("failed to init workflow scheduler", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	NewWorkflowScheduler(workflowSvc)
 | 
			
		||||
	if err := InitCertificateScheduler(certificateSvc); err != nil {
 | 
			
		||||
		app.GetLogger().Error("failed to init certificate scheduler", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,6 @@ type workflowService interface {
 | 
			
		|||
	InitSchedule(ctx context.Context) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWorkflowScheduler(service workflowService) error {
 | 
			
		||||
func InitWorkflowScheduler(service workflowService) error {
 | 
			
		||||
	return service.InitSchedule(context.Background())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,6 +92,7 @@ func (w *WorkflowDispatcher) Dispatch(data *WorkflowWorkerData) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	w.enqueueWorker(data)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case w.chWork <- data:
 | 
			
		||||
	default:
 | 
			
		||||
| 
						 | 
				
			
			@ -138,6 +139,11 @@ func (w *WorkflowDispatcher) Shutdown() {
 | 
			
		|||
	w.queueMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	// 等待所有正在执行的 WorkflowRun 完成
 | 
			
		||||
	w.workerMutex.Lock()
 | 
			
		||||
	for _, worker := range w.workers {
 | 
			
		||||
		worker.Cancel()
 | 
			
		||||
	}
 | 
			
		||||
	w.workerMutex.Unlock()
 | 
			
		||||
	w.wg.Wait()
 | 
			
		||||
	w.workers = make(map[string]*workflowWorker)
 | 
			
		||||
	w.workerIdMap = make(map[string]string)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,7 +72,6 @@ func onWorkflowRecordCreateOrUpdate(ctx context.Context, record *core.Record) er
 | 
			
		|||
		})
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.GetLogger().Error("add cron job failed", "err", err)
 | 
			
		||||
		return fmt.Errorf("add cron job failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,6 +48,8 @@ func (s *WorkflowService) InitSchedule(ctx context.Context) error {
 | 
			
		|||
 | 
			
		||||
	scheduler := app.GetScheduler()
 | 
			
		||||
	for _, workflow := range workflows {
 | 
			
		||||
		var errs []error
 | 
			
		||||
 | 
			
		||||
		err := scheduler.Add(fmt.Sprintf("workflow#%s", workflow.Id), workflow.TriggerCron, func() {
 | 
			
		||||
			s.StartRun(ctx, &dtos.WorkflowStartRunReq{
 | 
			
		||||
				WorkflowId: workflow.Id,
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +57,11 @@ func (s *WorkflowService) InitSchedule(ctx context.Context) error {
 | 
			
		|||
			})
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(errs) > 0 {
 | 
			
		||||
			return errors.Join(errs...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@
 | 
			
		|||
  "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": "Time zone is based on the server.",
 | 
			
		||||
  "workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments. Time zone is based on the server.",
 | 
			
		||||
  "workflow_node.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
 | 
			
		||||
  "workflow_node.start.form.trigger_cron.guide": "Tips: If you have multiple workflows, it is recommended to set them to run at multiple times of the day instead of always running at specific times.<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>",
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@
 | 
			
		|||
  "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": "支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式,时区以服务器设置为准。",
 | 
			
		||||
  "workflow_node.start.form.trigger_cron.tooltip": "五段式表达式,支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式。时区以服务器设置为准。",
 | 
			
		||||
  "workflow_node.start.form.trigger_cron.extra": "预计最近 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>",
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
export const validCronExpression = (expr: string): boolean => {
 | 
			
		||||
  try {
 | 
			
		||||
    parseExpression(expr);
 | 
			
		||||
 | 
			
		||||
    if (expr.trim().split(" ").length !== 5) return false; // pocketbase 后端仅支持五段式的表达式
 | 
			
		||||
    return true;
 | 
			
		||||
  } catch {
 | 
			
		||||
    return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,19 +12,15 @@ export const validCronExpression = (expr: string): boolean => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
export const getNextCronExecutions = (expr: string, times = 1): Date[] => {
 | 
			
		||||
  if (!expr) return [];
 | 
			
		||||
  if (!validCronExpression(expr)) return [];
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const now = new Date();
 | 
			
		||||
    const cron = parseExpression(expr, { currentDate: now, iterator: true });
 | 
			
		||||
  const now = new Date();
 | 
			
		||||
  const cron = parseExpression(expr, { currentDate: now, iterator: true });
 | 
			
		||||
 | 
			
		||||
    const result: Date[] = [];
 | 
			
		||||
    for (let i = 0; i < times; i++) {
 | 
			
		||||
      const next = cron.next();
 | 
			
		||||
      result.push(next.value.toDate());
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
  } catch {
 | 
			
		||||
    return [];
 | 
			
		||||
  const result: Date[] = [];
 | 
			
		||||
  for (let i = 0; i < times; i++) {
 | 
			
		||||
    const next = cron.next();
 | 
			
		||||
    result.push(next.value.toDate());
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue