SpringBoot Quartz页面交互控制

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务。

SpringBoot对quartz进行了集成。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

<!-- mysql -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

<!-- jdbc -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

JobStore任务存储

RAMJobStoreJDBCJobStore

若使用jdbc存储需指定数据库如mysql及数据源datasource如jdbc或者mybatis

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.3.2</version>
</dependency>

application.yml配置

spring:
  server:
    port: 8080
    servlet:
      context-path: /lovin
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/quartz?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  quartz:
    job-store-type: jdbc #数据库方式
    jdbc:
      initialize-schema: never #不初始化表结构 never always
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO #默认主机名和时间戳生成实例ID,可以是任何字符串,但对于所有调度程序来说,必须是唯一的 对应qrtz_scheduler_state INSTANCE_NAME字段
            #instanceName: clusteredScheduler #quartzScheduler
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX #持久化配置
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #我们仅为数据库制作了特定于数据库的代理
            useProperties: false #以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
            tablePrefix: QRTZ_  #数据库表前缀
            misfireThreshold: 60000 #在被认为“失火”之前,调度程序将“容忍”一个Triggers将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)。
            clusterCheckinInterval: 5000 #设置此实例“检入”*与群集的其他实例的频率(以毫秒为单位)。影响检测失败实例的速度。
            isClustered: true #打开群集功能
          threadPool: #连接池
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

Quartz.jdbc.initialize-schema 改成always 会初始化表结构,生成标后改为never

注意tablePrefix: QRTZ_ 的大小写,查看数据库生成的表进行设置。

表名称 说明
qrtz_blob_triggers Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
qrtz_calendars 以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers 存储Cron Trigger,包括Cron表达式和时区信息。
qrtz_fired_triggers 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
qrtz_job_details 存储每一个已配置的Job的详细信息
qrtz_locks 存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps 存储已暂停的Trigger组的信息
qrtz_scheduler_state 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
qrtz_triggers 存储已配置的 Trigger的信息
qrzt_simprop_triggers

编写任务执行器

继承自QuartzJobBean, 此处添加@Component加入IOC容器,可以不添加通过class使用

@Slf4j
@Component
public class HelloJob extends QuartzJobBean {

  @Override
  protected void executeInternal (JobExecutionContext context) throws JobExecutionException {
    log.info("HelloJob "+System.currentTimeMillis());
  }
}

操作job的服务

spring-boot-starter-quartz集成注入了Scheduler,通过PostConstruct启动调度器。

@Service
public class QuartzService {

  @Autowired
  private Scheduler scheduler;

