校验定时参数之cron

1、概述

Cron 表达式是一个用于配置定时任务的字符串,它由六个或七个空格分隔的字段组成,分别表示时间的不同单位。Cron 表达式通常用于作业调度系统中,如 Unix/Linux 系统的 cron 守护进程,以及一些现代的作业调度框架,如 Quartz 和 Spring Scheduler等。

例如:* * * * * *

每个星号 () 代表一个时间单位,从左到右依次表示:
秒(Seconds):0-59
分钟(Minutes):0-59
小时(Hours):0-23
日期(Day of Month):1-31
月份(Month):1-12 或 JAN-DEC
星期几(Day of Week):0-7(0 和 7 都代表周日)或 SUN-SAT
年份(Year):可选字段,1970-2099
除了使用星号 (
) 表示所有可能的时间单位,Cron 表达式还支持以下特殊字符:
逗号 (,):用于枚举值,例如在分钟字段中使用 5,20,30 表示在每小时的第 5、20、30 分钟触发。
连字符 (-):用于范围,例如在小时字段中使用 5-10 表示从早上 5 点到上午 10 点之间每小时的开始时刻触发。
斜杠 (/):用于指定时间的间隔,例如在分钟字段中使用 0/15 表示每 15 分钟触发一次。
此外,还有一些特殊字符可以用于特定的场景:
问号 (?):用在日期或星期字段中,表示不指定具体的值。
L:用在日期字段中表示该月最后一天,用在星期字段中表示周六(或数字 7)。
W:用在日期字段中,表示最接近指定日期的工作日(周一至周五)。
#:用在星期字段中,用于指定月份中的第几个星期几,例如 6#3 表示该月的第三个星期五。
Cron 表达式提供了一种灵活且强大的方式来定义何时运行作业,使得用户能够根据需要精确地控制任务的触发时间。

2、生成cron表达式方式

有没有想过对 cron 表达式进行检验的。例如:如下截图中输入表达式后,点击 校验定时参数 校验cron表达式的正确性。
1、对于cron表达式可以考虑是否借鉴xxl-job的实现。
2、由运维人员手动输入cron表达式
3、调用第3方插件生成cron表达式

jQuery Cron:这是一个基于 jQuery 的库,可以很容易地集成到网页中,提供一个用户友好的界面来生成 Cron 表达式。
Vue Cron:如果你在使用 Vue.js,那么 Vue Cron 是一个很好的选择,它是一个基于 Vue.js 的 Cron 表达式生成器组件。
Ant Design Vue Cron:这是另一个基于 Vue.js 的 Cron 表达式生成器组件,适用于使用 Ant Design Vue 的项目。

在这里插入图片描述

3、cron表达式校验

那么生成的cron表达式生成后需要校验,校验有以下两种方法:

3-1、cron-utils

引入依赖 注意:getHandler、validate两个方法

<!-- https://mvnrepository.com/artifact/com.cronutils/cron-utils -->
<dependency>
    <groupId>com.cronutils</groupId>
    <artifactId>cron-utils</artifactId>
    <version>9.2.1</version>
</dependency>
@Slf4j
@Service
public class TimingStrategyService {

    private static final int NEXT_N_TIMES = 5;

    private static final List<String> TIPS = Collections.singletonList("It is valid, but has not trigger time list!");


    private final Map<TimeExpressionType, TimingStrategyHandler> strategyContainer;

    public TimingStrategyService(List<TimingStrategyHandler> timingStrategyHandlers) {
        // init
        strategyContainer = new EnumMap<>(TimeExpressionType.class);
        for (TimingStrategyHandler timingStrategyHandler : timingStrategyHandlers) {
            strategyContainer.put(timingStrategyHandler.supportType(), timingStrategyHandler);
        }
    }

    /**
     * 计算接下来几次的调度时间
     *
     * @param timeExpressionType 定时表达式类型
     * @param timeExpression     表达式
     * @param startTime          起始时间(include)
     * @param endTime            结束时间(include)
     * @return 调度时间列表
     */
    public List<String> calculateNextTriggerTimes(TimeExpressionType timeExpressionType, String timeExpression, Long startTime, Long endTime) {

        TimingStrategyHandler timingStrategyHandler = getHandler(timeExpressionType);
        List<Long> triggerTimeList = new ArrayList<>(NEXT_N_TIMES);
        Long nextTriggerTime = System.currentTimeMillis();
        do {
            nextTriggerTime = timingStrategyHandler.calculateNextTriggerTime(nextTriggerTime, timeExpression, startTime, endTime);
            if (nextTriggerTime == null) {
                break;
            }
            triggerTimeList.add(nextTriggerTime);
        } while (triggerTimeList.size() < NEXT_N_TIMES);

        if (triggerTimeList.isEmpty()) {
            return TIPS;
        }
        return triggerTimeList.stream().map(t -> DateFormatUtils.format(t, OmsConstant.TIME_PATTERN)).collect(Collectors.toList());
    }

