java 实现动态定时任务

  • Post author:
  • Post category:java


**



一:corn时间转换工具类




```java
package com.ypkj.provider.utils;

import com.ypkj.provider.cron.CronPatternConstant;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author cgz
 * @version 1.0
 * @date 2023/5/23 11:02
 */
public enum GenerateCronUtil {
    /**
     * 单例
     */
    INSTANCE;

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd HH:mm:ss");

    private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    private final SimpleDateFormat weekFormat = new SimpleDateFormat("E");

    public static void generateCronByPeriodAndTime(String formatTime) {
    }

    /**
     * 根据执行周期和初次执行时间,生成cron表达式
     *
     * @param period    执行周期
     * @param beginTime 初次执行时间
     * @return cron表达式
     */
    public String generateCronByPeriodAndTime(PeriodEnum period, String beginTime) {
        Date parsedDate;
        try {
            if(period.name().equals("WEEKLY")){
                parsedDate = simpleDateFormat.parse(beginTime);
            }else{
                parsedDate = dateFormat.parse(beginTime);
            }
        } catch (ParseException e) {
            return "error";
        }
        String[] dateAndTime = beginTime.split(" ");
        String date = dateAndTime[0];
        String time = dateAndTime[1];
        String[] splitDate = date.split("/");
        String month = splitDate[0];
        String day = splitDate[1];
        String[] splitTime = time.split(":");
        String hour = splitTime[0];
        String minute = splitTime[1];
        String second = splitTime[2];
        String cron = "";
        switch (period) {
            case ONCE:
                cron = String.format(CronPatternConstant.ONCE_CRON_PATTERN, second, minute, hour, day, month);
                break;
            case DAILY:
                cron = String.format(CronPatternConstant.DAILY_CRON_PATTERN, second, minute, hour);
                break;
            case WEEKLY:
                String week = weekFormat.format(parsedDate);
                String weekCode = WeekEnum.nameOf(week).getCode();
                cron = String.format(CronPatternConstant.WEEKLY_CRON_PATTERN, second, minute, hour, weekCode);
                break;
            case MONTHLY:
                cron = String.format(CronPatternConstant.MONTHLY_CRON_PATTERN, second, minute, hour, day);
                break;
            default:
                break;
        }
        return cron;
    }

    public static void main(String[] args) {
        String time = "2023/05/30 12:10:20";

        String weeklyCron = GenerateCronUtil.INSTANCE.generateCronByPeriodAndTime(PeriodEnum.WEEKLY, time);
        String monthlyCron = GenerateCronUtil.INSTANCE.generateCronByPeriodAndTime(PeriodEnum.MONTHLY, time);

        System.out.println("每周执行cron:" + weeklyCron);

    }




}


package com.ypkj.provider.utils;

/**
 * 执行周期枚举
 * @author cgz
 * @version 1.0
 * @date 2023/5/23 10:59
 */
public enum PeriodEnum {
    /**
     * 执行一次
     */
    ONCE,
    /**
     * 每天
     */
    DAILY,
    /**
     * 每周
     */
    WEEKLY,
    /**
     * 每月
     */
    MONTHLY

}

package com.ypkj.provider.utils;

import com.google.common.base.Strings;
import com.ypkj.common.utils.StringUtils;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * * 星期枚举
 * @author cgz
 * @version 1.0
 * @date 2023/5/23 11:00
 */
@Getter
public enum WeekEnum {
    /**
     * 周一
     */
    Monday("星期一", "Mon"),
    /**
     * 周二
     */
    Tuesday("星期二", "Tue"),
    /**
     * 周三
     */
    Wednesday("星期三", "Wed"),
    /**
     * 周四
     */
    Thursday("星期四", "Thu"),
    /**
     * 周五
     */
    Friday("星期五", "Fri"),
    /**
     * 周六
     */
    Saturday("星期六", "Sat"),
    /**
     * 周日
     */
    Sunday("星期日", "Sun"),
    ;

    private static final Map<String, WeekEnum> CODE_ROLE_MAP = new HashMap<>();

    private static final Map<String, WeekEnum> NAME_ROLE_MAP = new HashMap<>();

    static {
        for (WeekEnum type : WeekEnum.values()) {
            NAME_ROLE_MAP.put(type.name, type);
            CODE_ROLE_MAP.put(type.code, type);
        }
    }

    private final String name;

    private final String code;

    WeekEnum(final String name, final String code) {
        this.name = name;
        this.code = code;
    }

    /**
     * to WeekEnum by code.
     *
     * @param code code
     * @return WeekEnum
     */
    public static WeekEnum codeOf(final String code) {
        if (StringUtils.isBlank(code)) {
            return Sunday;
        }
        WeekEnum matchType = CODE_ROLE_MAP.get(code);
        return Objects.isNull(matchType) ? Sunday : matchType;
    }

    /**
     * to WeekEnum by name.
     *
     * @param name name
     * @return WeekEnum
     */
    public static WeekEnum nameOf(final String name) {
        if (Strings.isNullOrEmpty(name)) {
            return Sunday;
        }
        WeekEnum matchType = NAME_ROLE_MAP.get(name);
        return Objects.isNull(matchType) ? Sunday : matchType;
    }

}

package com.ypkj.provider.cron;

/**
 *  cron表达式模板
 * @author cgz
 * @version 1.0
 * @date 2023/5/23 11:03
 */
public interface CronPatternConstant {
    /**
     * 执行单次cron表达式模板
     * eg: 59 59 23 1 12 ? 2022 (2022-12-01 23:59:59执行一次)
     */
     String ONCE_CRON_PATTERN = "%s %s %s %s %s ?";
    /**
     * 每天执行cron表达式模板
     * eg: 59 59 23 * * ? (每日23:59:59执行)
     */
    String DAILY_CRON_PATTERN = "%s %s %s * * ?";
    /**
     * 每周执行cron表达式模板
     * eg: 59 59 23 ? * Fri (每周五23:59:59执行)
     */
    String WEEKLY_CRON_PATTERN = "%s %s %s ? * %s";
    /**
     * 每月执行cron表达式模板
     * eg: 59 59 23 8 * ? (每月8号23:59:59执行)
     */
    String MONTHLY_CRON_PATTERN = "%s %s %s %s * ?";

}






二:定时任务工具类



import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;

/**
 * @ClassName: ScheduleUtils
 * @Description: 定时任务类
 * @author lixk
 * @date 2017年8月16日 下午1:27:13
 * @version [1.0, 2017年8月16日]
 * @since version 1.0
 */
@Slf4j
public class ScheduleUtils {

	// task集合。Task里面包含ScheduledFuture,ScheduledFuture是延时执行的关键类
	private static final Map<Integer, Task> TASK_MANAGER = new HashMap<Integer, Task>();
	// 定时器线程池
	private static final ScheduledExecutorService EXECUTOR_POOL = Executors.newScheduledThreadPool(10);
	// 定时任务队列
	private static final BlockingQueue<Task> TASK_QUEUE = new LinkedBlockingQueue<Task>();

	// 静态初始化方法。用于跑定时任务队列,队列里有任务就取出来执行。
	static {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		executor.execute(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Task task = TASK_QUEUE.take();
						// 任务有效,则执行任务
						if (task.isEffective()) {
							task.execute();
						}
					} catch (Exception e) {
						log.error("定时任务执行异常:", e);
					}
				}
			}
		});
		executor.shutdown();
	}

	/**
	 * 
	 * @Title: add
	 * @Description: 添加动态定时任务
	 * @param JobDto
	 */
	public synchronized static void add(JobDto JobDto) throws Exception {
		// 终结执行中的任务
		cancel(JobDto.getId());

		Task task = new Task(TASK_QUEUE, EXECUTOR_POOL, JobDto.getClassName(), JobDto.getMethodName(), JobDto.getCron(), JobDto);
		TASK_MANAGER.put(JobDto.getId(), task);
		// 将任务加入队列
		TASK_QUEUE.put(task);
	}

	/**
	 * 
	 * @Title: cancel
	 * @Description: 取消动态定时任务
	 * @param taskId
	 */
	public synchronized static void cancel(Integer taskId) {
		if (taskId == null) {
			return;
		}
		Task task = TASK_MANAGER.get(taskId);
		if (task != null) {
			// 关闭任务,停止任务线程
			task.setEffective(false);
			ScheduledFuture<?> future = task.getScheduledFuture();
			if (future != null) {
				future.cancel(true);
			}
		}
		TASK_MANAGER.remove(taskId);
	}

	/**
	 * 
	 * @ClassName: Task
	 * @Description: 任务内部类
	 * @author lixk
	 * @date 2017年8月16日 下午7:38:44
	 * @version [1.0, 2017年8月16日]
	 * @since version 1.0
	 */
	public static class Task {
		private BlockingQueue<Task> queue; // 任务队列
		private CronTrigger trigger; // cron触发器
		private ScheduledExecutorService executor; // 定时器线程池
		private Class<?> clazz; // 反射类名
		private Object targetObject; // 反射对象
		private Method method; // 反射方法
		private Task self; // task对象自己
		private JobDto jobDto; // 任务对象。里面可放自定义业务数据。
		private ScheduledFuture<?> scheduledFuture; // task对象的future
		private boolean effective = true; // task对象状态

		public Task(BlockingQueue<Task> queue, ScheduledExecutorService executor,
					String className, String methodName, String cron, JobDto jobDto) throws Exception {
			this.queue = queue;
			this.executor = executor;
			this.trigger = new CronTrigger(cron);
			this.clazz = Class.forName(className);
//			this.targetObject = clazz.newInstance(); // 此处原本是反射获取的对象,由于获取到的对象里面的依赖注入为null,所以换成下面的getBean方式来获取对象
			this.targetObject = SpringContextHolder.getBean(clazz);
			// 调用目标方法。xxx.class为传入参数对象(可根据自己的业务进行调整)。
			this.method = clazz.getDeclaredMethod(methodName, JobDto.class);
			this.self = this;
			this.jobDto = jobDto;
		}

		public void execute() throws Exception {
			Date now = new Date();
			// 感觉cron表达式没什么必要...,反正到这里都要转成delay(等待时间),后续应该可以优化成直接用执行时间来搞,研究时间有限,先这样
			long delay = trigger.next(now).getTime() - now.getTime(); // 等待时间

			// schedule(Runnable command, long delay, TimeUnit unit) 创建并执行在给定延迟后启用的一次性操作。
			this.scheduledFuture = executor.schedule(new Runnable() {
				@Override
				public void run() {
					try {
						// 执行任务
						log.warn("动态定时器执行任务,jobDto={}", JSON.toJSONString(jobDto));
						method.invoke(targetObject, jobDto);
					} catch (Exception e) {
						log.error("定时任务执行异常:", e);
					}
					// 可以根据自己业务判断是否将任务重新加入队列
//					finally {
//						// 把当前任务加入队列
//						try {
//							queue.put(self);
//						} catch (InterruptedException e) {
//							logger.error("添加定时任务到队列异常:", e);
//						}
//					}
				}
			}, delay, TimeUnit.MILLISECONDS);

		}

		public ScheduledFuture<?> getScheduledFuture() {
			return scheduledFuture;
		}

		public boolean isEffective() {
			return effective;
		}

		public void setEffective(boolean effective) {
			this.effective = effective;
		}
	}

}

/**************************************** cron表达式解析工具类 *******************************************/

/**
 * Date sequence generator for a
 * <a href="http://www.manpagez.com/man/5/crontab/">Crontab pattern</a>,
 * allowing clients to specify a pattern that the sequence matches.
 *
 * <p>
 * The pattern is a list of six single space-separated fields: representing
 * second, minute, hour, day, month, weekday. Month and weekday names can be
 * given as the first three letters of the English names.
 *
 * <p>
 * Example patterns:
 * <ul>
 * <li>"0 0 * * * *" = the top of every hour of every day.</li>
 * <li>"*&#47;10 * * * * *" = every ten seconds.</li>
 * <li>"0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.</li>
 * <li>"0 0 6,19 * * *" = 6:00 AM and 7:00 PM every day.</li>
 * <li>"0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every
 * day.</li>
 * <li>"0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays</li>
 * <li>"0 0 0 25 12 ?" = every Christmas Day at midnight</li>
 * </ul>
 *
 * @author Dave Syer
 * @author Juergen Hoeller
 * @since 3.0
 * @see CronTrigger
 */
class CronTrigger {

	private final String expression;

	private final TimeZone timeZone;

	private final BitSet months = new BitSet(12);

	private final BitSet daysOfMonth = new BitSet(31);

	private final BitSet daysOfWeek = new BitSet(7);

	private final BitSet hours = new BitSet(24);

	private final BitSet minutes = new BitSet(60);

	private final BitSet seconds = new BitSet(60);

	/**
	 * Construct a {@link CronTrigger} from the pattern provided, using the
	 * default {@link TimeZone}.
	 * 
	 * @param expression
	 *            a space-separated list of time fields
	 * @throws IllegalArgumentException
	 *             if the pattern cannot be parsed
	 * @see TimeZone#getDefault()
	 */
	public CronTrigger(String expression) {
		this(expression, TimeZone.getDefault());
	}

	/**
	 * Construct a {@link CronTrigger} from the pattern provided, using the
	 * specified {@link TimeZone}.
	 * 
	 * @param expression
	 *            a space-separated list of time fields
	 * @param timeZone
	 *            the TimeZone to use for generated trigger times
	 * @throws IllegalArgumentException
	 *             if the pattern cannot be parsed
	 */
	public CronTrigger(String expression, TimeZone timeZone) {
		this.expression = expression;
		this.timeZone = timeZone;
		parse(expression);
	}

	/**
	 * Return the cron pattern that this sequence generator has been built for.
	 */
	String getExpression() {
		return this.expression;
	}

	/**
	 * Get the next {@link Date} in the sequence matching the Cron pattern and
	 * after the value provided. The return value will have a whole number of
	 * seconds, and will be after the input value.
	 * 
	 * @param date
	 *            a seed value
	 * @return the next value matching the pattern
	 */
	public Date next(Date date) {
		/*
		 * The plan:
		 * 
		 * 1 Start with whole second (rounding up if necessary)
		 * 
		 * 2 If seconds match move on, otherwise find the next match: 2.1 If
		 * next match is in the next minute then roll forwards
		 * 
		 * 3 If minute matches move on, otherwise find the next match 3.1 If
		 * next match is in the next hour then roll forwards 3.2 Reset the
		 * seconds and go to 2
		 * 
		 * 4 If hour matches move on, otherwise find the next match 4.1 If next
		 * match is in the next day then roll forwards, 4.2 Reset the minutes
		 * and seconds and go to 2
		 */

		Calendar calendar = new GregorianCalendar();
		calendar.setTimeZone(this.timeZone);
		calendar.setTime(date);

		// First, just reset the milliseconds and try to calculate from there...
		calendar.set(Calendar.MILLISECOND, 0);
		long originalTimestamp = calendar.getTimeInMillis();
		doNext(calendar, calendar.get(Calendar.YEAR));

		if (calendar.getTimeInMillis() == originalTimestamp) {
			// We arrived at the original timestamp - round up to the next whole
			// second and try again...
			calendar.add(Calendar.SECOND, 1);
			doNext(calendar, calendar.get(Calendar.YEAR));
		}

		return calendar.getTime();
	}

	private void doNext(Calendar calendar, int dot) {
		List<Integer> resets = new ArrayList<Integer>();

		int second = calendar.get(Calendar.SECOND);
		List<Integer> emptyList = Collections.emptyList();
		int updateSecond = findNext(this.seconds, second, calendar, Calendar.SECOND, Calendar.MINUTE, emptyList);
		if (second == updateSecond) {
			resets.add(Calendar.SECOND);
		}

		int minute = calendar.get(Calendar.MINUTE);
		int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets);
		if (minute == updateMinute) {
			resets.add(Calendar.MINUTE);
		} else {
			doNext(calendar, dot);
		}

		int hour = calendar.get(Calendar.HOUR_OF_DAY);
		int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets);
		if (hour == updateHour) {
			resets.add(Calendar.HOUR_OF_DAY);
		} else {
			doNext(calendar, dot);
		}

		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
		int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
		int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets);
		if (dayOfMonth == updateDayOfMonth) {
			resets.add(Calendar.DAY_OF_MONTH);
		} else {
			doNext(calendar, dot);
		}

		int month = calendar.get(Calendar.MONTH);
		int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets);
		if (month != updateMonth) {
			if (calendar.get(Calendar.YEAR) - dot > 4) {
				throw new IllegalArgumentException("Invalid cron expression \"" + this.expression + "\" led to runaway search for next trigger");
			}
			doNext(calendar, dot);
		}

	}

	private int findNextDay(Calendar calendar, BitSet daysOfMonth, int dayOfMonth, BitSet daysOfWeek, int dayOfWeek, List<Integer> resets) {

		int count = 0;
		int max = 366;
		// the DAY_OF_WEEK values in java.util.Calendar start with 1 (Sunday),
		// but in the cron pattern, they start with 0, so we subtract 1 here
		while ((!daysOfMonth.get(dayOfMonth) || !daysOfWeek.get(dayOfWeek - 1)) && count++ < max) {
			calendar.add(Calendar.DAY_OF_MONTH, 1);
			dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
			dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
			reset(calendar, resets);
		}
		if (count >= max) {
			throw new IllegalArgumentException("Overflow in day for expression \"" + this.expression + "\"");
		}
		return dayOfMonth;
	}

	/**
	 * Search the bits provided for the next set bit after the value provided,
	 * and reset the calendar.
	 * 
	 * @param bits
	 *            a {@link BitSet} representing the allowed values of the field
	 * @param value
	 *            the current value of the field
	 * @param calendar
	 *            the calendar to increment as we move through the bits
	 * @param field
	 *            the field to increment in the calendar (@see {@link Calendar}
	 *            for the static constants defining valid fields)
	 * @param lowerOrders
	 *            the Calendar field ids that should be reset (i.e. the ones of
	 *            lower significance than the field of interest)
	 * @return the value of the calendar field that is next in the sequence
	 */
	private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List<Integer> lowerOrders) {
		int nextValue = bits.nextSetBit(value);
		// roll over if needed
		if (nextValue == -1) {
			calendar.add(nextField, 1);
			reset(calendar, Arrays.asList(field));
			nextValue = bits.nextSetBit(0);
		}
		if (nextValue != value) {
			calendar.set(field, nextValue);
			reset(calendar, lowerOrders);
		}
		return nextValue;
	}

	/**
	 * Reset the calendar setting all the fields provided to zero.
	 */
	private void reset(Calendar calendar, List<Integer> fields) {
		for (int field : fields) {
			calendar.set(field, field == Calendar.DAY_OF_MONTH ? 1 : 0);
		}
	}

	// Parsing logic invoked by the constructor

	/**
	 * Parse the given pattern expression.
	 */
	private void parse(String expression) throws IllegalArgumentException {
		String[] fields = expression.split(" ");
		if (!areValidCronFields(fields)) {
			throw new IllegalArgumentException(String.format("Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
		}
		setNumberHits(this.seconds, fields[0], 0, 60);
		setNumberHits(this.minutes, fields[1], 0, 60);
		setNumberHits(this.hours, fields[2], 0, 24);
		setDaysOfMonth(this.daysOfMonth, fields[3]);
		setMonths(this.months, fields[4]);
		setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);
		if (this.daysOfWeek.get(7)) {
			// Sunday can be represented as 0 or 7
			this.daysOfWeek.set(0);
			this.daysOfWeek.clear(7);
		}
	}

	/**
	 * Replace the values in the comma-separated list (case insensitive) with
	 * their index in the list.
	 * 
	 * @return a new String with the values from the list replaced
	 */
	private String replaceOrdinals(String value, String commaSeparatedList) {
		String[] list = commaSeparatedList.split(",");
		for (int i = 0; i < list.length; i++) {
			String item = list[i].toUpperCase();
			value = value.toUpperCase().replace(item, "" + i);
		}
		return value;
	}

	private void setDaysOfMonth(BitSet bits, String field) {
		int max = 31;
		// Days of month start with 1 (in Cron and Calendar) so add one
		setDays(bits, field, max + 1);
		// ... and remove it from the front
		bits.clear(0);
	}

	private void setDays(BitSet bits, String field, int max) {
		if (field.contains("?")) {
			field = "*";
		}
		setNumberHits(bits, field, 0, max);
	}

	private void setMonths(BitSet bits, String value) {
		int max = 12;
		value = replaceOrdinals(value, "FOO,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC");
		BitSet months = new BitSet(13);
		// Months start with 1 in Cron and 0 in Calendar, so push the values
		// first into a longer bit set
		setNumberHits(months, value, 1, max + 1);
		// ... and then rotate it to the front of the months
		for (int i = 1; i <= max; i++) {
			if (months.get(i)) {
				bits.set(i - 1);
			}
		}
	}

	private void setNumberHits(BitSet bits, String value, int min, int max) {
		String[] fields = value.split(",");
		for (String field : fields) {
			if (!field.contains("/")) {
				// Not an incrementer so it must be a range (possibly empty)
				int[] range = getRange(field, min, max);
				bits.set(range[0], range[1] + 1);
			} else {
				String[] split = field.split("/");
				if (split.length > 2) {
					throw new IllegalArgumentException("Incrementer has more than two fields: '" + field + "' in expression \"" + this.expression + "\"");
				}
				int[] range = getRange(split[0], min, max);
				if (!split[0].contains("-")) {
					range[1] = max - 1;
				}
				int delta = Integer.valueOf(split[1]);
				if (delta <= 0) {
					throw new IllegalArgumentException("Incrementer delta must be 1 or higher: '" + field + "' in expression \"" + this.expression + "\"");
				}
				for (int i = range[0]; i <= range[1]; i += delta) {
					bits.set(i);
				}
			}
		}
	}

	private int[] getRange(String field, int min, int max) {
		int[] result = new int[2];
		if (field.contains("*")) {
			result[0] = min;
			result[1] = max - 1;
			return result;
		}
		if (!field.contains("-")) {
			result[0] = result[1] = Integer.valueOf(field);
		} else {
			String[] split = field.split("-");
			if (split.length > 2) {
				throw new IllegalArgumentException("Range has more than two fields: '" + field + "' in expression \"" + this.expression + "\"");
			}
			result[0] = Integer.valueOf(split[0]);
			result[1] = Integer.valueOf(split[1]);
		}
		if (result[0] >= max || result[1] >= max) {
			throw new IllegalArgumentException("Range exceeds maximum (" + max + "): '" + field + "' in expression \"" + this.expression + "\"");
		}
		if (result[0] < min || result[1] < min) {
			throw new IllegalArgumentException("Range less than minimum (" + min + "): '" + field + "' in expression \"" + this.expression + "\"");
		}
		if (result[0] > result[1]) {
			throw new IllegalArgumentException("Invalid inverted range: '" + field + "' in expression \"" + this.expression + "\"");
		}
		return result;
	}

	/**
	 * Determine whether the specified expression represents a valid cron
	 * pattern.
	 * <p>
	 * Specifically, this method verifies that the expression contains six
	 * fields separated by single spaces.
	 * 
	 * @param expression
	 *            the expression to evaluate
	 * @return {@code true} if the given expression is a valid cron expression
	 * @since 4.3
	 */
	public static boolean isValidExpression(String expression) {
		String[] fields = expression.split(" ");
		return areValidCronFields(fields);
	}

	private static boolean areValidCronFields(String[] fields) {
		return (fields != null && fields.length == 6);
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof CronTrigger)) {
			return false;
		}
		CronTrigger otherCron = (CronTrigger) other;
		return (this.months.equals(otherCron.months) && this.daysOfMonth.equals(otherCron.daysOfMonth) && this.daysOfWeek.equals(otherCron.daysOfWeek) && this.hours.equals(otherCron.hours) && this.minutes.equals(otherCron.minutes) && this.seconds.equals(otherCron.seconds));
	}

	@Override
	public int hashCode() {
		return (17 * this.months.hashCode() + 29 * this.daysOfMonth.hashCode() + 37 * this.daysOfWeek.hashCode() + 41 * this.hours.hashCode() + 53 * this.minutes.hashCode() + 61 * this.seconds.hashCode());
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + ": " + this.expression;
	}

	public static void main(String[] args) {
		CronTrigger cron = new CronTrigger("0 * * 7 * *");
		System.out.println(cron.next(new Date()));
	}

}

**



三:定时任务具体代码实现


    /**
     * 主键id
     */
    @ApiModelProperty(value = "主键id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 定时任务名称
     */
    @ApiModelProperty(value = "定时任务名称")
    private String jobName;

  
    @ApiModelProperty(value = "会议定时任务类型 1更改为会议进行中 2更改会议为已结束")
    private int jobType;

    /**
     * 会议id
     */
    @ApiModelProperty(value = "业务id")
    private Integer bussinessId;

    /**
     * 定时任务类名
     */
    @ApiModelProperty(value = "定时任务类名")
    private String className;

    /**
     * 定时任务方法名
     */
    @ApiModelProperty(value = "定时任务方法名")
    private String methodName;

    /**
     * 定时任务cron表达式
     */
    @ApiModelProperty(value = "定时任务cron表达式")
    private String cron;

    /**
     * 定时任务执行时间
     */
    @ApiModelProperty(value = "定时任务执行时间")
    private Date executeTime;

    /**
     * 创建人
     */
    @ApiModelProperty(value = "创建人")
    private String createId;

    /**
     * 修改人
     */
    @ApiModelProperty(value = "修改人")
    private String modifyId;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private Date gmtCreate;

    /**
     * 修改时间
     */
    @ApiModelProperty(value = "修改时间")
    private Date gmtModify;

    /**
     
     */
    @ApiModelProperty(value = "删除标志位:00存在,01删除")
    private String delete_flag;


动态定时器接口



    /**
     * 定时任务初始化方法,将数据库的定时任务加入线程
     */
    void init();

    /**
     * 会议创建时,新增动态定时任务,到点把会议状态改为进行中
     * @param projectMeetingId
     */
    void addDynamicTask(Integer bussinessId);

    /**
     * 修改会议时候,更改定时任务的时间
     * @param 
     */
    void updateDynamicTask(Integer bussinessId);

    /**
     *
     * @param projectMeetingId
     */
    void deleteByMeetingId(Integer bussinessId);

    /**
     *
     * @param taskId
     */
    void deleteByTaskId(Integer taskId);


@Data
public class JobDto {

    /**
     * 定时任务id
     */
    private Integer id;

    /**
     * 定时任务名称
     */
    private String jobName;

    /**
     * 自定义业务id
     */
    private Integer businessId;

    /**
     * 定时任务类名
     */
    private String className;

    /**
     * 定时任务方法名
     */
    private String methodName;

    /**
     * 定时任务cron表达式
     */
    private String cron;

    /**
     * 定时任务状态 1、启用;2、停用
     */
    private Stirng delete_flag;

}


     */
     //编辑会议时候更改定时任务
    @Override
    public void updateDynamicTask(Integer bussinessId) {
        log.info("项目会议更新定时任务start,bussinessId={}", JSON.toJSONString(bussinessId));
        if (Objects.isNull(bussinessId)) {
            log.error("更新定时任务失败,项目会议为空");
        }
        //先查询定时任务业务数据
        //查询定时任务
        //判断定时任务是否存在,如果存在修改定时任务时间,不存在则添加定时任务往数据,并且把定时任务添加到线程任务重

        try {
         
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("项目会议更新定时任务end");
    }


    @Override
    public void addDynamicTask(Integer businessId) {
        log.info("项目会议新增定时任务start,businessId={}", JSON.toJSONString(businessId));
        //先查询业务数据
        if (Objects.isNull(businessId)) {
            log.error("新增定时任务失败,项目会议为空");
        }
        //新增定时任务
        try {
           //1.添加定时任务
           //2.把定时任务添加到线程中
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("项目会议新增定时任务end");
    }



	//添加定时任务,把会议从未开始改成进行中
    public void addMeetingChangeStatusDynamicTask(Business business) throws Exception {
        //定时任务执行时间。
        Date executeTime = business.getStartTime();
        if (executeTime.getTime() <= System.currentTimeMillis()) {
            log.error("addMeetingChangeDynamicTask,当前系统时间大于等于任务执行时间");
            return;
        }
        String cron = CronUtils.getCron(executeTime);
        // 数据库新增【更改会议状态】定时任务
        ProjectMeetingDynamicTask task = new ProjectMeetingDynamicTask();
        task.setProjectMeetingId(projectMeeting.getId());
        task.setStatus((byte) 1);
        task.setCreateId(userInfoComponent.getCurrentUserId());
        task.setGmtCreate(new Date());
        task.setJobName("定时任务名"));//这个随便取
        task.setClassName("类路径")//比如:com.test.DynamicTaskServiceImpl
        task.setMethodName("方法路径");//定义方法比如:changeMeetingStatus。定时器启动的时候会根据反射去执行该方法
        task.setProjectMeetingJobType("01");
        task.setCron(cron);
        task.setExecuteTime(executeTime);
        save(task);
        // 线程新增定时任务
        addJobByTask(task);
    }
/**
     * @Title: init
     * @Description: 项目启动时,将数据库的定时任务加入线程
     */
    @Override
    @Async
    public void init() {
        // 获取定时任务列表
        List<MeetingDynamicTask> dynamicTaskList = MeetingDynamicTaskMapper
                .selectList(new LambdaQueryWrapper<MeetingDynamicTask>()
                        .eq(ProjectMeetingDynamicTask::getStatus, 1)
                        // 任务执行时间大于等于当前系统时间
                        .ge(MeetingDynamicTask::getExecuteTime, new Date()));
        new Thread(() -> {
            for (MeetingDynamicTask dynamicTask : dynamicTaskList) {
                log.info("定时任务初始化,dynamicTask={}", JSON.toJSONString(dynamicTask));
                JobDto jobDto = new JobDto();
                BeanUtils.copyProperties(dynamicTask, jobDto);
                jobDto.setBusinessId(bussinessId);
                try {
                    ScheduleUtils.add(jobDto);
                } catch (Exception e) {
                    log.error("定时任务初始化异常", e);
                }
            }
        }).start();
    }



  /**
     * @param task (往数据库添加定时时间,顺便添加定时任务)
     * @throws Exception
     */
    private void addJobByTask(MeetingDynamicTask task) throws Exception {
        JobDto jobDto = new JobDto();
        BeanUtils.copyProperties(task, jobDto);
        jobDto.setBusinessId(task.getProjectMeetingId());
        ScheduleUtils.add(jobDto);
    }

    /**
     * @param task(往数据库修改定时时间,顺便修改定时任务)
     * @throws Exception
     */
    private void updateJobByTask(MeetingDynamicTask task) throws Exception {
        //取消任务
        ScheduleUtils.cancel(task.getId());
        //新增任务
        JobDto jobDto = new JobDto();
        BeanUtils.copyProperties(task, jobDto);
        jobDto.setBusinessId(task.getProjectMeetingId());
        ScheduleUtils.add(jobDto);
    }
    /**
     * 根据业务id删除定时任务
     *
     * @param projectMeetingId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByMeetingId(Integer businessId) {
        log.info("项目会议删除定时任务start,projectMeetingId={}", JSON.toJSONString(businessId));
        // 获取定时任务
        List<MeetingDynamicTask> taskList = MeetingDynamicTaskMapper.selectList(new LambdaQueryWrapper<MeetingDynamicTask>()
                .eq(MeetingDynamicTask::businessId, businessId).eq(MeetingDynamicTask::getStatus, 1));
        taskList.forEach(task -> {
            // 数据库停用定时任务
            task.setStatus("01");
            MeetingDynamicTaskMapper.updateById(task);
            // 线程取消定时任务
            ScheduleUtils.cancel(task.getId());
        });
        log.info("项目会议删除定时任务end");
    }


 /**
     * 映射方法到定时任务中
     *
     * @param jobDto
     */
    public void changeMeetingStatus(JobDto jobDto) {
        log.info("方法changeMeetingStatus。start,jobDto={}", JSON.toJSONString(jobDto));
        Bussiness bussiness= bussinessMapper.selectById(jobDto.getBusinessId());
        if (Objects.isNull(projectMeeting)) {
            log.error("changeMeetingStatus失败,项目会议为空");
        }
        bussiness.setMeetingStatus((byte) 1);
        bussiness.setModifyId("系统自动");
        bussiness.setGmtModify(new Date());
        bussiness.updateById(businessId);
        deleteByTaskId(jobDto.getId());
    }



四:开机自启动

/**
 * @author cgz
 * @version 1.0
 * @date 2023/6/9 15:55
 * 初始化定时任务防止重启服务,定时任务失效
 */
@Component
@Slf4j
public class ApplicationTask implements ApplicationRunner {

    @Autowired
    private IScPlanDynamicTaskService iScPlanDynamicTaskService;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        iScPlanDynamicTaskService.init();
        log.info("初始化定时任务");
    }
}



版权声明:本文为m0_47944994原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。