本文参考自:https://blog.csdn.net/lonelymanontheway/article/details/108391851
Cron表达式是一个字符串,以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,即两种语法格式:
一般情况下,第七个字符Year可省略不写。
除此以外,也有五段表达式的,如crontab,没有秒的概念。绝大多数情况下,都是6个字符,本文讨论的也是6个字符。
知识点:
很多,因为cron表达式有各种不同的类型,不同类型直接还是有一些细微的差别。
https://www.bejson.com/othertools/cron/
org.quartz.CronExpression
依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
官网:http://cron-parser.com/
https://awesomeopensource.com/project/jmrozanec/cron-utils
https://www.openhub.net/p/cron-utils
maven
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
<version>9.1.5</version>
</dependency>
参考下面
checkValid
方法。
构建cron表达式
如下图所示一个实际需求,需实现定时调度,其中周几、小时、分钟可配置化:
基于cron-utils写的一个工具类;
import com.cronutils.model.Cron;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import com.sun.deploy.util.StringUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class CronUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(CronUtil.class);
private static final String QUESTION = "?";
private static final String ASTERISK = "*";
private static final String COMMA = ",";
/**
* 替换 分钟、小时、日期、星期
*/
private static final String ORIGINAL_CRON = "0 %s %s %s * %s";
/**
* 检查cron表达式的合法性
*
* @param cron cron exp
* @return true if valid
*/
public static boolean checkValid(String cron) {
try {
// SPRING应该是使用最广泛的类型,但假若任务调度依赖于xxl-job平台,则需要调整为CronType.QUARTZ
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING);
CronParser parser = new CronParser(cronDefinition);
parser.parse(cron);
} catch (IllegalArgumentException e) {
LOGGER.error(String.format("cron=%s not valid", cron));
return false;
}
return true;
}
public static String buildCron(List<Integer> minutes, List<Integer> hours, List<Integer> weekdays) {
String minute;
if (minutes.equals(CronUtil.getInitMinutes())) {
minute = ASTERISK;
} else {
minute = StringUtils.join(minutes, COMMA);
}
String hour;
if (hours.equals(CronUtil.getInitHours())) {
hour = ASTERISK;
} else {
hour = StringUtils.join(hours, COMMA);
}
String weekday;
if (weekdays.equals(CronUtil.getInitWeekdays())) {
weekday = QUESTION;
} else {
weekday = StringUtils.join(weekdays, COMMA);
}
// 重点:星期和日字段冲突,判断周日的前端输入
if (weekday.equals(QUESTION)) {
return String.format(ORIGINAL_CRON, minute, hour, ASTERISK, weekday);
} else {
return String.format(ORIGINAL_CRON, minute, hour, QUESTION, weekday);
}
}
/**
* 解析db cron expression展示到前端
*
* @param cron cron
* @return minutes/hours/weekdays
*/
public static CustomCronField parseCon(String cron) {
if (!CronUtil.checkValid(cron)) {
return null;
}
List<String> result = Arrays.asList(cron.trim().split(" "));
CustomCronField field = new CustomCronField();
if (result.get(1).contains(COMMA)) {
field.setMinutes(Arrays.stream(result.get(1).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
} else if (result.get(1).equals(ASTERISK)) {
field.setMinutes(CronUtil.getInitMinutes());
} else {
field.setMinutes(new ArrayList(Integer.parseInt(result.get(1))));
}
if (result.get(2).contains(COMMA)) {
field.setHours(Arrays.stream(result.get(2).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
} else if (result.get(2).equals(ASTERISK)) {
field.setHours(CronUtil.getInitHours());
} else {
field.setHours(new ArrayList(Integer.parseInt(result.get(2))));
}
if (result.get(5).contains(COMMA)) {
field.setWeekdays(Arrays.stream(result.get(5).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
} else if (result.get(5).equals(QUESTION)) {
field.setWeekdays(CronUtil.getInitWeekdays());
} else {
field.setWeekdays(new ArrayList(Integer.parseInt(result.get(5))));
}
return field;
}
private static List<Integer> initArray(Integer num) {
List<Integer> result =new ArrayList<>(num);
for (int i = 0; i <= num; i++) {
result.add(i);
}
return result;
}
private static List<Integer> getInitMinutes() {
return CronUtil.initArray(59);
}
private static List<Integer> getInitHours() {
return CronUtil.initArray(23);
}
private static List<Integer> getInitWeekdays() {
return CronUtil.initArray(7).subList(1, 8);
}
@Data
public static class CustomCronField {
private List<Integer> minutes;
private List<Integer> hours;
private List<Integer> weekdays;
}
}
/**
* 根据cron表达式解析最近几次的执行时间
* @param cronStr
* @param num
* @return
*/
public static List<String> getExecutionTimeByNum(String cronStr, Integer num) {
CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING));
Cron cron = parser.parse(cronStr);
ExecutionTime time = ExecutionTime.forCron(cron);
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime next = getNext(time, now);
List<ZonedDateTime> timeList = new ArrayList<>(num);
timeList.add(next);
for (int i = 1; i < num; i++) {
next = getNext(time, next);
timeList.add(next);
}
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
List<String> resultList = new ArrayList<>(num);
for (ZonedDateTime item : timeList) {
String result = item.format(format);
resultList.add(result);
}
return resultList;
}
private static ZonedDateTime getNext(ExecutionTime time, ZonedDateTime current) {
return time.nextExecution(current).get();
}
/**
* 判断cron是否是按天执行
* 如果按天执行cron需以(* * ?)结尾
* @return true 是以* * ?结尾
*/
public static Boolean datasetCron(String cron) {
return StringUtils.isNotBlank(cron) && cron.matches(".* \\* \\* \\?$");
}
// 判断是否按天更新
boolean day = "*".equals(dataset.getCronExp().split(" ")[3]);