    /**
     * 计算下次的调度时间
     *
     * @param preTriggerTime     上次触发时间(nullable)
     * @param timeExpressionType 定时表达式类型
     * @param timeExpression     表达式
     * @param startTime          起始时间(include)
     * @param endTime            结束时间(include)
     * @return 下次的调度时间
     */
    public Long calculateNextTriggerTime(Long preTriggerTime, TimeExpressionType timeExpressionType, String timeExpression, Long startTime, Long endTime) {
        if (preTriggerTime == null || preTriggerTime < System.currentTimeMillis()) {
            preTriggerTime = System.currentTimeMillis();
        }
        return getHandler(timeExpressionType).calculateNextTriggerTime(preTriggerTime, timeExpression, startTime, endTime);
    }


    /**
     * 计算下次的调度时间并检查校验规则
     *
     * @param timeExpressionType 定时表达式类型
     * @param timeExpression     表达式
     * @param startTime          起始时间(include)
     * @param endTime            结束时间(include)
     * @return 下次的调度时间
     */
    public Long calculateNextTriggerTimeWithInspection( TimeExpressionType timeExpressionType, String timeExpression, Long startTime, Long endTime) {
        Long nextTriggerTime = calculateNextTriggerTime(null, timeExpressionType, timeExpression, startTime, endTime);
        if (TimeExpressionType.INSPECT_TYPES.contains(timeExpressionType.getV()) && nextTriggerTime == null) {
            throw new PowerJobException("time expression is out of date: " + timeExpression);
        }
        return nextTriggerTime;
    }


    public void validate(TimeExpressionType timeExpressionType, String timeExpression, Long startTime, Long endTime) {
        if (endTime != null) {
            if (endTime <= System.currentTimeMillis()) {
                throw new PowerJobException("lifecycle is out of date!");
            }
            if (startTime != null && startTime > endTime) {
                throw new PowerJobException("lifecycle is invalid! start time must earlier then end time.");
            }
        }
        getHandler(timeExpressionType).validate(timeExpression);
    }


    private TimingStrategyHandler getHandler(TimeExpressionType timeExpressionType) {
        TimingStrategyHandler timingStrategyHandler = strategyContainer.get(timeExpressionType);
        if (timingStrategyHandler == null) {
            throw new PowerJobException("No matching TimingStrategyHandler for this TimeExpressionType:" + timeExpressionType);
        }
        return timingStrategyHandler;
    }

}

校验控制器
注意:RequiredArgsConstructor注解
@RequiredArgsConstructor 是 Lombok 库中的一个注解,用于自动生成构造函数。这个注解的作用是在编译时自动生成一个构造函数,该构造函数只包含那些以 final 修饰或者以 @NonNull 注解的未初始化的字段。这样,Lombok 会为你创建一个构造函数,这些字段将成为构造函数的参数,并在构造函数内部被初始化。
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ExampleService {
private final AnotherService anotherService;
private final SomeRepository someRepository;
// … 方法实现
}
在这个例子中,ExampleService 类使用了 @RequiredArgsConstructor 注解,它有一个 final 字段 anotherService 和 someRepository。Spring 会自动识别这个构造函数,并在创建 ExampleService 的实例时自动注入 AnotherService 和 SomeRepository 的实例。
请注意,为了使自动注入工作,AnotherService 和 SomeRepository 也应该是 Spring 管理的 beans,通常通过在它们各自的类上使用 @Service、@Repository 或 @Component 等注解来标记。
使用 Lombok 的 @RequiredArgsConstructor 可以让您的代码更加简洁,避免了手动编写构造函数和注解每个构造参数的麻烦。然而,您应该谨慎使用 Lombok,确保团队成员都了解其工作原理,并且在项目中一致使用,以避免潜在的维护问题。

import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.powerjob.common.enums.TimeExpressionType;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.core.scheduler.TimingStrategyService;

import java.util.List;
@RestController
@RequestMapping("/validate")
@RequiredArgsConstructor
public class ValidateController {

    private final TimingStrategyService timingStrategyService;

    @GetMapping("/timeExpression")
    public ResultDTO<List<String>> checkTimeExpression(TimeExpressionType timeExpressionType,
                                                       String timeExpression,
                                                       @RequestParam(required = false) Long startTime,
                                                       @RequestParam(required = false) Long endTime
    ) {
        try {
            timingStrategyService.validate(timeExpressionType, timeExpression, startTime, endTime);
            return ResultDTO.success(timingStrategyService.calculateNextTriggerTimes(timeExpressionType, timeExpression, startTime, endTime));
        } catch (Exception e) {
            return ResultDTO.success(Lists.newArrayList(ExceptionUtils.getMessage(e)));
        }
    }
}

响应类ResultDTO

@Getter
@Setter
@ToString
public class ResultDTO<T> implements PowerSerializable {

    private boolean success;
    private T data;
    private String message;

    public static <T> ResultDTO<T> success(T data) {
        ResultDTO<T> r = new ResultDTO<>();
        r.success = true;
        r.data = data;
        return r;
    }

    public static <T> ResultDTO<T> failed(String message) {
        ResultDTO<T> r = new ResultDTO<>();
        r.success = false;
        r.message = message;
        return r;
    }

