2021-2-6-定时任务

快速开始、核心概念、Job和JobDetail、上下文、有状态Job和无状态Job、Trigger、整合到springboot中、监听器、Quartz外的其他定时器、完整案例

快速开始

1)定义一个JOB

package com.example.test;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

public class PrintJob implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("currentTime:" + new Date());
    }
}

2)定义任务

package com.example.test;


import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        //创建一个scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        //创建一个job
        JobDetail job = JobBuilder.newJob(PrintJob.class)
                .usingJobData("j1", "jv1")
                .withIdentity("myjob", "mygroup").build();// name "myjob", group "mygroup"

        //创建一个Trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")// name "trigger1", group "group1"
                .usingJobData("t1", "tv1")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)//设置定时
                        .repeatForever()//设置重复
                ).build();

        scheduler.scheduleJob(job,trigger);

        scheduler.start();

    }
}


核心概念

1)任务Job

每个job必须实现org.quarts.job接口,且需实现接口定义的execute方法

2)触发器Trigger

出发执行job,有两种,分别为SimpleTrigger和CronTrigger

3)调度器Scheduler

任务调度器,将任务和触发器整合起来,负责基于Trigger设定的时间来执行job


Job和JobDetail

1)Job实例在Quarts生命周期:每次调度器执行job时,在execute方法前会创建一个新的job实例,当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收

2)JobDetail,为job实例提供许多设置属性,以及JobDataMap成员变量属性,用来存储特定job实例的状态信息,调度器需要借助JobDetail对象来添加job实例。其重要属性:name、group、jobClass、jobDataMap

package com.example.test;


import org.quartz.*;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        JobDetail job = JobBuilder.newJob(PrintJob.class)
                .usingJobData("j1", "jv1")
                .withIdentity("myjob", "mygroup").build();
        System.out.println(job.getKey().getName());//myjob
        System.out.println(job.getKey().getGroup());//mygroup,如果未设置为DEFAULT
        System.out.println(job.getKey().getClass());//class org.quartz.JobKey

    }
}


上下文

1)JobExecutionContext

当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法

Job能通过JobExecutionContext对象访问到Quarts运行时的环境以及job本身的明细数据

2)JobDataMap

在进行任务调度时,JobDataMap存储在JobExecutionContext中。可用来装载任何可序列化的数据对象,当Job实例对象被执行时这些参数对象会传递给它。JobDataMap实现了JDK的Map接口,并添加了非常方便的方法来存取基本数据类型

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("j1","jv1")//往JobDataMap中存数据
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();

    }
}

package com.example.demo;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

public class PrintJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //获取JobDataMap的数据
        System.out.println(context.getJobDetail().getJobDataMap().get("j1"));
    }
}


有状态Job和无状态Job

默认的Job是无状态的,如果要有状态需要添加注解

1)无状态情况下

package com.example.demo;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


public class PrintJob implements Job {
    private Integer count;

    public void setCount(Integer count) {
        this.count = count;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        count++;
        System.out.println(count);//一直打印1
        context.getJobDetail().getJobDataMap().put("count",count);
    }
}

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)//往JobDataMap中存数据
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();

    }
}

2)有状态

package com.example.demo;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;


@PersistJobDataAfterExecution//加上这个注解
public class PrintJob implements Job {
    private Integer count;

    public void setCount(Integer count) {
        this.count = count;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        count++;
        System.out.println(count);//一直打印1
        context.getJobDetail().getJobDataMap().put("count",count);
    }
}

省略构造定时任务的main函数


Trigger

1)设置任务开始时间、结束时间

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();

        //开始时间
        Date start = new Date();

        //结束时间
        Date end = new Date();
        end.setTime(end.getTime()+10000);//开始和结束时间间隔10秒

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .startAt(start)
                .endAt(end)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())//每5秒执行一次
                .build();

        //最终任务执行两次
        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();

    }
}

2)设置重复次数

package com.example.test;


import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail job = JobBuilder.newJob(PrintJob.class)
                .withIdentity("myjob", "mygroup").build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1","deafult")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                        .withRepeatCount(5) //设置重复次数,总共执行6次
                         )
                .build();

        scheduler.scheduleJob(job,trigger);
        scheduler.start();

    }
}

3)CronTrigger

基于日历来触发任务

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;


public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();
        
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();

    }
}

4)triggerh和job的关系

一个trigger只能对应一个job,一个job可对应多个trigger

5)挂起

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;


public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.start();
        scheduler.standby();//挂起
        try {
            Thread.sleep(50000L);//等待50秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduler.start();//重新启动
    }
}

6)关闭

关闭后,不可重启

7)获取任务开始时间

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;


public class Test {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        Date date = scheduler.scheduleJob(jobDetail,trigger);//获取任务执行开始的时间
        System.out.println(date);
        scheduler.start();

    }
}


整合到springboot中

1开始

1)依赖

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz
compile group: 'org.springframework.boot', name: 'spring-boot-starter-quartz', version: '2.4.2'

2)创建Job

package com.example.demo.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


public class PrintJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Integer count =(Integer) context.getJobDetail().getJobDataMap().get("count");
        System.out.println(count);
        count++;
        context.getJobDetail().getJobDataMap().put("count",count);
    }
}

