文章目录
- 定时任务监控界面 /monitor/job
- pojo - sys_job 定时任务表
- SysJobController控制管理定时任务
- SysJobLogController控制定时任务日志记录 /monitor/jobLog
- service层ISysJobLogService
- quartz进行任务调度
- quartz服务模块结构
- 核心API介绍
- ScheduleUtils 定时任务工具类
- 抽象quartz调用类AbstractQuartzJob(实现Job接口)
- 定时任务处理类(禁止并发执行) 继承抽象类
- 定时任务处理类(允许并发执行) 继承抽象类
- 任务执行工具类 JobInvokeUtil
- cron表达式工具类
- 定时任务调度测试类RyTask
- 定时任务执行顺序
定时任务监控界面 /monitor/job
pojo - sys_job 定时任务表

package com.ruoyi.quartz.domain;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.quartz.util.CronUtils;
public class SysJob extends BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;
@Excel(name = "任务序号", cellType = ColumnType.NUMERIC)
private Long jobId;
@Excel(name = "任务名称")
private String jobName;
@Excel(name = "任务组名")
private String jobGroup;
@Excel(name = "调用目标字符串")
private String invokeTarget;
@Excel(name = "执行表达式 ")
private String cronExpression;
@Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行")
private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
@Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止")
private String concurrent;
@Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停")
private String status;
public Long getJobId()
{
return jobId;
}
public void setJobId(Long jobId)
{
this.jobId = jobId;
}
@NotBlank(message = "任务名称不能为空")
@Size(min = 0, max = 64, message = "任务名称不能超过64个字符")
public String getJobName()
{
return jobName;
}
public void setJobName(String jobName)
{
this.jobName = jobName;
}
public String getJobGroup()
{
return jobGroup;
}
public void setJobGroup(String jobGroup)
{
this.jobGroup = jobGroup;
}
@NotBlank(message = "调用目标字符串不能为空")
@Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符")
public String getInvokeTarget()
{
return invokeTarget;
}
public void setInvokeTarget(String invokeTarget)
{
this.invokeTarget = invokeTarget;
}
@NotBlank(message = "Cron执行表达式不能为空")
@Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符")
public String getCronExpression()
{
return cronExpression;
}
public void setCronExpression(String cronExpression)
{
this.cronExpression = cronExpression;
}
public Date getNextValidTime()
{
if (StringUtils.isNotEmpty(cronExpression))
{
return CronUtils.getNextExecution(cronExpression);
}
return null;
}
public String getMisfirePolicy()
{
return misfirePolicy;
}
public void setMisfirePolicy(String misfirePolicy)
{
this.misfirePolicy = misfirePolicy;
}
public String getConcurrent()
{
return concurrent;
}
public void setConcurrent(String concurrent)
{
this.concurrent = concurrent;
}
public String getStatus()
{
return status;
}
public void setStatus(String status)
{
this.status = status;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("jobId", getJobId())
.append("jobName", getJobName())
.append("jobGroup", getJobGroup())
.append("cronExpression", getCronExpression())
.append("nextValidTime", getNextValidTime())
.append("misfirePolicy", getMisfirePolicy())
.append("concurrent", getConcurrent())
.append("status", getStatus())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}
SysJobController控制管理定时任务