    public static <T> ResultDTO<T> failed(Throwable t) {
        return failed(ExceptionUtils.getStackTrace(t));
    }

}

3-2、quartz

引入依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

CornValidatorUtil.java

import org.quartz.CronExpression;

import java.text.ParseException;
public class CornValidatorUtil {

    public static String validateCornExpression(String cornExpression) {
        try {
            // 尝试解析Cron表达式
            new CronExpression(cornExpression);
            return null; // 如果没有异常抛出,则表达式有效
        } catch (ParseException e) {
            // 解析失败,返回错误信息
            return e.getMessage();
        }
    }

//    public static void main(String[] args) {
//        String cornExpression = "0 0 0/1 0 0 ? ";
//        String errorMessage = validateCornExpression(cornExpression);
//        if (errorMessage != null) {
//            System.out.println("Cron表达式无效: " + errorMessage);
//        } else {
//            System.out.println("Cron表达式有效");
//        }
//    }
}
  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: @Scheduled是Spring框架中用于实现定时任务的注解,常用于后台定时任务、定时数据备份等场景。而有时候我们可能需要对定时任务的cron表达式动态修改,比如需要根据业务需求动态改变定时任务的执行时间,在这种情况下,@Scheduled的cron参数就能够派上用场了。 动态修改@Scheduled注解的cron参数需要使用Java反射机制,具体步骤如下: 1.获取定时任务所在类的Class对象; 2.通过Class对象获取注解方法; 3.获取注解方法上的cron参数; 4.通过反射修改cron参数值; 5.完成cron参数值的动态修改。 需要注意的是,cron参数的修改只会在下一次定时任务执行时生效,不会影响已经在执行的定时任务。 总体而言,动态修改@Scheduled注解的cron参数是一项非常实用的功能,它让我们能够更灵活地控制定时任务的执行时间,提高系统的可维护性和稳定性。 ### 回答2: 在Spring Boot中,我们可以通过使用`@Scheduled`注解来创建定时任务。`@Scheduled`注解有一个`cron`参数,它接受一个cron表达式,用于设置定时任务的执行时间。但是,有时我们需要在运行时动态修改定时任务的cron表达式,以便能够实现更灵活和适应变化的功能。 一种常见的方法是使用`ScheduledTaskRegistrar`接口,它允许我们在运行时注册并配置定时任务。我们可以实现`SchedulingConfigurer`接口,并在`configureTasks()`方法中使用`ScheduledTaskRegistrar`来添加新的定时任务或修改现有的定时任务。 首先,让我们看一个简单的示例,它创建一个每5秒执行一次的定时任务,并在每次执行时输出当前时间戳。 ```java @Component public class MyTask { @Scheduled(cron = "*/5 * * * * *") public void execute() { System.out.println("Current Timestamp: " + System.currentTimeMillis()); } } ``` 要在运行时动态修改此任务的cron表达式,我们可以在`SchedulingConfigurer`实现类中注入`MyTask`并使用`ScheduledTaskRegistrar`来注册该任务。 ```java @Configuration @EnableScheduling public class AppConfig implements SchedulingConfigurer { @Autowired private MyTask myTask; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addCronTask( () -> myTask.execute(), "0/10 * * * * *" // 新的cron表达式 ); } } ``` 在上面的示例中,我们使用`addCronTask()`方法向`ScheduledTaskRegistrar`注册新的定时任务。第一个参数是一个lambda表达式,它将被调用以执行实际的任务。第二个参数是新的cron表达式。 现在,每隔10秒钟,定时任务将被调用一次,而不是每5秒钟。我们可以使用类似的方法修改现有的定时任务的cron表达式或删除定时任务。 总之,我们可以在Spring Boot中通过使用`ScheduledTaskRegistrar`接口来动态修改定时任务的cron表达式,以实现更灵活和适应变化的功能。 ### 回答3: 在使用springboot的时候,比如我们需要定时执行某个任务,一般会用到注解 @Scheduled。这个注解是非常方便的,使用起来非常简单。但是有时候我们需要对已经设置好的定时任务进行一些修改,比如修改执行时间,那么该怎么办呢? 事实上,@Scheduled定时任务动态修改cron参数是非常简单的。我们只需要在修改完参数后重新调用任务即可。以下是具体的步骤: 1. 在需要定时执行的方法上加上注解@Scheduled(cron = "${cron}"),cron表示定时任务的表达式。 2. 在properties或者yml配置文件中设置定时任务表达式,比如cron = "0 0/5 * * * ?",表示每5分钟执行一次。 3. 在程序运行时,如果需要更改cron表达式,需要动态的获取对应的属性配置,然后重新设置。比如在配置文件中加上cron1属性,表示备用的cron表达式,然后在代码中监听这个属性的变化,如果发生变化,就重新执行任务。 4. 当然,如果不想动态修改cron表达式,也可以通过修改配置文件的方式来达到效果,修改完后重新启动应用即可。 综上所述,@Scheduled定时任务动态修改cron参数非常简单,只需要按照以上的步骤即可。通过这种方式,可以非常方便的对定时任务进行调整,使得系统更加灵活可控。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值