import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
/**
* 定时任务工具
*
* @author zhangxuetu
* @date 2023-01-09
*/
@Component
@Slf4j
public class ScheduleUtil {
/*** ID对应的定时任务数据 */
private static final Map<Object, Data> DATA_MAP = new HashMap<>();
/*** 任务ID */
private static long id = 0L;
/*** 定时任务线程池 */
private static ThreadPoolTaskScheduler threadPoolTaskScheduler = null;
/**
* 获取定时任务线程池对象
*/
private static ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
if (threadPoolTaskScheduler == null) {
threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.initialize();
}
return threadPoolTaskScheduler;
}
/**
* 执行任务的数据
*/
private static class Data {
private final Object id;
private final ScheduledFuture<?> scheduledFuture;
private final String cron;
private final Runnable runnable;
public Data(Object id, ScheduledFuture<?> scheduledFuture, String cron, Runnable runnable) {
this.id = id;
this.scheduledFuture = scheduledFuture;
this.cron = cron;
this.runnable = runnable;
}
public Object getId() {
return id;
}
public ScheduledFuture<?> getScheduledFuture() {
return scheduledFuture;
}
public String getCron() {
return cron;
}
public Runnable getRunnable() {
return runnable;
}
}
/**
* 获取这个ID的定时任务数据
*
* @param id ID值
* @return 返回定时任务数据对象
*/
private static <T> Data getData(T id) {
return DATA_MAP.get(id);
}
/**
* 是否有这个定时任务
*
* @param id ID值
* @param <T> ID值类型
* @return 返回是否有这个定时任务
*/
public static <T> boolean hasScheduledFuture(T id) {
return DATA_MAP.containsKey(id);
}
/**
* 添加自定义定时任务
*
* @param cron 定时任务表达式
* @param runnable 执行功能
* @return 返回这次定时任务的 ID 值
*/
public static long addTask(String cron, Runnable runnable) throws IllegalArgumentException {
// 直到没有相同的 ID 的时候才添加
do {
id++;
} while (DATA_MAP.containsKey(id));
return addTask(id, cron, runnable);
}
/**
* 添加自定义定时任务
*
* @param id 自定义设置的 ID 值
* @param cron 定时任务表达式
* @param runnable 执行功能
* @throws IllegalArgumentException 参数异常
*/
public static <T> T addTask(T id, String cron, Runnable runnable) throws IllegalArgumentException {
if (id == null) {
throw new IllegalArgumentException("ID 值不能为 null!");
}
if (hasScheduledFuture(id)) {
throw new IllegalArgumentException("已添加过这个 ID 的定时任务!");
}
log.info("新增定时任务:id = {}, cron = {}", id, cron);
ScheduledFuture<?> schedule = getThreadPoolTaskScheduler().schedule(runnable, triggerContext -> new CronTrigger(cron).nextExecutionTime(triggerContext));
Data data = new Data(id, schedule, cron, runnable);
DATA_MAP.put(id, data);
return id;
}
/**
* 停止定时任务
*
* @param id 定时任务的ID
* @return 返回是否停止成功,如果不成功则代表没有这个任务
*/
public static <T> boolean stop(T id) throws NullPointerException {
if (hasScheduledFuture(id)) {
ScheduledFuture<?> scheduledFuture = getData(id).getScheduledFuture();
scheduledFuture.cancel(true);
DATA_MAP.remove(id);
log.info("移除定时任务:id = {}", id);
return true;
}
return false;
}
/**
* 更新定时任务
*
* @param id 定时任务的ID
* @param cron 定时任务表达式
* @param <T> ID值的类型
*/
public static <T> boolean update(T id, String cron) {
if (hasScheduledFuture(id)) {
Runnable runnable = getData(id).getRunnable();
stop(id);
addTask(id, cron, runnable);
return true;
}
return false;
}
/**
* 更新定时任务
*
* @param id 定时任务的ID
* @param runnable 执行的功能
* @param <T> ID值的类型
*/
public static <T> boolean update(T id, Runnable runnable) {
if (hasScheduledFuture(id)) {
String cron = getData(id).getCron();
stop(id);
addTask(id, cron, runnable);
return true;
}
return false;
}
/**
* 更新定时任务
*
* @param id 定时任务的ID
* @param cron 定时任务表达式
* @param runnable 运行的方法
* @param <T> ID值的类型
*/
public static <T> boolean update(T id, String cron, Runnable runnable) {
if (hasScheduledFuture(id)) {
stop(id);
addTask(id, cron, runnable);
return true;
}
return false;
}
public static String cron(String time, List<? extends Comparable<?>> weeks) {
return cron(Arrays.asList(time), weeks);
}
/**
* 转为定时任务的表达式
*
* @param times 时间格式字符串或LocalTime列表(格式:时:分:秒)
* @param weeks 周列表
* @return 返回定时任务的表达式
*/
public static String cron(List<?> times, List<? extends Comparable<?>> weeks) {
if (times == null || times.isEmpty()) {
throw new IllegalArgumentException("times 参数为空!");
}
if (weeks == null || weeks.isEmpty()) {
throw new IllegalArgumentException("weeks 参数为空!");
}
List<String> seconds = new ArrayList<>();
List<String> minutes = new ArrayList<>();
List<String> hours = new ArrayList<>();
for (Object time : times) {
LocalTime localTime = LocalTime.parse(time.toString());
seconds.add(String.valueOf(localTime.getSecond()));
minutes.add(String.valueOf(localTime.getMinute()));
hours.add(String.valueOf(localTime.getHour()));
}
return String.format("%s %s %s ? * %s",
String.join(",", seconds),
String.join(",", minutes),
String.join(",", hours),
String.join(",", weeks.toArray(new String[0]))
);
}
}