  @PostConstruct
  public void startScheduler () {
    try {
      scheduler.start();
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 增加一个job
   *
   * @param jobClass     任务实现类
   * @param jobName      任务名称
   * @param jobGroupName 任务组名
   * @param jobTime      时间表达式 (这是每隔多少秒为一次任务)
   * @param jobTimes     运行的次数 (<0:表示不限次数)
   * @param jobData      参数
   */
  public void addJob (Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime,
      int jobTimes, Map jobData) {
    try {
      // 任务名称和组构成任务key
      JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
          .build();
      // 设置job参数
      if (jobData != null && jobData.size() > 0) {
        jobDetail.getJobDataMap().putAll(jobData);
      }
      // 使用simpleTrigger规则
      Trigger trigger = null;
      if (jobTimes < 0) {
        trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
            .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime))
            .startNow().build();
      } else {
        trigger = TriggerBuilder
            .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder
                .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes))
            .startNow().build();
      }
      // 注册jobDetail与trigger到调度器
      scheduler.scheduleJob(jobDetail, trigger);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 增加一个job
   *
   * @param jobClass     任务实现类
   * @param jobName      任务名称(建议唯一)
   * @param jobGroupName 任务组名
   * @param jobTime      时间表达式 (如:0/5 * * * * ? )
   * @param jobData      参数
   */
  public void addJob (Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobTime, Map jobData) {
    try {
      // 创建jobDetail实例,绑定Job实现类
      // 指明job的名称,所在组的名称,以及绑定job类
      // 任务名称和组构成任务key
      JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
          .build();
      // 设置job参数
      if (jobData != null && jobData.size() > 0) {
        jobDetail.getJobDataMap().putAll(jobData);
      }
      // 定义调度触发规则
      // 使用cornTrigger规则
      // 触发器key
      Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
          .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
          .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).startNow().build();
      // 把作业和触发器注册到任务调度中
      scheduler.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 修改 一个job的 时间表达式
   *
   * @param jobName
   * @param jobGroupName
   * @param jobTime
   */
  public void updateJob (String jobName, String jobGroupName, String jobTime) {
    try {
      TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
      CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
      trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
          .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();
      // 重启触发器
      scheduler.rescheduleJob(triggerKey, trigger);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 删除任务一个job
   *
   * @param jobName      任务名称
   * @param jobGroupName 任务组名
   */
  public void deleteJob (String jobName, String jobGroupName) {
    try {
      scheduler.deleteJob(new JobKey(jobName, jobGroupName));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 暂停一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void pauseJob (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.pauseJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 恢复一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void resumeJob (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.resumeJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 立即执行一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void runAJobNow (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.triggerJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 获取所有计划中的任务列表
   *
   * @return
   */
  public List<Map<String, Object>> queryAllJob () {
    List<Map<String, Object>> jobList = null;
    try {
      GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
      Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
      jobList = new ArrayList<Map<String, Object>>();
      for (JobKey jobKey : jobKeys) {
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        for (Trigger trigger : triggers) {
          Map<String, Object> map = new HashMap<>();
          map.put("jobName", jobKey.getName());
          map.put("jobGroupName", jobKey.getGroup());
          map.put("description", "触发器:" + trigger.getKey());
          Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
          map.put("jobStatus", triggerState.name());
          if (trigger instanceof CronTrigger) {
            CronTrigger cronTrigger = (CronTrigger) trigger;
            String cronExpression = cronTrigger.getCronExpression();
            map.put("jobTime", cronExpression);
          }
          jobList.add(map);
        }
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return jobList;
  }

  /**
   * 获取所有正在运行的job
   *
   * @return
   */
  public List<Map<String, Object>> queryRunJob () {
    List<Map<String, Object>> jobList = null;
    try {
      List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
      jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
      for (JobExecutionContext executingJob : executingJobs) {
        Map<String, Object> map = new HashMap<String, Object>();
        JobDetail jobDetail = executingJob.getJobDetail();
        JobKey jobKey = jobDetail.getKey();
        Trigger trigger = executingJob.getTrigger();
        map.put("jobName", jobKey.getName());
        map.put("jobGroupName", jobKey.getGroup());
        map.put("description", "触发器:" + trigger.getKey());
        Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
        map.put("jobStatus", triggerState.name());
        if (trigger instanceof CronTrigger) {
          CronTrigger cronTrigger = (CronTrigger) trigger;
          String cronExpression = cronTrigger.getCronExpression();
          map.put("jobTime", cronExpression);
        }
        jobList.add(map);
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return jobList;
  }
}

Quartz控制器

与前端页面交互的control,前端页面交互时调用service实现对job的操控。

@Api(tags = "Quartz控制器")
@RestController
public class QuartzController {


  @Autowired
  private QuartzService quartzService;

  @ApiOperation("添加任务")
  @PostMapping("/addJob")
  public Object addJob (@RequestBody JobRequestDto requestDto){
    Object bean = SpringUtil.getBean(requestDto.getBeanName());
    if(!(bean instanceof QuartzJobBean)){
      return "bean type is not QuartzJobBean";
    }
    QuartzJobBean jobBean = (QuartzJobBean) bean;
    quartzService.addJob(jobBean.getClass(),requestDto.getJobName(),requestDto.getJobGroupName(),requestDto.getCron(),requestDto.getParams());

    return "success";
  }

  // ... updateJob deleteJob pauseJob resumeJob  runAJobNow queryAllJob queryRunJob
}
@Data
public class JobRequestDto {

  String beanName;

  String jobName;

  String jobGroupName;

  String cron;

  Map<String,Object> params;
}

启动服务,发送请求

{
  "beanName": "helloJob",
  "cron": "0/5 * * * * ?",
  "jobGroupName": "paw-demo",
  "jobName": "helloJob",
  "params": {}
}

就会看到控制台输出,HelloJob已启动

2021-08-12 14:20:30.029  INFO 17620 --- [eduler_Worker-7] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749230029
2021-08-12 14:20:35.035  INFO 17620 --- [eduler_Worker-8] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749235035
2021-08-12 14:20:40.030  INFO 17620 --- [eduler_Worker-9] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749240030
2021-08-12 14:20:45.031  INFO 17620 --- [duler_Worker-10] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749245031

查看数据库

相关数据已保存,如QRTZ_JOB_DETAILS

SCHED_NAME    JOB_NAME    JOB_GROUP    DESCRIPTION    JOB_CLASS_NAME    IS_DURABLE    IS_NONCONCURRENT    IS_UPDATE_DATA    REQUESTS_RECOVERY    JOB_DATA
quartzScheduler    helloJob    paw-demo        com.paw.quartz.jobs.HelloJob    0    0    0    0    

停止服务再次启动

quartz会通过jdbc从数据库获取配置,启动任务。

总结

Springboot提供了quartz的集成spring-boot-starter-quartz,可以采用jdbcStore的方式将job存储在数据库,服务重启任务不丢失。通过API接口对job进行控制,从而实现页面交互控制。用户只需要继承QuartzJobBean实现业务逻辑,然后通过页面将业务类bean提交到job中即可实现定时服务。

上一篇:21个微信快速加好友方法


下一篇:关于使用微信登录第三方APP的实现(Android版)