3)创建配置类

package com.example.demo.config;

import com.example.demo.job.PrintJob;
import org.quartz.JobDataMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;



@Configuration
public class QuartzConfig {
    @Bean
    public JobDetailFactoryBean getJob(){
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(PrintJob.class);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("count",0);
        factoryBean.setJobDataMap(jobDataMap);
        return factoryBean;
    }

    @Bean
    public SimpleTriggerFactoryBean getTrigger(JobDetailFactoryBean jobDetailFactoryBean){
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        factoryBean.setRepeatInterval(1000);
        factoryBean.setRepeatCount(100);
        return factoryBean;
    }

    @Bean
    public SchedulerFactoryBean getSchedule(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
        return schedulerFactoryBean;
    }
}

2自动创建表

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
  quartz:
    job-store-type: jdbc //job保存方式设置为jdbc
    jdbc:
      initialize-schema: always //启动时总是初始化表

3通用方式

1)配置

package com.zhanghuan.scheduler.job.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class ScheduleConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        //quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        //线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        //JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        //集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");

        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");

        //PostgreSQL数据库,需要打开此注释
        //prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");

        factory.setQuartzProperties(prop);

        factory.setSchedulerName("RenrenScheduler");
        //延时启动
        factory.setStartupDelay(30);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        //可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        //设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

2)注入Schedule



package com.zhanghuan.scheduler.job.controller;


import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import com.zhanghuan.scheduler.job.utils.TestJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.Map;

@RestController
@RequestMapping("/sys/schedule")
public class ScheduleJobController {
	@Autowired
	Scheduler scheduler;
												
	@RequestMapping("/s")
	public R getScheduler() throws SchedulerException {
		JobDetail jobDetail = JobBuilder.newJob(TestJob.class).build();
		Trigger trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ? ")).build();
        //使用注入的scheduler绑定任务和触发器
		Date date = scheduler.scheduleJob(jobDetail,trigger);
		return R.ok().put("scheduler",date);
	}

}


监听器

1JobListener监听器

1)定义监听器

package com.example.demo;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MyJobListener implements JobListener {
    @Override
    public String getName() {
        String name = this.getClass().getSimpleName();
        System.out.println(name);
        return name;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        //Scheduler在jobDetail将要被执行时调用
        String name = context.getJobDetail().getKey().getName();
        System.out.println(name+"will execute");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        //Scheduler在jobDetail将要被执行,但又被TriggerLister否决时执行
        System.out.println("2");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        //Scheduler在jobDetail执行后时调用
        System.out.println("3");
    }
}

2)添加监听器

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;

import java.util.Properties;


public class Test {
    public static void main(String[] args) throws SchedulerException {

        Properties properties = new Properties();
        properties.put("org.quartz.threadPool.threadCount","10");

        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());//添加监听器
        scheduler.start();

    }
}

2TriggerListener监听器

1)定义监听器

package com.example.demo;

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;

public class MyTriggerListener implements TriggerListener {
    @Override
    public String getName() {
        return this.getClass().getSimpleName();//必须返回,否则抛出异常
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        //Trigger被触发时执行
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        //假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行
        return false;
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        //Trigger 错过触发时
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
        //Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法
        System.out.println("Trigger complete Job");
    }
}

2)添加监听器

package com.example.demo;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;

import java.util.Properties;


public class Test {
    public static void main(String[] args) throws SchedulerException {

        Properties properties = new Properties();
        properties.put("org.quartz.threadPool.threadCount","10");

        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        

        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
                .usingJobData("count",0)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("t1","default")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("t1","default")));
        scheduler.start();

    }
}


Quartz外的其他定时器

1)Timer

package com.example.test;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(new Date());
            }
        },1000,5000);////延时1s,之后每隔5s运行一次
    }
}

2)spingboot项目自带的定时任务

(1)方法上加注解

package com.example.test.controller;

import com.example.test.entity.Book;
import com.example.test.mapper.BookMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.UUID;

@RestController
public class BBB {
    @Autowired
    BookMapper bookMapper;

    @GetMapping("/book/list")
    @Scheduled(cron = "1/5 * * * * ?")//定时任务的注解
    public List<Book> getBooks(){
        return bookMapper.selectList(null);
    }

    @GetMapping("/book/insert")
    @Scheduled(cron = "1/5 * * * * ?")//定时任务的注解,只支持6位,年份是不支持的,带年份的7位格式会报错
    public String insertBook(){
        String uuid = UUID.randomUUID().toString().replace("-","");
        Book book = new Book(uuid,"老人与海","海明威");
        bookMapper.insert(book);
        return "ok";
    }
}

  • @Scheduled(fixedRate = 6000):上一次开始执行时间点之后 6 秒再执行。
  • @Scheduled(fixedDelay = 6000):上一次执行完毕时间点之后 6 秒再执行。
  • @Scheduled(initialDelay=1000, fixedRate=6000):第一次延迟 1 秒后执行,之后按 fixedRate 的规则每 6 秒执行一次。

(2)启动类开启定时任务

package com.example.test;

import com.example.test.util.DatabaseSnapShot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.sql.SQLException;

