使用Quartz框架实现定时任务动态生成和执行
思考:需求场景
-
从页面上动态添加接口Url,填写Corn表达式,约束Url接口的执行次数和时间。能够对任务进行管理(新增、执行、停止、删除)等功能。
-
Quartz的使用详情请看https://blog.csdn.net/qq_37248504/article/details/106874496。
库表设计
t_task_cron:cron表达式表,每个任务的cron
DROP TABLE IF EXISTS `t_task_cron`;
CREATE TABLE `t_task_cron` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`corn` varchar(100) DEFAULT NULL,
`cornname` varchar(100) DEFAULT NULL,
`corecode` varchar(100) DEFAULT NULL,
`recordertime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `t_task_cron` VALUES ('1', '*/10 * * * * ?', '每隔10s运行', 'EveryTenSeconds', '2020-12-22 20:34:56');
t_task_define:定义的要执行的任务
taskcode是唯一的,当前运行任务的code
DROP TABLE IF EXISTS `t_task_define`;
CREATE TABLE `t_task_define` (
`id` int(100) NOT NULL AUTO_INCREMENT,
`taskcode` varchar(255) NOT NULL,
`taskname` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`urlparams` varchar(255) DEFAULT NULL COMMENT 'url参数',
`method` varchar(10) DEFAULT NULL,
`cron` varchar(50) DEFAULT NULL,
`definetime` datetime DEFAULT NULL,
`recordername` varchar(100) DEFAULT NULL,
`recordercode` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_taskcode` (`taskcode`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `t_task_define` VALUES ('1', 'test', 'task任务测试', 'localhost:8013/taskdefine/test1', null, 'post', '1', '2020-11-30 20:02:16', '1', '1');
t_task_deploy:已经部署的任务
DROP TABLE IF EXISTS `t_task_deploy`;
CREATE TABLE `t_task_deploy` (
`id` varchar(50) NOT NULL,
`taskid` varchar(50) DEFAULT NULL,
`deployed` int(1) unsigned zerofill NOT NULL COMMENT '是否部署',
`deploytime` datetime DEFAULT NULL,
`recordername` varchar(255) DEFAULT NULL,
`recordercode` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `t_task_deploy` VALUES ('', '1', '1', '2020-12-22 20:35:43', null, null);
t_task_perform_log:任务执行日志状态
DROP TABLE IF EXISTS `t_task_perform_log`;
CREATE TABLE `t_task_perform_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deployid` varchar(30) DEFAULT NULL,
`status` int(255) DEFAULT NULL COMMENT '状态码',
`url` varchar(150) DEFAULT NULL,
`method` varchar(15) DEFAULT NULL COMMENT '请求方式',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
设计思路
页面提交相关操作,后端进行任务新建、执行、停止等功能。
类设计图
- QuartzController:BaseController是我的工具Controller
- QuartzServiceImpl:具体业务逻辑实现类
- Job 执行的具体步骤
QuartzUtils:
- quartz任务启动、停止、重启、删除等操作的工具类
public class QuartzUtils {
private final static Logger logger = LoggerFactory.getLogger(QuartzUtils.class);
/**
* 创建定时任务 定时任务创建之后默认启动状态
*
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws Exception
*/
public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean) {
try {
//获取到定时任务的执行类 必须是类的绝对路径名称
//定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。
Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass());
// 构建定时任务信息
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build();
// 设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
// 构建触发器trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (ClassNotFoundException e) {
logger.error("定时任务类路径出错:请输入类的绝对路径");
} catch (SchedulerException e) {
logger.error("创建定时任务出错:" + e.getMessage());
}
}
/**
* 根据任务名称暂停定时任务
*
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void pauseScheduleJob(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
logger.error("暂停定时任务出错:" + e.getMessage());
}
}
/**
* 根据任务名称恢复定时任务
*
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void resumeScheduleJob(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
logger.error("启动定时任务出错:" + e.getMessage());
}
}
/**
* 根据任务名称立即运行一次定时任务
*
* @param scheduler 调度器
* @param jobName 定时任务名称
* @param taskInfo
* @throws SchedulerException
*/
public static boolean runOnce(Scheduler scheduler, String jobName, TaskInfo taskInfo) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
if(CollectionUtils.isEmpty(triggersOfJob)){
boolean flag = false;
for (Trigger trigger : triggersOfJob) {
if(jobKey.equals(trigger.getJobKey())){
flag = true;
}
}
// 当前任务没有Trigger则新建
if(!flag){
// 定义QuartzBean
QuartzBean quartzBean = new QuartzBean();
// 定义任务执行的类
quartzBean.setJobClass("com.li.taskcenter.controller.quartz.MyTask");
// 定义任务的名称唯一
quartzBean.setJobName(taskInfo.getTaskcode());
// corn表达式
quartzBean.setCronExpression(taskInfo.getCorn());
// 创建任务执行执行
QuartzUtils.createScheduleJob(scheduler,quartzBean);
// 停止执行
QuartzUtils.pauseScheduleJob(scheduler,quartzBean.getJobName());
}
}
// 执行一次
logger.info("=====> 任务执行一次开始...");
scheduler.triggerJob(jobKey);
logger.info("=====> 任务执行一次结束...");
return true;
} catch (SchedulerException e) {
logger.error("运行定时任务出错:" + e.getMessage());
return false;
}
}
/**
* 更新定时任务
*
* @param scheduler 调度器
* @param quartzBean 定时任务信息类
* @throws SchedulerException
*/
public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) {
try {
//获取到对应任务的触发器
TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());
//设置定时任务执行方式
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());
//重新构建任务的触发器trigger
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置对应的job
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
logger.error("更新定时任务出错:" + e.getMessage());
}
}
/**
* 根据定时任务名称从调度器当中删除定时任务
*
* @param scheduler 调度器
* @param jobName 定时任务名称
* @throws SchedulerException
*/
public static void deleteScheduleJob(Scheduler scheduler, String jobName) {
JobKey jobKey = JobKey.jobKey(jobName);
try {
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
logger.error("删除定时任务出错:" + e.getMessage());
}
}
}
MyTask
- MyTask继承QuartzJobBean,重写executeInternal,实现具体的任务运行逻辑。
package com.li.taskcenter.controller.quartz;
import com.alibaba.fastjson.JSONObject;
import com.li.taskcenter.domain.taskcenter.TaskInfo;
import com.li.taskcenter.mapper.taskcenter.TaskDefineMapper;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Trigger;
import org.quartz.impl.JobExecutionContextImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @Description:任务执行总类继承 QuartzJobBean重写 executeInternal()
* @Author:LiDong
* @Create:2020/12/15
* @Version:1.0.0
*/
public class MyTask extends QuartzJobBean {
@Autowired
private TaskDefineMapper taskDefineMapper;
/**
* @Author LiDong
* @Description //TODO 从jobExecutionContext 中拿出当前task的code
* @Date 22:07 2020/12/22
* @Param [jobExecutionContext]
* @return void
**/
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//TODO 这里写定时任务的执行逻辑
JobExecutionContextImpl jobDetail= ((JobExecutionContextImpl) jobExecutionContext);
Trigger trigger = jobDetail.getTrigger();
// 获得任务编码
String taskcode = trigger.getJobKey().getName();
dealJob(taskcode);
}
/**
* @Author LiDong
* @Description //TODO 执行url的具体逻辑
* @Date 21:33 2020/12/23
* @Param [taskcode]
* @return void
**/
private void dealJob(String taskcode) {
Map<String,String> map = new HashMap<>(1);
map.put("taskcode",taskcode);
TaskInfo taskDefine = taskDefineMapper.getTaskDefine(map);
if(Objects.nonNull(taskDefine)) {
String url = taskDefine.getUrl();
String method = taskDefine.getMethod();
String urlparams = taskDefine.getUrlparams();
Map<String, String> param = JSONObject.parseObject(urlparams, Map.class);
System.out.println("动态的定时任务执行时间:" + new Date().toLocaleString());
}
}
}
Controller、Service、ServiceImpl 部分代码
- QuartzController
@RestController
@RequestMapping("/quartz")
public class QuartzController extends BaseController {
/**
* 注入任务调度
**/
@Resource
private Scheduler scheduler;
@Autowired
private QuartzService quartzService;
@RequestMapping("/createJob")
public ResponseInfo createJob() {
return quartzService.createJob(scheduler,getParamMap(request),getCurrentUser());
}
@RequestMapping("/pauseJob")
public ResponseInfo pauseJob() {
return quartzService.pauseJob(scheduler,getParamMap(request),getCurrentUser());
}@RestController
@RequestMapping("/quartz")
public class QuartzController extends BaseController {
/**
* 注入任务调度
**/
@Resource
private Scheduler scheduler;
@Autowired
private QuartzService quartzService;
@RequestMapping("/createJob")
public ResponseInfo createJob() {
return quartzService.createJob(scheduler,getParamMap(request),getCurrentUser());
}
@RequestMapping("/pauseJob")
public ResponseInfo pauseJob() {
return quartzService.pauseJob(scheduler,getParamMap(request),getCurrentUser());
}
}
- QuartzServiceImpl
@Service
public class QuartzServiceImpl implements QuartzService {
private static final Logger logger = LoggerFactory.getLogger(QuartzServiceImpl.class);
@Autowired
private TaskDefineMapper taskDefineMapper;
/**
* @Author LiDong
* @Description //TODO 创建一个任务
* @Date 20:45 2020/12/22
* @Param [quartzBean, paramMap, currentUser]
* @return com.li.core.config.globalexception.ResponseInfo
**/
@Override
public ResponseInfo createJob(Scheduler scheduler, Map<String, String> paramMap, CurrentUser currentUser) {
ResponseInfo result = new ResponseInfo();
try {
TaskInfo taskInfo=getTaskInfo(paramMap,currentUser,result);
if(Objects.nonNull(taskInfo)){
// 定义QuartzBean
QuartzBean quartzBean = new QuartzBean();
// 定义任务执行的类
quartzBean.setJobClass("com.li.taskcenter.controller.quartz.MyTask");
// 定义任务的名称唯一
quartzBean.setJobName(taskInfo.getTaskcode());
// corn表达式
quartzBean.setCronExpression(taskInfo.getCorn());
QuartzUtils.createScheduleJob(scheduler,quartzBean);
}
return result;
} catch (Exception e) {
logger.info(e.getMessage());
return result;
}
}
private TaskInfo getTaskInfo(Map<String, String> paramMap, CurrentUser currentUser, ResponseInfo result) {
// 任务定义Id
String taskdefineid = paramMap.getOrDefault("taskdefineid", null);
if(StringUtils.isEmpty(taskdefineid)){
result.setCode(500);
result.setMessage("任务定义Id为空!");
}
Map<String,String> map = new HashMap<>(1);
map.put("taskdefineid",taskdefineid);
TaskInfo taskInfo =taskDefineMapper.getTaskDefine(map);
return taskInfo;
}
/**
* @Author LiDong
* @Description // 停用一个任务
* @Date 20:45 2021/1/5
* @Param [scheduler, paramMap, currentUser]
* @return com.li.core.config.globalexception.ResponseInfo
**/
@Override
public ResponseInfo pauseJob(Scheduler scheduler, Map<String, String> paramMap, CurrentUser currentUser) {
ResponseInfo responseInfo = new ResponseInfo();
try {
TaskInfo taskInfo=getTaskInfo(paramMap,currentUser,responseInfo);
if(Objects.nonNull(taskInfo)){
String taskcode = taskInfo.getTaskcode();
QuartzUtils.pauseScheduleJob (scheduler,taskcode);
responseInfo.setCode(200);
responseInfo.setMessage(taskcode+"任务暂停成功!");
return responseInfo;
}
responseInfo.setMessage("当前任务暂停失败!");
return responseInfo;
} catch (Exception e) {
responseInfo.setCode(500);
responseInfo.setMessage(e.getMessage());
return responseInfo;
}
}
}
完整代码见地址https://gitee.com/Marlon_Brando/back/tree/master/OnlineShop