编辑
2026-09-09
java
00

目录

描述
我的思路
加载依赖
第一步:创建数据库
第二步:创建一个定时任务的工具类
第三步:定时任务监听器MyJobListener
第四步:重启项目的时候确保定时任务启动
第五步:创建一个定时任务
控制任务的接口方法,对任务的增删该查 都在 下面的service类中
最后

描述

  • 系统需要使用到定时任务去完成一些数据处理的工作。
  • 希望有一个页面可以控制任务的执行计划,并且记录每次执行的结果,如有异常则记录日志到数据库中。
  • quartz官网给出的需要11张表来协同完成,实际中并不需要这么多功能(可能你的项目需要)。
  • 我认为两种表就可以完成日常需要的工作,schedule_job(定时任务表),schedule_log(任务执行记录表)。

我的思路

  • 第一步 创建两种表用于存储定时任务信息以及执行记录信息。
  • 第二步 需要记录每条执行的状态结果,执行中,执行失败,执行成功等,不可能在每个Job类中写,本来是想用aop切面,但是一直不生效。后来发现可以通过JobListener监听器的方式实现。这里需要注意就是,在执行开始新增记录,在执行结束或异常更新记录,为保证取得记录的logId,在执行开始时把logId放在ThreadLocal中,执行结束在从ThreadLocal取的logId。
  • 第三步 JobListener监听器需要监听那些任务,需要在JobSchedule初始化的时候指定。第二步中的static {}静态代码块中
  • 第四步 为了确保项目启动时,数据库中启动状态的项目正常启动,所以需要在项目初始化时启动数据库中的任务。
  • 通过页面控制的话,我需要提供一些接口如:新增一个定时任务,修改执行计划/启动或关闭,查询某个任务的执行记录等。
  • 对定时任务的操作状态需要同步更新到数据库中,所以我在service中调用JobSchedule工具类控制任务执行,同时跟新数据的任务状态等信息。

加载依赖

  • 配置的话我这里使用默认的。(不写就是默认了)
xml
<!--quartz依赖--> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.3</version> </dependency>

第一步:创建数据库

  • schedule_job:用于记录每个定时任务的信息