@SpringBootApplication
@MapperScan("com.example.test.mapper")
@EnableScheduling//启动类加这个注解
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

}


完整案例

1设计思想

1)创建任务

使用一张表存放bean信息,以表中的id作为唯一标识,每次创建任务时将bean信息通过contex传入任务模板,执行任务模板时,会读取contex中的bean,反射形式创建bean对象并执行其run方法

2)任务管理

以数据库为中心,id作为当前任务唯一标识,同步管理quartz中的任务实例

3)任务操作

暂停任务,调用scheduler.pauseJob(getJobKey(jobId))

恢复任务,调用scheduler.resumeJob(getJobKey(jobId))

创建任务,调用scheduler.scheduleJob(jobDetail, trigger)

更新任务,调用scheduler.rescheduleJob(triggerKey, trigger)

立即执行任务,调用scheduler.triggerJob(getJobKey(scheduleJob.getJobId()),dataMap)

删除任务,调用scheduler.deleteJob(getJobKey(jobId))

任务操作完成后,会将状态同步更新到数据库中

4)启动初始化

每次springboot启动时,会从数据库中读取所有任务信息,并根据任务的状态标识,将任务初始化

2

1)定义任务表

CREATE TABLE `schedule_job` (
  `job_id` varchar(255) NOT NULL COMMENT '任务id',
  `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
  `params` varchar(2000) DEFAULT NULL COMMENT '参数',
  `cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式',
  `status` tinyint(4) DEFAULT NULL COMMENT '任务状态  0:正常  1:暂停',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`job_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='定时任务'

2)任务日志表

CREATE TABLE `schedule_job_log` (
  `log_id` bigint(255) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
  `job_id` varchar(255) NOT NULL COMMENT '任务id',
  `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
  `params` varchar(2000) DEFAULT NULL COMMENT '参数',
  `status` tinyint(4) NOT NULL COMMENT '任务状态    0:成功    1:失败',
  `error` varchar(2000) DEFAULT NULL COMMENT '失败信息',
  `times` int(11) NOT NULL COMMENT '耗时(单位:毫秒)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`log_id`) USING BTREE,
  KEY `job_id` (`job_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=23979 DEFAULT CHARSET=utf8 COMMENT='定时任务日志'

3代码

1)配置类



package com.zhanghuan.scheduler.job.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class ScheduleConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        //quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        //线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        //JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        //集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");

        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");

        //PostgreSQL数据库,需要打开此注释
        //prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");

        factory.setQuartzProperties(prop);

        factory.setSchedulerName("RenrenScheduler");
        //延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        //可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        //设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

2)定义工具类

(1)spring中获取bean对象的工具类

package com.zhanghuan.scheduler.job.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class BaseHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    /**
     * 服务器启动,Spring容器初始化时,当加载了当前类为bean组件后,
     * 将会调用下面方法注入ApplicationContext实例
     */
    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        System.out.println("初始化了");
        BaseHolder.applicationContext = arg0;
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    /**
     * 外部调用这个getBean方法就可以手动获取到bean
     * 用bean组件的name来获取bean
     * @param beanName
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T>T getBean(String beanName){
        return (T) applicationContext.getBean(beanName);
    }
}

(2)定义通用定时任务



package com.zhanghuan.scheduler.job.utils;

import com.zhanghuan.scheduler.job.dao.ScheduleJobLogDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.lang.reflect.Method;
import java.util.Date;


/**
 * 定时任务
 *
 */
public class ScheduleJob extends QuartzJobBean {
	private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void executeInternal(JobExecutionContext context) {
        ScheduleJobEntity scheduleJob = (ScheduleJobEntity) context.getMergedJobDataMap()
        		.get(ScheduleJobEntity.JOB_PARAM_KEY);

        ScheduleJobLogDao scheduleJobLogDao = BaseHolder.getBean("scheduleJobLogDao");
        
        //任务开始时间
        long startTime = System.currentTimeMillis();

		ScheduleJobLogEntity scheduleJobLogEntity = new ScheduleJobLogEntity();
		scheduleJobLogEntity.setJobId(scheduleJob.getJobId());
		scheduleJobLogEntity.setBeanName(scheduleJob.getBeanName());
		scheduleJobLogEntity.setParams(scheduleJob.getParams());
		scheduleJobLogEntity.setCreateTime(new Date());
        
        try {
            //执行任务
        	logger.debug("任务准备执行,任务ID:" + scheduleJob.getJobId());

			Object target = BaseHolder.getBean(scheduleJob.getBeanName());
			Method method = target.getClass().getDeclaredMethod("run", String.class);
			method.invoke(target, scheduleJob.getParams());

			long times = System.currentTimeMillis()-startTime;
			scheduleJobLogEntity.setTimes((int)times);
			//失败:1 成功:0
			scheduleJobLogEntity.setStatus(0);

		} catch (Exception e) {
			logger.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
			long times = System.currentTimeMillis()-startTime;
			scheduleJobLogEntity.setTimes((int)times);
			//失败:1 成功:0
			scheduleJobLogEntity.setStatus(1);
			scheduleJobLogEntity.setError(e.toString());
		}finally {
			scheduleJobLogDao.save(scheduleJobLogEntity);
		}
    }
}

(3)定义定时任务工具类



package com.zhanghuan.scheduler.job.utils;

import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.enums.ScheduleJobStatus;
import org.quartz.*;

/**
 * 定时任务工具类
 *
 */
public class ScheduleUtils {
    private final static String JOB_NAME = "TASK_";
    
    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(String jobId) {
        return TriggerKey.triggerKey(JOB_NAME + jobId);
    }
    
    /**
     * 获取jobKey
     */
    public static JobKey getJobKey(String jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }

    /**
     * 获取表达式触发器
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("获取定时任务CronTrigger出现异常",e);
        }
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
        	//构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId())).build();

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
            		.withMisfireHandlingInstructionDoNothing();

            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build();

            //放入参数,运行时的方法可以获取
            jobDetail.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);

            scheduler.scheduleJob(jobDetail, trigger);
            
            //暂停任务
            if(scheduleJob.getStatus() == ScheduleJobStatus.PAUSE.getValue()){
            	pauseJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("创建定时任务失败", e);
        }
    }
    
    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
            		.withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());
            
            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            
            //参数
            trigger.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);
            
            scheduler.rescheduleJob(triggerKey, trigger);
            
            //暂停任务
            if(scheduleJob.getStatus() == ScheduleJobStatus.PAUSE.getValue()){
            	pauseJob(scheduler, scheduleJob.getJobId());
            }
            
        } catch (SchedulerException e) {
            throw  new RuntimeException("更新定时任务失败", e);
        }
    }

    /**
     * 立即执行任务
     */
    public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
        try {
        	//参数
        	JobDataMap dataMap = new JobDataMap();
        	dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);
        	
            scheduler.triggerJob(getJobKey(scheduleJob.getJobId()),dataMap);
        } catch (SchedulerException e) {
            throw new RuntimeException("立即执行定时任务失败", e);
        }
    }

    /**
     * 暂停任务
     */
    public static void pauseJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("暂停定时任务失败", e);
        }
    }

    /**
     * 恢复任务
     */
    public static void resumeJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("暂停定时任务失败", e);
        }
    }

    /**
     * 删除定时任务
     */
    public static void deleteScheduleJob(Scheduler scheduler, String jobId) {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RuntimeException("删除定时任务失败", e);
        }
    }
}

4)定义实体类

(1)任务实体



package com.zhanghuan.scheduler.job.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * 定时任务
 *
 */
@Data
@Table(name = "schedule_job")
@Entity
public class ScheduleJobEntity implements Serializable {
	private static final long serialVersionUID = 1L;
	
	/**
	 * 任务调度参数key
	 */
    public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
	
	/**
	 * 任务id
	 */
	@Id
	private String jobId;

	/**
	 * spring bean名称
	 */
	@Column(name = "bean_name")
	private String beanName;
	
	/**
	 * 参数
	 */
	@Column(name = "params")
	private String params;
	
	/**
	 * cron表达式
	 */
	@Column(name = "cron_expression")
	private String cronExpression;

	/**
	 * 任务状态
	 */
	@Column(name = "status")
	private Integer status;

	/**
	 * 备注
	 */
	@Column(name = "remark")
	private String remark;

	/**
	 * 创建时间
	 */
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
	@Column(name = "create_time")
	private Date createTime;

}

(2)任务日志实体

package com.zhanghuan.scheduler.job.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * 定时任务日志
 *
 */
@Data
@Table(name = "schedule_job_log")
@Entity
public class ScheduleJobLogEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 日志id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long logId;

    /**
     * 任务id
     */
    private String jobId;

    /**
     * spring bean名称
     */
    private String beanName;

    /**
     * 参数
     */
    private String params;

    /**
     * 任务状态    0:成功    1:失败
     */
    private Integer status;

    /**
     * 失败信息
     */
    private String error;

    /**
     * 耗时(单位:毫秒)
     */
    private Integer times;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

}

(3)response实体

package com.zhanghuan.scheduler.job.entity;

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

/**
 * 返回数据
 *
 */
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
        put("msg", "success");
    }

    public Integer getCode() {
        return (Integer) this.get("code");
    }

    public String getMsg() {
        return (String) this.get("msg");
    }

    public static R error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    @Override
    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

5)定义任务状态的枚举类

package com.zhanghuan.scheduler.job.enums;

public enum ScheduleJobStatus {
    NORMAL(0),
    /**
     * 暂停
     */
    PAUSE(1);

    private int value;

    ScheduleJobStatus(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

6)定义持久层

(1)定时任务



package com.zhanghuan.scheduler.job.dao;


import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;


/**
 * 定时任务
 *
 */

public interface ScheduleJobDao extends JpaRepository<ScheduleJobEntity,String> {
    //分页按照beanName查找
    @Query(value="select * from schedule_job where bean_name like concat('%',?1,'%') limit ?2,?3",nativeQuery=true)
    List<ScheduleJobEntity> findAllByBeanNameLike(String beanName,int offset,int limit);
    //返回beanName查找的记录数
    long countAllByBeanNameLike(String beanName);
}

(2)任务日志

package com.zhanghuan.scheduler.job.dao;

import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * 任务日志
 *
 */
public interface ScheduleJobLogDao extends JpaRepository<ScheduleJobLogEntity,Long> {
    //分页按照beanName查找
    @Query(value="select * from schedule_job_log where bean_name like concat('%',?1,'%')  order by create_time desc limit ?2,?3",nativeQuery=true)
    List<ScheduleJobLogEntity> findAllByBeanNameLikeOrderByCreateTimeDesc(String beanName, int offset, int limit);
    //返回beanName查找的记录数
    long countAllByBeanNameLike(String beanName);
}

7)定义服务层

(1)定时任务



package com.zhanghuan.scheduler.job.service.impl;


import com.zhanghuan.scheduler.job.dao.ScheduleJobDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.enums.ScheduleJobStatus;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import com.zhanghuan.scheduler.job.utils.ScheduleUtils;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.*;

@Service("scheduleJobService")
public class ScheduleJobServiceImpl implements ScheduleJobService {

	@Autowired
	ScheduleJobDao scheduleJobDao;

	@Autowired
    private Scheduler scheduler;
	
	/**
	 * 项目启动时,初始化定时器
	 */
	@PostConstruct
	public void init(){
		List<ScheduleJobEntity> scheduleJobList = scheduleJobDao.findAll();
		for(ScheduleJobEntity scheduleJob : scheduleJobList){
			CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
            //如果不存在,则创建
            if(cronTrigger == null) {
                ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
            }else {
                ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
            }
		}
	}

	@Override
	public Map<String,Object> findAll(Map<String,Object> map){

		int page = Integer.valueOf((String) map.get("page")) ;
		int limit = Integer.valueOf((String) map.get("limit"));
		String beanName = (String) map.get("beanName");

		if(beanName==null){
			beanName="";
		}
		if( StringUtils.hasLength(beanName.trim()) ){
			int offset = (page-1)*limit;
			List<ScheduleJobEntity> content = scheduleJobDao.findAllByBeanNameLike(beanName,offset,limit);
			long total = scheduleJobDao.countAllByBeanNameLike("%"+beanName+"%");
			map.put("content",content);
			map.put("total",total);
		}else{
			PageRequest pageRequest = PageRequest.of(page-1,limit);
			Page pageObj = scheduleJobDao.findAll(pageRequest);
			List<ScheduleJobEntity> content = pageObj.getContent();
			long total = pageObj.getTotalElements();
			System.out.println(total);
			map.put("content",content);
			map.put("total",total);
		}
		return map;
	}

	@Override
	public ScheduleJobEntity findById(String id) {
		return scheduleJobDao.getOne(id);
	}


	@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveJob(ScheduleJobEntity scheduleJob) {
		scheduleJob.setJobId(UUID.randomUUID().toString().replace("-",""));
		scheduleJob.setCreateTime(new Date());
		scheduleJob.setStatus(ScheduleJobStatus.NORMAL.getValue());
        ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
		scheduleJobDao.save(scheduleJob);
    }
	
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void update(ScheduleJobEntity scheduleJob) {
        ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
        scheduleJobDao.save(scheduleJob);
    }

	@Override
	@Transactional(rollbackFor = Exception.class)
    public void deleteBatch(String[] jobIds) {
    	for(String jobId : jobIds){
    		ScheduleUtils.deleteScheduleJob(scheduler, jobId);
    	}
    	List<ScheduleJobEntity> list = scheduleJobDao.findAllById(Arrays.asList(jobIds));
    	scheduleJobDao.deleteInBatch(list);

	}

	@Override
    public int updateBatch(String[] jobIds, int status){
		List<ScheduleJobEntity> list = scheduleJobDao.findAllById(Arrays.asList(jobIds));
    	Map<String, Object> map = new HashMap<>(2);
    	int i = 0;
		for (ScheduleJobEntity job:list
			 ) {
			job.setStatus(status);
			scheduleJobDao.save(job);
			i++;
		}
    	return i;
    }
    
	@Override
	@Transactional(rollbackFor = Exception.class)
    public void run(String[] jobIds) {
		ScheduleJobEntity jobEntity;
    	for(String jobId : jobIds){
			jobEntity = scheduleJobDao.findById(jobId).get();
    		ScheduleUtils.run(scheduler, jobEntity );
    	}
    }

	@Override
	@Transactional(rollbackFor = Exception.class)
    public void pause(String[] jobIds) {
        for(String jobId : jobIds){
    		ScheduleUtils.pauseJob(scheduler, jobId);
    	}
        
    	updateBatch(jobIds, ScheduleJobStatus.PAUSE.getValue());
    }

	@Override
	@Transactional(rollbackFor = Exception.class)
    public void resume(String[] jobIds) {
    	for(String jobId : jobIds){
    		ScheduleUtils.resumeJob(scheduler, jobId);
    	}

    	updateBatch(jobIds,ScheduleJobStatus.NORMAL.getValue());
    }
    
}

(2)任务日志

package com.zhanghuan.scheduler.job.service.impl;


import com.zhanghuan.scheduler.job.dao.ScheduleJobLogDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

@Service("scheduleJobLogService")
public class ScheduleJobLogServiceImpl implements ScheduleJobLogService {

    @Autowired
    ScheduleJobLogDao scheduleJobLogDao;

    @Override
    public Map<String,Object> queryPage(Map<String, Object> map) {
        int page = Integer.valueOf((String) map.get("page")) ;
        int limit = Integer.valueOf((String) map.get("limit"));
        String beanName = (String) map.get("beanName");

        if(beanName==null){
            beanName="";
        }
        if( StringUtils.hasLength(beanName.trim()) ){
            int offset = (page-1)*limit;
            List<ScheduleJobLogEntity> content = scheduleJobLogDao.findAllByBeanNameLikeOrderByCreateTimeDesc(beanName,offset,limit);
            long total =scheduleJobLogDao.countAllByBeanNameLike("%"+beanName+"%");
            map.put("content",content);
            map.put("total",total);
        }else{
            PageRequest pageRequest = PageRequest.of(page-1,limit, Sort.by(Sort.Direction.DESC,"createTime"));
            Page pageObj = scheduleJobLogDao.findAll(pageRequest);
            List<ScheduleJobLogEntity> content = pageObj.getContent();
            long total = pageObj.getTotalElements();
            map.put("content",content);
            map.put("total",total);
        }
        return map;
    }
}

8)定义控制器

(1)定时任务



package com.zhanghuan.scheduler.job.controller;


import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

/**
 * 定时任务
 *
 */
@RestController
@RequestMapping("/sys/schedule")
@CrossOrigin
public class ScheduleJobController {
	@Autowired
	private ScheduleJobService scheduleJobService;

	@Autowired
	Scheduler scheduler;

	
	/**
	 * 定时任务列表
	 */
	@RequestMapping("/list")
	public R list(@RequestParam Map<String, Object> params){
		if( params==null || params.isEmpty() ){
			params = new HashMap<>();
			params.put("page","0");
			params.put("limit","10");
		}
		return R.ok().put("page", scheduleJobService.findAll(params));
	}
	
	/**
	 * 定时任务信息
	 */
	@RequestMapping("/info/{jobId}")
	public R info(@PathVariable("jobId") String jobId){
		ScheduleJobEntity schedule = scheduleJobService.findById(jobId);
		return R.ok().put("schedule", schedule);
	}
	
	/**
	 * 保存定时任务
	 */
	@RequestMapping("/save")
	public R save(@RequestBody ScheduleJobEntity scheduleJob){
		
		scheduleJobService.saveJob(scheduleJob);
		
		return R.ok();
	}
	
	/**
	 * 修改定时任务
	 */
	@RequestMapping("/update")
	public R update(@RequestBody ScheduleJobEntity scheduleJob){
				
		scheduleJobService.update(scheduleJob);
		
		return R.ok();
	}
	
	/**
	 * 删除定时任务
	 */
	@RequestMapping("/delete")
	public R delete(@RequestBody String[] jobIds){
		scheduleJobService.deleteBatch(jobIds);
		
		return R.ok();
	}
	
	/**
	 * 立即执行任务
	 */
	@RequestMapping("/run")
	public R run(@RequestBody String[] jobIds){
		scheduleJobService.run(jobIds);
		
		return R.ok();
	}
	
	/**
	 * 暂停定时任务
	 */
	@RequestMapping("/pause")
	public R pause(@RequestBody String[] jobIds){
		scheduleJobService.pause(jobIds);
		
		return R.ok();
	}
	
	/**
	 * 恢复定时任务
	 */
	@RequestMapping("/resume")
	public R resume(@RequestBody String[] jobIds){
		scheduleJobService.resume(jobIds);
		return R.ok();
	}

}

(2)任务日志

package com.zhanghuan.scheduler.job.controller;

import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.service.ScheduleJobLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/sys/schedulelog")
@CrossOrigin
public class ScheduleJobLogController {

    @Autowired
    ScheduleJobLogService scheduleJobLogService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        if( params==null || params.isEmpty() ){
            params = new HashMap<>();
            params.put("page",0);
            params.put("limit",10);
        }
        return R.ok().put("page", scheduleJobLogService.queryPage(params));
    }
}

9)定义任务bean的接口

所有任务必须实现该接口,并且注入到容器中



package com.zhanghuan.scheduler.job.task;

/**
 * 定时任务接口,所有定时任务都要实现该接口
 *
 */
public interface ITask {

    /**
     * 执行定时任务接口
     *
     * @param params   参数,多参数使用JSON数据
     */
    void run(String params);
}

10)前端

<template>
  <div>
    <el-row>
      <el-col :span="20" :offset="2">
        <el-row>
          <el-col :span="6" style="background:green">
            <el-input v-model="q.beanName" placeholder="请输入beanname"></el-input>
          </el-col>
          <el-col :span="10" class="text-left">
            <el-button @click="query()">查询</el-button>
            <el-button type="primary" @click="openDiolog('add')">新增</el-button>
            <el-button type="primary" @click="openDiolog('edit')">修改</el-button>
            <el-button type="primary" @click="del()">删除</el-button>
            <el-button type="primary" @click="pause()">暂停</el-button>
            <el-button type="primary" @click="resume()">恢复</el-button>
            <el-button type="primary" @click="run()">立即执行</el-button>
          </el-col>
          <el-col :span="8">
            <el-button type="danger" style="float:right" @click="openLogDiolog()">日志列表</el-button>
          </el-col>
        </el-row>
        <el-row style="margin-top:2rem;">
          <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%"
            @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55">
            </el-table-column>
            <el-table-column prop="jobId" label="任务ID"></el-table-column>
            <el-table-column prop="beanName" label="bean名称"></el-table-column>
            <el-table-column prop="params" label="参数"></el-table-column>
            <el-table-column prop="cronExpression" label="cron表达式"></el-table-column>
            <el-table-column prop="remark" label="备注"></el-table-column>
            <el-table-column prop="status" label="状态">
              <template slot-scope="scope">
                <el-tag type="success" v-if="scope.row.status==0">正常</el-tag>
                <el-tag type="danger" v-if="scope.row.status==1">暂停</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-row>
      </el-col>
    </el-row>
    <el-row style="margin-top:2rem;">
      <el-col :span="20" :offset="2">
        <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="q.page"
          :page-sizes="[10, 20, 30, 40]" :page-size="q.limit" layout="total, sizes, prev, pager, next, jumper"
          :total="q.total">
        </el-pagination>
      </el-col>
    </el-row>

    <el-dialog title="新增或编辑" :visible.sync="dialogFormVisible">
      <el-row>
        <el-col :span="4" :offset="2">
          <p style="text-align:center">bean名称</p>
        </el-col>
        <el-col :span="16">
          <el-input placeholder="spring bean名称" v-model="task.beanName"></el-input>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="4" :offset="2">
          <p style="text-align:center">参数</p>
        </el-col>
        <el-col :span="16">
          <el-input placeholder="参数" v-model="task.params"></el-input>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="4" :offset="2">
          <p style="text-align:center">cron表达式</p>
        </el-col>
        <el-col :span="16">
          <el-input placeholder="如:0 0 1 2 * * ?" v-model="task.cronExpression"></el-input>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="4" :offset="2">
          <p style="text-align:center">备注</p>
        </el-col>
        <el-col :span="16">
          <el-input placeholder="备注" v-model="task.remark"></el-input>
        </el-col>
      </el-row>

      <div slot="footer" class="dialog-footer">
        <el-button @click="closeDiolog()">取 消</el-button>
        <el-button type="primary" @click="submit()">确 定</el-button>
      </div>
    </el-dialog>

    <el-dialog title="日志列表" :visible.sync="dialogLogVisible" width="100%">
      <el-row>
        <el-col :span="6" style="background:green">
          <el-input v-model="q_log.beanName" placeholder="请输入beanname"></el-input>
        </el-col>
        <el-col :span="2">
          <el-button @click="querylog()">查询</el-button>
        </el-col>
      </el-row>
      <el-row style="margin-top:1rem;">
        <el-col>
          <el-table :data="logData">
            <el-table-column prop="logId" label="id"></el-table-column>
            <el-table-column prop="jobId" label="任务id"></el-table-column>
            <el-table-column prop="beanName" label="beanName"></el-table-column>
            <el-table-column prop="params" label="参数"></el-table-column>
            <el-table-column prop="error" label="错误信息"></el-table-column>
            <el-table-column prop="times" label="耗时(毫秒)"></el-table-column>
            <el-table-column prop="createTime" label="执行时间"></el-table-column>
          </el-table>
        </el-col>
      </el-row>
      <el-row style="margin-top:2rem;">
        <el-col>
          <el-pagination @size-change="handleLogSizeChange" @current-change="handleLogCurrentChange"
            :current-page="q_log.page" :page-sizes="[8, 20, 30, 40]" :page-size="q_log.limit"
            layout="total, sizes, prev, pager, next, jumper" :total="q_log.total">
          </el-pagination>
        </el-col>
      </el-row>
    </el-dialog>

  </div>

</template>

<script>
  //import func from '../../vue-temp/vue-editor-bridge';
  // @ is an alias to /src
  //import HelloWorld from '@/components/HelloWorld.vue'
  import _ from "lodash"

  export default {
    name: 'Home',
    data() {
      return {
        q: {
          beanName: "",
          page: 1,
          limit: 10,
          total: 1
        },
        q_log: {
          beanName: "",
          page: 1,
          limit: 8,
          total: 1
        },
        baseurl: this.$store.state.url,
        tableData: [],
        logData: [],
        multipleSelection: [],
        dialogFormVisible: false,
        dialogLogVisible: false,
        diologType: "add",
        task: {
          jobId: "",
          beanName: "",
          params: "",
          cronExpression: "",
          remark: ""
        }

      }
    },
    components: {
      //HelloWorld
    },
    methods: {
      toggleSelection(rows) {
        if (rows) {
          rows.forEach(row => {
            this.$refs.multipleTable.toggleRowSelection(row);
          });
        } else {
          this.$refs.multipleTable.clearSelection();
        }
      },
      handleSelectionChange(val) {
        this.multipleSelection = val;
      },
      handleSizeChange(limit) {
        this.q.limit = limit
        this.reload()
      },
      handleLogSizeChange(limit) {
        this.q_log.limit = limit
        this.reloadLog()
      },
      handleCurrentChange(page) {
        this.q.page = page
        this.reload()
      },
      handleLogCurrentChange(page) {
        this.q_log.page = page
        this.reloadLog()
      },
      query() {
        this.reload()
      },
      querylog() {
        this.reloadLog()
      },
      clearTask() {
        this.task.jobId = ""
        this.task.beanName = ""
        this.task.params = ""
        this.task.cronExpression = ""
        this.task.remark = ""
      },
      closeDiolog() {
        this.dialogFormVisible = false
        this.clearTask()
      },
      openDiolog(type) {
        if (type == "edit") {
          if (this.multipleSelection.length == 0) {
            this.$message({
              message: '未选择任何任务',
              type: 'warning'
            });
          } else if (this.multipleSelection.length > 1) {
            this.$message({
              message: '只可选择一条任务',
              type: 'warning'
            });
          } else {
            this.dialogFormVisible = true
            this.diologType = "edit"
            this.task = _.cloneDeep(this.multipleSelection[0])
          }

        } else if (type == "add") {
          this.diologType = "add"
          this.clearTask()
          this.dialogFormVisible = true
        }
      },
      openLogDiolog() {
        this.q_log.beanName=""
        this.reloadLog()
        this.dialogLogVisible = true
      },
      submit() {
        this.dialogFormVisible = false
        if (this.diologType == "add") {
          this.axios.post(this.baseurl + "/sys/schedule/save", this.task).then(res => {
            if (res.data.code == 0) {
              this.$message({
                type: 'success',
                message: '新增成功!'
              });
            } else {
              this.$message({
                type: 'error',
                message: '新增失败!'
              });
            }
            this.reload()
          })
        } else {
          this.axios.post(this.baseurl + "/sys/schedule/update", this.task).then(res => {
            if (res.data.code == 0) {
              this.$message({
                type: 'success',
                message: '编辑成功!'
              });
            } else {
              this.$message({
                type: 'error',
                message: '编辑失败!'
              });
            }
            this.reload()
          })
        }
      },
      del() {
        let ids = _.map(this.multipleSelection, "jobId")
        if (ids.length == 0) {
          this.$message({
            message: '未选择任何任务',
            type: 'warning'
          });
        } else {
          this.$confirm('此操作将永久删除该任务, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            this.axios.post(this.baseurl + "/sys/schedule/delete", ids).then(res => {
              if (res.data.code == 0) {
                this.$message({
                  type: 'success',
                  message: '删除成功!'
                });
              } else {
                this.$message({
                  type: 'error',
                  message: '删除失败!'
                });
              }
              this.reload()
            })

          }).catch(() => {
            this.$message({
              type: 'info',
              message: '已取消删除'
            });
          });
        }
      },
      run() {
        let ids = _.map(this.multipleSelection, "jobId")
        if (ids.length == 0) {
          this.$message({
            message: '未选择任何任务',
            type: 'warning'
          });
        } else {
          this.axios.post(this.baseurl + "/sys/schedule/run", ids).then(res => {
            if (res.data.code == 0) {
              this.$message({
                type: 'success',
                message: '运行成功!'
              });
            } else {
              this.$message({
                type: 'error',
                message: '运行失败!'
              });
            }
            this.reload()
          })
        }
      },
      resume() {
        let ids = _.map(this.multipleSelection, "jobId")
        if (ids.length == 0) {
          this.$message({
            message: '未选择任何任务',
            type: 'warning'
          });
        } else {
          this.axios.post(this.baseurl + "/sys/schedule/resume", ids).then(res => {
            if (res.data.code == 0) {
              this.$message({
                type: 'success',
                message: '恢复成功!'
              });
            } else {
              this.$message({
                type: 'error',
                message: '恢复失败!'
              });
            }
            this.reload()
          })
        }
      },
      pause() {
        let ids = _.map(this.multipleSelection, "jobId")
        if (ids.length == 0) {
          this.$message({
            message: '未选择任何任务',
            type: 'warning'
          });
        } else {
          this.axios.post(this.baseurl + "/sys/schedule/pause", ids).then(res => {
            if (res.data.code == 0) {
              this.$message({
                type: 'success',
                message: '暂停成功!'
              });
            } else {
              this.$message({
                type: 'error',
                message: '暂停失败!'
              });
            }
            this.reload()
            this.reload()
          })
        }
      },
      reload() {
        let that = this
        this.axios.get(this.baseurl + "/sys/schedule/list?" + "beanName=" + this.q.beanName + "&page=" + this.q.page +
          "&limit=" + this.q.limit).then(res => {
          that.$set(that.$data, 'tableData', res.data.page.content)
          that.q.total = res.data.page.total
        })
      },
      reloadLog() {
        let that = this
        this.axios.get(this.baseurl + "/sys/schedulelog/list?" + "beanName=" + this.q_log.beanName + "&page=" + this
          .q_log.page +
          "&limit=" + this.q_log.limit).then(res => {
          that.$set(that.$data, 'logData', res.data.page.content)
          that.q_log.total = res.data.page.total
        })
      }
    },
    created() {
      this.reload()
    }
  }
</script>
posted @   SylvesterZhang  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示