文章目录
一、Quartz基础
Quartz使用文档,使用Quartz实现动态任务,Spring集成Quartz,Quartz集群部署,Quartz源码分析
二、使用Quartz实现定时任务的动态调度
1、使用Quartz-jobStore 持久化
Quartz使用文档,使用Quartz实现动态任务,Spring集成Quartz,Quartz集群部署,Quartz源码分析
2、前端页面实现效果图
3、自定义job表
CREATE database quartz;
CREATE TABLE `sys_job` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`job_name` varchar(512) NOT NULL COMMENT '任务名称',
`job_group` varchar(512) NOT NULL COMMENT '任务组名',
`job_cron` varchar(512) NOT NULL COMMENT '时间表达式',
`job_class_path` varchar(1024) NOT NULL COMMENT '类路径,全类型',
`job_data_map` varchar(1024) DEFAULT NULL COMMENT '传递map参数',
`job_status` int(2) NOT NULL COMMENT '状态:1启用 0停用',
`job_describe` varchar(1024) DEFAULT NULL COMMENT '任务功能描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
-- 插入3条数据,3个任务
-- 注意第三条,是一个发送邮件的任务,需要改成你自己的QQ和授权码。不知道什么是授权码的自己百度。
INSERT INTO `sys_job` (`id`, `job_name`, `job_group`, `job_cron`, `job_class_path`, `job_data_map`, `job_status`, `job_describe`) VALUES (22, 'test', 'test', '*/20 * * * * ?', 'com.demo.task.TestTask1', NULL, 1, 'a job a');
INSERT INTO `sys_job` (`id`, `job_name`, `job_group`, `job_cron`, `job_class_path`, `job_data_map`, `job_status`, `job_describe`) VALUES (23, 'test2', 'test', '*/30 * * * * ?', 'com.demo.task.TestTask2', NULL, 1, 'another job');
INSERT INTO `sys_job` (`id`, `job_name`, `job_group`, `job_cron`, `job_class_path`, `job_data_map`, `job_status`, `job_describe`) VALUES (24, 'test3', 'mail', '*/10 * * * * ?', 'com.demo.task.TestTask3', '{\"data\":{\"loginAccount\":\"改成你的QQ邮箱\",\"loginAuthCode\":\"改成你的邮箱授权码\",\"sender\":\"改成你的QQ邮箱\",\"emailContent\":\" 你好。\",\"emailContentType\":\"text/html;charset=utf-8\",\"emailSubject\":\"十万火急\",\"recipients\":\"改成你要的收件人邮箱,可以有多个,英文逗号隔开\"}}', 1, 'fdsafdfds');
-- 清空表的脚本
DELETE FROM QRTZ_BLOB_TRIGGERS;
DELETE FROM QRTZ_CALENDARS;
DELETE FROM QRTZ_CRON_TRIGGERS;
DELETE FROM QRTZ_FIRED_TRIGGERS;
DELETE FROM QRTZ_LOCKS;
DELETE FROM QRTZ_PAUSED_TRIGGER_GRPS;
DELETE FROM QRTZ_SCHEDULER_STATE;
DELETE FROM QRTZ_SIMPLE_TRIGGERS;
DELETE FROM QRTZ_SIMPROP_TRIGGERS;
DELETE FROM QRTZ_TRIGGERS;
DELETE FROM QRTZ_JOB_DETAILS;
COMMIT;
4、增删改查Controller
@Controller()
@RequestMapping("/job")
public class JobController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysJobService sysJobService;
/**
* 打开列表页
* @return
*/
@RequestMapping("/jobList")
public String jobList() {
return "jobListPage";
}
/**
* 打开详情界面
* @param id
* @param model
* @return
*/
@RequestMapping("/toDetail")
public String toDetail(Integer id, Model model) {
SysJob job = sysJobService.selectByPrimaryKey(id);
model.addAttribute("job",job);
return "jobDetail";
}
/**
* 打开修改界面
* @param id
* @param model
* @return
*/
@RequestMapping("/toUpdate")
public String toUpdate(Integer id, Model model) {
SysJob job = sysJobService.selectByPrimaryKey(id);
model.addAttribute("job",job);
return "jobUpdate";
}
/**
* 打开新增界面
* @return
*/
@RequestMapping("/toJob")
public String toJob (){
return "jobAdd";
}
/**
* 查询任务列表(带分页)
*
* @return
*/
@RequestMapping(value = "/queryList", method = RequestMethod.GET)
@ResponseBody
public LayuiData queryJobList(HttpServletRequest request, HttpServletResponse response) {
String idStr = request.getParameter("id");
String jobName = request.getParameter("jobName");
String jobGroup = request.getParameter("jobGroup");
String jobCron = request.getParameter("jobCron");
String jobClassPath = request.getParameter("jobClassPath");
String jobDescribe = request.getParameter("jobDescribe");
HashMap<String, String> map = new HashMap<String, String>();
if (StringUtils.isNotBlank(idStr)) {
map.put("id", idStr);
}
if (StringUtils.isNotBlank(jobName)) {
map.put("jobName", jobName);
}
if (StringUtils.isNotBlank(jobGroup)) {
map.put("jobGroup", jobGroup);
}
if (StringUtils.isNotBlank(jobCron)) {
map.put("jobCron", jobCron);
}
if (StringUtils.isNotBlank(jobClassPath)) {
map.put("jobClassPath", jobClassPath);
}
if (StringUtils.isNotBlank(jobDescribe)) {
map.put("jobDescribe", jobDescribe);
}
int page = Integer.parseInt(request.getParameter("page"));
int limit = Integer.parseInt(request.getParameter("limit"));
if(page>=1){
page = (page-1)*limit;
}
LayuiData layuiData = new LayuiData();
try {
List<SysJob> jobList = sysJobService.querySysJobList(map);
int count = sysJobService.getJobCount();
layuiData.setCode(0);
layuiData.setCount(count);
layuiData.setMsg("数据请求成功");
layuiData.setData(jobList);
return layuiData;
} catch (Exception e) {
throw new BizException("查询任务列表异常:" + e.getMessage());
}
}
/**
* 添加定时任务
*
* @throws Exception
*/
@PostMapping(value = "/addJob")
@ResponseBody
@Transactional
public int addjob(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("添加任务开始... ...");
int num =0;
String jobName = request.getParameter("jobName");
String jobClassPath= request.getParameter("jobClassPath");
String jobGroup= request.getParameter("jobGroup");
String jobCron= request.getParameter("jobCron");
String jobDescribe= request.getParameter("jobDescribe");
String jobDataMap= request.getParameter("jobDataMap");
if (StringUtils.isBlank(jobName)) {
throw new BizException("任务名称不能为空");
}
if (StringUtils.isBlank(jobGroup)) {
throw new BizException("任务组别不能为空");
}
if (StringUtils.isBlank(jobCron)) {
throw new BizException("Cron表达式不能为空");
}
if (StringUtils.isBlank(jobClassPath)) {
throw new BizException("任务类路径不能为空");
}
// 参数不为空时校验格式
if(StringUtils.isNotBlank(jobDataMap)){
try {
JSONObject.parseObject(jobDataMap);
} catch (Exception e) {
throw new BizException("参数JSON格式错误");
}
}
// 已存在校验
SysJob queryBean = new SysJob();
queryBean.setJobName(jobName);
SysJob result = sysJobService.selectByBean(queryBean);
if (null != result) {
throw new BizException("任务名为" + jobName + "的任务已存在!");
}
SysJob bean = new SysJob();
bean.setJobName(jobName);
bean.setJobClassPath(jobClassPath);
bean.setJobGroup(jobGroup);
bean.setJobCron(jobCron);
bean.setJobDescribe(jobDescribe);
bean.setJobDataMap(jobDataMap);
bean.setJobStatus(Constant.JOB_STATE.YES);
try {
num = sysJobService.insertSelective(bean);
} catch (Exception e) {
throw new BizException("新增定时任务失败");
}
SchedulerUtil.addJob(jobClassPath,jobName, jobGroup, jobCron,jobDataMap);
return num;
}
/**
* 变更定时任务执行状态
* @return
* @throws Exception
*/
@GetMapping(value = "/changeState")
@ResponseBody
public int changeState(@RequestParam(value = "id") String idStr)throws Exception{
logger.info("变更定时任务状态开始... ...");
if (StringUtils.isBlank(idStr)) {
throw new BizException("任务ID不能为空");
}
int id = Integer.parseInt(idStr);
// 校验
SysJob queryBean = new SysJob();
queryBean.setId(id);
SysJob result = sysJobService.selectByBean(queryBean);
if (null == result) {
throw new BizException("任务ID为" + id + "的任务不存在!");
}
SysJob updateBean = new SysJob();
updateBean.setId(id);
//如果是现在是启用,则停用
if(Constant.JOB_STATE.YES == result.getJobStatus()){
updateBean.setJobStatus(Constant.JOB_STATE.NO);
//SchedulerUtil.jobPause(result.getJobName(), result.getJobGroup());
Boolean b=SchedulerUtil.isResume(result.getJobName(), result.getJobGroup());
if (b) {
SchedulerUtil.jobdelete(result.getJobName(), result.getJobGroup());
}
}
//如果现在是停用,则启用
if(Constant.JOB_STATE.NO == result.getJobStatus()){
updateBean.setJobStatus(Constant.JOB_STATE.YES);
//SchedulerUtil.jobresume(result.getJobName(), result.getJobGroup());
Boolean b=SchedulerUtil.isResume(result.getJobName(), result.getJobGroup());
//存在则激活,不存在则添加
if (b) {
SchedulerUtil.jobresume(result.getJobName(), result.getJobGroup());
}else {
SchedulerUtil.addJob(result.getJobClassPath(),result.getJobName(), result.getJobGroup(), result.getJobCron(),result.getJobDataMap());
}
}
try {
sysJobService.updateByPrimaryKeySelective(updateBean);
} catch (Exception e) {
throw new BizException("更新数据库的定时任务信息异常!");
}
// 1表示成功
return 1;
}
/**
* 删除一个任务
*
* @throws Exception
*/
@PostMapping(value = "/deleteJob")
@ResponseBody
public int deletejob(@RequestParam(value = "id") String idStr) throws Exception {
logger.info("删除定时任务状态开始... ...");
int num =0;
if (StringUtils.isBlank(idStr)) {
throw new BizException("任务ID不能为空");
}
int id = Integer.parseInt(idStr);
// 存在性校验
SysJob queryBean = new SysJob();
queryBean.setId(id);
SysJob result = sysJobService.selectByBean(queryBean);
if (null == result) {
throw new BizException("任务ID为" + idStr + "的任务不存在!");
}
try {
num = sysJobService.deleteByPrimaryKey(id);
} catch (Exception e) {
throw new BizException("从数据库删除定时任务时发生异常!");
}
SchedulerUtil.jobdelete(result.getJobName(), result.getJobGroup());
return num;
}
/**
* 修改定时表达式
*/
@RequestMapping("/reSchedulejob")
@ResponseBody
public int updateByBean(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("修改定时任务信息开始... ...");
int num =0;
String jobCron = request.getParameter("jobCron");
String jobDescribe = request.getParameter("jobDescribe");
String idStr = request.getParameter("id");
int id = Integer.parseInt(idStr);
// 数据非空校验
if (!StringUtils.isNotBlank(idStr)) {
throw new BizException("任务ID不能为空");
}
SysJob result = sysJobService.selectByPrimaryKey(id);
// 数据不存在
if (null == result) {
throw new BizException("根据任务ID[" + id + "]未查到相应的任务记录");
}
SysJob bean = new SysJob();
bean.setId(id);
bean.setJobCron(jobCron);
bean.setJobDescribe(jobDescribe);
try {
num = sysJobService.updateByPrimaryKeySelective(bean);
} catch (Exception e) {
throw new BizException("变更任务表达式异常:" + e.getMessage());
}
//只有任务状态为启用,才开始运行
// 如果先启动再手工插入数据,此处会报空指针异常
if( result.getJobStatus() ==Constant.JOB_STATE.YES ){
SchedulerUtil.jobReschedule(result.getJobName(), result.getJobGroup(),jobCron);
}
// 返回成功
return num;
}
/**
* 展示任务调度管理页
*
* @param request
* @param rep
* @return
*/
@RequestMapping(value = "/jobPage", method = RequestMethod.GET)
public String getJobPage(HttpServletRequest request, HttpServletResponse rep) {
return "job/job_info";
}
}
5、Quartz工具类
/**
* Quartz工具类
*/
public class SchedulerUtil {
private static Logger logger = LoggerFactory.getLogger(SchedulerUtil.class);
/**
* 新增定时任务
* @param jobClassName 类路径
* @param jobName 任务名称
* @param jobGroupName 组别
* @param cronExpression Cron表达式
* @param jobDataMap 需要传递的参数
* @throws Exception
*/
public static void addJob(String jobClassName,String jobName, String jobGroupName, String cronExpression,String jobDataMap) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
.withIdentity(jobName, jobGroupName).build();
// JobDataMap用于传递任务运行时的参数,比如定时发送邮件,可以用json形式存储收件人等等信息
if (StringUtils.isNotEmpty(jobDataMap)) {
JSONObject jb = JSONObject.parseObject(jobDataMap);
Map<String, Object> dataMap =(Map<String, Object>) jb.get("data");
for (Map.Entry<String, Object> m:dataMap.entrySet()) {
jobDetail.getJobDataMap().put(m.getKey(),m.getValue());
}
}
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
.withSchedule(scheduleBuilder).startNow().build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
logger.info("创建定时任务失败" + e);
throw new Exception("创建定时任务失败");
}
}
/**
* 停用一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobPause(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 启用一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobresume(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 删除一个定时任务
* @param jobName 任务名称
* @param jobGroupName 组别
* @throws Exception
*/
public static void jobdelete(String jobName, String jobGroupName) throws Exception {
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
}
/**
* 更新定时任务表达式
* @param jobName 任务名称
* @param jobGroupName 组别
* @param cronExpression Cron表达式
* @throws Exception
*/
public static void jobReschedule(String jobName, String jobGroupName, String cronExpression) throws Exception {
try {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).startNow().build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败" + e);
throw new Exception("更新定时任务失败");
}
}
/**
* 检查Job是否存在
* @throws Exception
*/
public static Boolean isResume(String jobName, String jobGroupName) throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
Boolean state = scheduler.checkExists(triggerKey);
return state;
}
/**
* 暂停所有任务
* @throws Exception
*/
public static void pauseAlljob() throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
scheduler.pauseAll();
}
/**
* 唤醒所有任务
* @throws Exception
*/
public static void resumeAlljob() throws Exception {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
sched.resumeAll();
}
/**
* 获取Job实例
* @param classname
* @return
* @throws Exception
*/
public static BaseJob getClass(String classname) throws Exception {
try {
Class<?> c = Class.forName(classname);
return (BaseJob) c.newInstance();
} catch (Exception e) {
throw new Exception("类["+classname+"]不存在!");
}
}
}
6、测试任务类
@DisallowConcurrentExecution
public class TestTask1 implements BaseJob {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName() + " " +sdf.format(date) + " Task1: ----hello1----");
}
}
public class TestTask2 implements BaseJob {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName() + " " +sdf.format(date) + " Task2: ----hello2----");
}
}
public class TestTask3 implements BaseJob {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysJobService sysJobService;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
logger.info("开始执行任务3... ...");
HashMap<String,String> map = new HashMap<String,String>();
map.put("jobGroup", "mail");
map.put("jobStatus", "1");
List<SysJob> jobList= sysJobService.querySysJobList(map);
if( null == jobList || jobList.size() ==0){
logger.info("没有状态为可用的发送邮件任务... ...");
}
for (SysJob sysJob:jobList) {
logger.info("开始发送邮件... ...");
}
}
}
7、springboot启动初始化定时任务
/**
* 这个类用于启动SpringBoot时,加载作业。run方法会自动执行。
*
* 另外可以使用 ApplicationRunner
*
*/
public class InitStartSchedule implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysJobService sysJobService;
@Autowired
private MyJobFactory myJobFactory;
@Override
public void run(String... args) throws Exception {
/**
* 用于程序启动时加载定时任务,并执行已启动的定时任务(只会执行一次,在程序启动完执行)
*/
//查询job状态为启用的
HashMap<String,String> map = new HashMap<String,String>();
map.put("jobStatus", "1");
List<SysJob> jobList= sysJobService.querySysJobList(map);
if( null == jobList || jobList.size() ==0){
logger.info("系统启动,没有需要执行的任务... ...");
}
// 通过SchedulerFactory获取一个调度器实例
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
// 如果不设置JobFactory,Service注入到Job会报空指针
scheduler.setJobFactory(myJobFactory);
// 启动调度器
scheduler.start();
for (SysJob sysJob:jobList) {
String jobClassName=sysJob.getJobName();
String jobGroupName=sysJob.getJobGroup();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(sysJob.getJobClassPath()).getClass()).withIdentity(jobClassName, jobGroupName).build();
if (StringUtils.isNotEmpty(sysJob.getJobDataMap())) {
JSONObject jb = JSONObject.parseObject(sysJob.getJobDataMap());
Map<String, Object> dataMap = (Map<String, Object>)jb.get("data");
for (Map.Entry<String, Object> m:dataMap.entrySet()) {
jobDetail.getJobDataMap().put(m.getKey(),m.getValue());
}
}
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(sysJob.getJobCron());
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).startNow().build();
// 任务不存在的时候才添加
if( !scheduler.checkExists(jobDetail.getKey()) ){
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
logger.info("\n创建定时任务失败"+e);
throw new Exception("创建定时任务失败");
}
}
}
}
public static BaseJob getClass(String classname) throws Exception
{
Class<?> c= Class.forName(classname);
return (BaseJob)c.newInstance();
}
}
8、自定义JobFactory,使Task注册为Bean
/**
*
* 将Spring的对象注入到Quartz Job 1
*/
public class AdaptableJobFactory implements JobFactory {
@Override
public Job newJob(TriggerFiredBundle bundle, Scheduler arg1) throws SchedulerException {
return newJob(bundle);
}
public Job newJob(TriggerFiredBundle bundle) throws SchedulerException {
try {
// 返回Job实例
Object jobObject = createJobInstance(bundle);
return adaptJob(jobObject);
}
catch (Exception ex) {
throw new SchedulerException("Job instantiation failed", ex);
}
}
// 通过反射的方式创建实例
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Method getJobDetail = bundle.getClass().getMethod("getJobDetail");
Object jobDetail = ReflectionUtils.invokeMethod(getJobDetail, bundle);
Method getJobClass = jobDetail.getClass().getMethod("getJobClass");
Class jobClass = (Class) ReflectionUtils.invokeMethod(getJobClass, jobDetail);
return jobClass.newInstance();
}
protected Job adaptJob(Object jobObject) throws Exception {
if (jobObject instanceof Job) {
return (Job) jobObject;
}
else if (jobObject instanceof Runnable) {
return new DelegatingJob((Runnable) jobObject);
}
else {
throw new IllegalArgumentException("Unable to execute job class [" + jobObject.getClass().getName() +
"]: only [org.quartz.Job] and [java.lang.Runnable] supported.");
}
}
}
/**
*
* 将Spring的对象注入到Quartz Job 2
*/
@Component
public class MyJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
// 使我们的JOB注入到Spring容器
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
9、省略的内容
此处省略了SysJob实体类,以及Mapper等对数据库的操作。
10、总结
本文只是大致实现一个基于Quartz实现定时任务的动态调度,具体细节还有很多未完善的。自己实现一个任务调度器的话,非常麻烦细节很多。
实现任务调度,推荐使用xxl-job分布式任务调度,或者Elastic-job。
如果需求实在是想要动态的创建任务,修改任务,那么目前为止我还没有更好的办法,只能参考该文章了。