sql
CREATE TABLE `schedule_job` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(100) NOT NULL COMMENT '任务名称', `clazz` varchar(100) NOT NULL COMMENT '任务执行的类', `cron` varchar(50) NOT NULL COMMENT 'cron表达式', `status` int(11) NOT NULL DEFAULT '2' COMMENT '1 开启 2 关闭 ', `job_group_name` varchar(50) NOT NULL DEFAULT 'DEFAULT_JOB_GROUP', `trigger_group_name` varchar(50) NOT NULL DEFAULT 'DEFAULT_TRIGGER_GROUP', `des` varchar(200) DEFAULT NULL COMMENT '描述', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  • schedule_log:用于记录定时任务的执行详情
sql
CREATE TABLE `schedule_log` ( `id` varchar(32) NOT NULL COMMENT '主键', `job_id` varchar(100) NOT NULL COMMENT '定时任务的id', `job_name` varchar(100) DEFAULT NULL, `job_clazz` varchar(100) DEFAULT NULL COMMENT '任务执行的类', `status` int(11) NOT NULL DEFAULT '1' COMMENT '1 成功 2 异常 ', `log_info` text COMMENT '描述', `create_time` bigint(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

第二步:创建一个定时任务的工具类

  • JobSchedule 工具类提供对任务的 添加,修改,删除操作。
  • 提供对定时任务的控制页面的话,就是通过Controller对应的service中调用此工具类,来实现对定时任务的控制。(后面会有Controller - service)的代码
java
/** * @calssName JobSchedule * @Description 任务调度的工具类 * @Author xxx * @DATE 2020/5/28 11:20 */ public class JobSchedule { private static final Logger LOGGER = LoggerFactory.getLogger(JobSchedule.class); private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory(); private static String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP"; private static String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP"; // 默认将 MyJobListener 绑定到 JOB_GROUP_NAME,在MyJobListener通过监听的方式记录 记录定时任务的执行记录 static { try { Scheduler sched = gSchedulerFactory.getScheduler(); sched.getListenerManager().addJobListener(new MyJobListener(),jobGroupEquals(JOB_GROUP_NAME)); }catch (Exception e){ LOGGER.error(">>>>>>>>>>>>>>>>>>>>>> 定时任务addJobListener失败 >>>>>>>>>>>>>>>>>>>>>>"); } } private JobSchedule() { } /** * @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名 * @param jobName 任务名 * @param cls 任务 * @param cron 时间设置,参考quartz说明文档 * @throws SchedulerException * */ public static void addJob(String jobName, Class cls, String cron) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); // 用于描叙Job实现类及其他的一些静态信息,构建一个作业实例 JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build(); // 构建一个触发器,规定触发的规则 Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器 .withIdentity(jobName, TRIGGER_GROUP_NAME)// 给触发器起一个名字和组名 .startNow()// 立即执行 .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间 .build();// 产生触发器 sched.scheduleJob(jobDetail, trigger); LOGGER.info("添加任务:{},{},{}",jobName,cls,cron); // 启动 if (!sched.isShutdown()) { sched.start(); } } /** * @Description: 添加一个定时任务 * * @param jobName 任务名 * @param jobGroupName 任务组名 * @param triggerName 触发器名 * @param triggerGroupName 触发器组名 * @param cls 任务 * @param cron 时间设置,参考quartz说明文档 */ public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class cls, String cron) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); // 用于描叙Job实现类及其他的一些静态信息,构建一个作业实例 JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, jobGroupName).build(); // 构建一个触发器,规定触发的规则 Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器 .withIdentity(jobName, triggerGroupName)// 给触发器起一个名字和组名 .startNow()// 立即执行 .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间 .build();// 产生触发器 sched.scheduleJob(jobDetail, trigger); LOGGER.info("添加任务:{},{},{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName,cls,cron); // 启动 if (!sched.isShutdown()) { sched.start(); } } /** * @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名) * * @param jobName * @param cron * @throws SchedulerException */ public static void modifyJobTime(String jobName, String cron) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); if (trigger == null) { return; } String oldTime = trigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(cron)) { JobDetail jobDetail = sched.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME)); Class objJobClass = jobDetail.getJobClass(); removeJob(jobName); addJob(jobName, objJobClass, cron); LOGGER.info("修改任务:{},{}",jobName,cron); } } /** * @Description: 修改或添加一个任务,jobName 存在时修改任务,不存在时则新增任务 */ public static void modifyOrAddJobTime(String jobName, String cron, Class cls) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); TriggerKey triggerKey = new TriggerKey(jobName, TRIGGER_GROUP_NAME); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); if (trigger == null) { // 此任务不存在 addJob(jobName, cls, cron); LOGGER.info("修改任务:{},{}",jobName,cron); }else{ String oldTime = trigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(cron)) { JobDetail jobDetail = sched.getJobDetail(new JobKey(jobName, JOB_GROUP_NAME)); Class objJobClass = jobDetail.getJobClass(); removeJob(jobName); addJob(jobName, objJobClass, cron); LOGGER.info("修改任务:{},{}",jobName,cron); } } } /** * @Description: 移除一个任务(使用默认的任务组名,触发器名,触发器组名) * @param jobName * @throws SchedulerException */ public static void removeJob(String jobName) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); JobKey jobKey = new JobKey(jobName, TRIGGER_GROUP_NAME); // 停止触发器 sched.pauseJob(jobKey); sched.unscheduleJob(new TriggerKey(jobName, TRIGGER_GROUP_NAME));// 移除触发器 sched.deleteJob(jobKey);// 删除任务 LOGGER.info("移除任务:{}",jobName); } /** * 移除任务 * * @param jobName * @param jobGroupName * @param triggerName * @param triggerGroupName * @throws SchedulerException */ public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); JobKey jobKey = new JobKey(jobName, jobGroupName); // 停止触发器 sched.pauseJob(jobKey); sched.unscheduleJob(new TriggerKey(jobName, triggerGroupName));// 移除触发器 sched.deleteJob(jobKey);// 删除任务 LOGGER.info("移除任务:{},{},{},{},{},{}",jobName,jobGroupName,triggerName,triggerGroupName); } /** * 启动所有任务 * * @throws SchedulerException */ public static void startJobs() throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); sched.start(); LOGGER.info("启动所有任务"); } /** * 关闭所有定时任务 * @throws SchedulerException */ public static void shutdownJobs() throws SchedulerException { Scheduler sched = gSchedulerFactory.getScheduler(); if (!sched.isShutdown()) { sched.shutdown(); LOGGER.info("关闭所有任务"); } } }

第三步:定时任务监听器MyJobListener

  • 定时任务监听器:通过监听的方式记录 记录定时任务的执行记录。
  • 创建日志的时候回把logId放在ThreadLocal中,在执行结果从ThreadLocal取logId来更新日志状态。
java
/** * @calssName MyJobListener * @Description 定时任务监听器:通过监听的方式记录 记录定时任务的执行记录 * @Author xxx * @DATE 2020/5/29 14:18 */ @Component public class MyJobListener implements JobListener{ private JobService jobService; // 用于保存 logId ThreadLocal<String> threadLocal = new ThreadLocal<>(); @Override public String getName() { return "myJobListener"; } // 执行开始新增一条执行日志,状态为执行中 @Override public void jobToBeExecuted(JobExecutionContext jobExecutionContext) { if(jobService == null){ jobService = SpringUtils.getBean(JobService.class); } JobDetail jobDetail = jobExecutionContext.getJobDetail(); String name = jobDetail.getKey().getName(); String logId = jobService.saveScheduleLog(name, JobLogStatusEnum.RUNNING.getCode(), ""); threadLocal.set(logId); // 把执行记录放到 threadLocal,提供给执行结束后取此结果 } // 任务拒绝执行时,修改日志状态为:执行失败 @Override public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) { String logId = threadLocal.get(); // 执行记录logId jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), "执行失败"); } // 任务执行时结果,判断是否有异常,修改对应状态 @Override public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) { String logId = threadLocal.get(); // 执行记录logId if(e == null){ // 没有异常修改记录为成功 jobService.updateScheduleLog(logId, JobLogStatusEnum.SUCCESS.getCode(), "执行成功"); }else{ // 存在异常修改记录失败,并且把异常信息保存到数据库中 jobService.updateScheduleLog(logId, JobLogStatusEnum.ERROR.getCode(), e.toString()); } } }

第四步:重启项目的时候确保定时任务启动

  • 需要在项目初始化的时候,把数据中启动的任务添加到数据库中
  • 该类会在项目启动后执行: xxx implements ServletContextAware
java
@Configuration public class ScheduleJobInitConfig implements ServletContextAware { private static final Logger logger = LoggerFactory.getLogger(ScheduleJobInitConfig.class); @Autowired private JobService jobService; @Override public void setServletContext(ServletContext servletContext) { jobService.initScheduleJob(); logger.info("初始化定时任务成功 ..."); } }

第五步:创建一个定时任务

java
/** * @calssName JobA * @Description 每写一个定时任务需要在数据库中schedule_job中配置一条数据,game-admin项目初始化时会加载数据库中的已启动的定时任务 * @Author xxx * @DATE 2020/5/28 11:14 */ @Component public class JobA implements Job { private static final Logger logger = LoggerFactory.getLogger(JobA.class); private JobService jobService; @Override public void execute(JobExecutionContext context) { System.out.println("=================>JobA "); // 这里写需要执行的业务逻辑 } }

控制任务的接口方法,对任务的增删该查 都在 下面的service类中

  • 完成以上这些,定时任务模块基本完成。接下来的就是为了控制界面提供的接口。我们这边前后的分离于是我直接使用postman调用接口控制。
  • controller
java
/** * @calssName ScheduleController * @Description TODO * @Author xxx * @DATE 2020/5/28 12:26 */ @RestController @RequestMapping("/v1/schedule/job") public class JobController { @Autowired private JobService jobService; /** * 查看所有任务列表 */ @GetMapping("/list") public R getJobList(){ try{ Map<String, Object> result = jobService.getAllJob(); return R.data(result); }catch (Exception e){ return R.error(e); } } /** * 查看某个任务的执行详情 */ @GetMapping("/schedule/detail") public R getJobScheduleDetail(@RequestParam("jobId") int jobId){ try{ Map<String, Object> result = jobService.getJobScheduleDetail(jobId); return R.data(result); }catch (Exception e){ return R.error(e); } } /** * 查看某个任务的详情 */ @GetMapping("/detail") public R getJobDetail(@RequestParam("jobId") int jobId){ try{ Map<String, Object> result = jobService.getJobDetail(jobId); return R.data(result); }catch (Exception e){ return R.error(e); } } /** * 修改任务 */ @PostMapping("/update") public R updateJob(ScheduleJob job){ try{ Map<String, Object> result = jobService.updateJob(job); return R.data(result); }catch (Exception e){ return R.error(e); } } }
  • service
java
/** * @calssName JobService * @Description * @Author xxx * @DATE 2020/5/28 15:59 */ @Service public class JobService { private static final Logger logger = LoggerFactory.getLogger(JobService.class); @Autowired private ScheduleJobRepoImpl scheduleJobRepo; @Autowired private ScheduleLogRepoImpl scheduleLogRepo; /** * 查看所有任务列表 */ public Map<String,Object> getAllJob(){ List<ScheduleJob> list = scheduleJobRepo.getList(); Map<String,Object> result = new HashMap<>(); result.put("list", list); return result; } /** * 查看某个任务的执行详情 */ public Map<String,Object> getJobScheduleDetail(int jobId){ List<ScheduleLog> list = scheduleLogRepo.getListByJobId(jobId); Map<String,Object> result = new HashMap<>(); result.put("list", list); return result; } /** * 查看某个任务的详情 */ public Map<String,Object> getJobDetail(int jobId){ ScheduleJob scheduleJob = scheduleJobRepo.getScheduleJobById(jobId); Map<String,Object> result = new HashMap<>(); result.put("scheduleJob", scheduleJob); return result; } /** * 修改任务 */ public Map<String,Object> updateJob(ScheduleJob scheduleJob){ Map<String,Object> result = new HashMap<>(); Class<?> clszz = null; try { clszz = Class.forName(scheduleJob.getClazz()); }catch (ClassNotFoundException e){ logger.error("修改定时任务异常,无法找到指定的任务类"); throw new AppException(ErrorCode.SYS_PARAMS_ERROR.code(), "无法找到指定的任务类"); } try { Integer status = scheduleJob.getStatus(); if(status == 1){ // 修改或者添加任务 JobSchedule.modifyOrAddJobTime(scheduleJob.getName(), scheduleJob.getCron(),clszz); }else if(status == 2){ // 停止任务 JobSchedule.removeJob(scheduleJob.getName()); }else{ return result; } }catch (SchedulerException e){ logger.error("修改定时任务异常:{}", e); throw new AppException(ErrorCode.SYS_ERROR); } scheduleJobRepo.updateScheduleJob(scheduleJob); return result; } /** * 启动数据库中配置的定时任务 */ public void initScheduleJob(){ List<ScheduleJob> list = scheduleJobRepo.getEnableList(); for (ScheduleJob scheduleJob: list){ Class<?> clszz = null; try { clszz = Class.forName(scheduleJob.getClazz()); JobSchedule.addJob(scheduleJob.getName(), clszz, scheduleJob.getCron()); } catch (SchedulerException e){ logger.error("初始化启动定时任务异常:{}", e); continue; } catch (ClassNotFoundException e){ logger.error("初始化启动定时任务异常,无法找到指定的任务类"); continue; } } } /** * 新增一条执行日志 */ public String saveScheduleLog(String name, int status, String logInfo){ ScheduleJob scheduleJob = scheduleJobRepo.getScheduleJobByName(name); ScheduleLog scheduleLog = new ScheduleLog(scheduleJob,status,logInfo); scheduleLogRepo.saveScheduleLog(scheduleLog); return scheduleLog.getId(); } /** * 更新一条执行日志 */ public void updateScheduleLog(String id, int status, String logInfo){ scheduleLogRepo.updateScheduleLog(id,status,logInfo); } }

最后

  • 如果在这里获得过启发和思考,希望点赞支持!对于内容有不同的看法欢迎来信交流。
  • 技术栈 >> java
  • 邮箱 >> 15673219519@163.com

本文作者:jiangshaoneng

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!