package com.ruoyi.quartz.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.service.ISysJobService;
@Controller
@RequestMapping("/monitor/job")
public class SysJobController extends BaseController
{
private String prefix = "monitor/job";
@Autowired
private ISysJobService jobService;
@RequiresPermissions("monitor:job:view")
@GetMapping()
public String job()
{
return prefix + "/job";
}
service层ISysJobService
package com.ruoyi.quartz.service;
import java.util.List;
import org.quartz.SchedulerException;
import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.quartz.domain.SysJob;
public interface ISysJobService
{
public List<SysJob> selectJobList(SysJob job);
public SysJob selectJobById(Long jobId);
public int pauseJob(SysJob job) throws SchedulerException;
public int resumeJob(SysJob job) throws SchedulerException;
public int deleteJob(SysJob job) throws SchedulerException;
public void deleteJobByIds(String ids) throws SchedulerException;
public int changeStatus(SysJob job) throws SchedulerException;
public void run(SysJob job) throws SchedulerException;
public int insertJob(SysJob job) throws SchedulerException, TaskException;
public int updateJob(SysJob job) throws SchedulerException, TaskException;
public boolean checkCronExpressionIsValid(String cronExpression);
}
SysJobLogController控制定时任务日志记录 /monitor/jobLog
package com.ruoyi.quartz.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.domain.SysJobLog;
import com.ruoyi.quartz.service.ISysJobLogService;
import com.ruoyi.quartz.service.ISysJobService;
@Controller
@RequestMapping("/monitor/jobLog")
public class SysJobLogController extends BaseController
{
private String prefix = "monitor/job";
@Autowired
private ISysJobService jobService;
@Autowired
private ISysJobLogService jobLogService;
@RequiresPermissions("monitor:job:view")
@GetMapping()
public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap)
{
if (StringUtils.isNotNull(jobId))
{
SysJob job = jobService.selectJobById(jobId);
mmap.put("job", job);
}
return prefix + "/jobLog";
}
@RequiresPermissions("monitor:job:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(SysJobLog jobLog)
{
startPage();
List<SysJobLog> list = jobLogService.selectJobLogList(jobLog);
return getDataTable(list);
}
@Log(title = "调度日志", businessType = BusinessType.EXPORT)
@RequiresPermissions("monitor:job:export")
@PostMapping("/export")
@ResponseBody
public AjaxResult export(SysJobLog jobLog)
{
List<SysJobLog> list = jobLogService.selectJobLogList(jobLog);
ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
return util.exportExcel(list, "调度日志");
}
@Log(title = "调度日志", businessType = BusinessType.DELETE)
@RequiresPermissions("monitor:job:remove")
@PostMapping("/remove")
@ResponseBody
public AjaxResult remove(String ids)
{
return toAjax(jobLogService.deleteJobLogByIds(ids));
}
@RequiresPermissions("monitor:job:detail")
@GetMapping("/detail/{jobLogId}")
public String detail(@PathVariable("jobLogId") Long jobLogId, ModelMap mmap)
{
mmap.put("name", "jobLog");
mmap.put("jobLog", jobLogService.selectJobLogById(jobLogId));
return prefix + "/detail";
}
@Log(title = "调度日志", businessType = BusinessType.CLEAN)
@RequiresPermissions("monitor:job:remove")
@PostMapping("/clean")
@ResponseBody
public AjaxResult clean()
{
jobLogService.cleanJobLog();
return success();
}
}
service层ISysJobLogService
package com.ruoyi.quartz.service;
import java.util.List;
import com.ruoyi.quartz.domain.SysJobLog;
public interface ISysJobLogService
{
public List<SysJobLog> selectJobLogList(SysJobLog jobLog);
public SysJobLog selectJobLogById(Long jobLogId);
public void addJobLog(SysJobLog jobLog);
public int deleteJobLogByIds(String ids);
public int deleteJobLogById(Long jobId);
public void cleanJobLog();
}
quartz进行任务调度
quartz服务模块结构

核心API介绍
- Scheduler :与调度器交互的主要接口。
- Job:实现该接口组件能被调度程序执行的任务类。
- JobDetail:用于定义Job的实例。
- Trigger:触发器,定义一个Job如何被调度器执行。
- TriggerBuilder:触发器创建器,用于创建触发器Trigger实例。
- JobBuilder :用于声明一个任务实例,也可以定义关于该任务的详情比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。
ScheduleUtils 定时任务工具类
package com.ruoyi.quartz.util;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.common.exception.job.TaskException.Code;
import com.ruoyi.quartz.domain.SysJob;
public class ScheduleUtils
{
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
{
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
{
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
public static JobKey getJobKey(Long jobId, String jobGroup)
{
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
Class<? extends Job> jobClass = getQuartzJobClass(job);
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
scheduler.scheduleJob(jobDetail, trigger);
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException
{
switch (job.getMisfirePolicy())
{
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
}
}
}
抽象quartz调用类AbstractQuartzJob(实现Job接口)
package com.ruoyi.quartz.util;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.utils.ExceptionUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.domain.SysJobLog;
import com.ruoyi.quartz.service.ISysJobLogService;
public abstract class AbstractQuartzJob implements Job
{
private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
SysJob sysJob = new SysJob();
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try
{
before(context, sysJob);
if (sysJob != null)
{
doExecute(context, sysJob);
}
after(context, sysJob, null);
}
catch (Exception e)
{
log.error("任务执行异常 - :", e);
after(context, sysJob, e);
}
}
protected void before(JobExecutionContext context, SysJob sysJob)
{
threadLocal.set(new Date());
}
protected void after(JobExecutionContext context, SysJob sysJob, Exception e)
{
Date startTime = threadLocal.get();
threadLocal.remove();
final SysJobLog sysJobLog = new SysJobLog();
sysJobLog.setJobName(sysJob.getJobName());
sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime);
sysJobLog.setEndTime(new Date());
long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime();
sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null)
{
sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
sysJobLog.setExceptionInfo(errorMsg);
}
else
{
sysJobLog.setStatus(Constants.SUCCESS);
}
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
}
protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
定时任务处理类(禁止并发执行) 继承抽象类
package com.ruoyi.quartz.util;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import com.ruoyi.quartz.domain.SysJob;
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
JobInvokeUtil.invokeMethod(sysJob);
}
}
定时任务处理类(允许并发执行) 继承抽象类
package com.ruoyi.quartz.util;
import org.quartz.JobExecutionContext;
import com.ruoyi.quartz.domain.SysJob;
public class QuartzJobExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
JobInvokeUtil.invokeMethod(sysJob);
}
}
任务执行工具类 JobInvokeUtil
package com.ruoyi.quartz.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob;
public class JobInvokeUtil
{
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
Object bean = Class.forName(beanName).newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
{
Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
}
else
{
Method method = bean.getClass().getDeclaredMethod(methodName);
method.invoke(bean);
}
}
public static boolean isValidClassName(String invokeTarget)
{
return StringUtils.countMatches(invokeTarget, ".") > 1;
}
public static String getBeanName(String invokeTarget)
{
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
public static String getMethodName(String invokeTarget)
{
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
public static List<Object[]> getMethodParams(String invokeTarget)
{
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
if (StringUtils.isEmpty(methodStr))
{
return null;
}
String[] methodParams = methodStr.split(",");
List<Object[]> classs = new LinkedList<>();
for (int i = 0; i < methodParams.length; i++)
{
String str = StringUtils.trimToEmpty(methodParams[i]);
if (StringUtils.contains(str, "'"))
{
classs.add(new Object[] { StringUtils.replace(str, "'", ""), String.class });
}
else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false"))
{
classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
}
else if (StringUtils.containsIgnoreCase(str, "L"))
{
classs.add(new Object[] { Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class });
}
else if (StringUtils.containsIgnoreCase(str, "D"))
{
classs.add(new Object[] { Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class });
}
else
{
classs.add(new Object[] { Integer.valueOf(str), Integer.class });
}
}
return classs;
}
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
{
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = (Class<?>) os[1];
index++;
}
return classs;
}
public static Object[] getMethodParamsValue(List<Object[]> methodParams)
{
Object[] classs = new Object[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = (Object) os[0];
index++;
}
return classs;
}
}
cron表达式工具类
package com.ruoyi.quartz.util;
import java.text.ParseException;
import java.util.Date;
import org.quartz.CronExpression;
public class CronUtils
{
public static boolean isValid(String cronExpression)
{
return CronExpression.isValidExpression(cronExpression);
}
public static String getInvalidMessage(String cronExpression)
{
try
{
new CronExpression(cronExpression);
return null;
}
catch (ParseException pe)
{
return pe.getMessage();
}
}
public static Date getNextExecution(String cronExpression)
{
try
{
CronExpression cron = new CronExpression(cronExpression);
return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
}
catch (ParseException e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
}
定时任务调度测试类RyTask
package com.ruoyi.quartz.task;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;
@Component("ryTask")
public class RyTask
{
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
{
System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
}
public void ryParams(String params)
{
System.out.println("执行有参方法:" + params);
}
public void ryNoParams()
{
System.out.println("执行无参方法");
}
}
定时任务执行顺序
