springboot + quartz 可视化 前后端分离版本 附源码

丶灬走出姿态 提交于 2020-01-13 08:46:39

quartz介绍

框架介绍

本文是一个quartz的demo版本,下载源码后刷表并修改配置文件数据库连接即可运行。

quartz基本对象

  • Scheduler:调度器,start()之后才可以正常调度任务。
  • Jobdetail :维护job信息的对象,通过jobName,jobGroupName确定获得唯一任务对象。
  • CronTrigger:cron表达式类型触发器,通过triggerName,triggerGroupName确定获得唯一任务对象,根据cron表达式触发job。
  • JobDataMap相当于每个jobdetail的本地变量,可以存储key-value值。

demo源码链接

demo基本功能描述

  • 定时任务增删改查
  • 立即执行
  • 暂停/恢复

demo页面效果图

  • 列表页
    在这里插入图片描述
  • 任务新增/编辑
    在这里插入图片描述
    在这里插入图片描述

demo后端实现过程

1.新建springboot 工程并引入所需依赖

		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>


        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <!--换成自己的数据库驱动-->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.2.5</version>
        </dependency>


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

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


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

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.0</version>
        </dependency>

2.建表语句
3.配置quartz&mybatis

server:
  port: xxxx

spring:
  #quartz属性
  quartz:
    properties:
      org:
        quartz:
          #quartz数据库连接
          dataSource:
            qzDS:
              URL: *****
              driver: org.postgresql.Driver
              maxConnections: 10
              password: *****
              user: *****
          #quartzjob存储方式
          jobStore:
            #jdbc持久化
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            #数据源连接名
            dataSource: qzDS
            #驱动代表
            driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
            #触发器忍耐时间 超过5s则错过
            misfireThreshold: 5000
            #表前缀
            tablePrefix: QRTZ_
            useProperties: false
          scheduler:
            instanceName: DEMO_JOBS_1.0
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            makeThreadsDaemons: true
            threadCount: 5
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
  datasource:
    url: *****
    username: *****
    password: *****
    driver-class-name: org.postgresql.Driver

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.demo.task.entity

4.实现定时任务增删改查

  • 新增mapper与对应的xml文件,对于quartz的表只推荐查询操作。
package com.demo.job.mapper;

import com.demo.job.entity.JobAndTrigger;

import java.util.List;

public interface JobAndTriggerMapper {

   List<JobAndTrigger> getJobAndTriggerDetails();

   void updateTriggerPreTriggerTime(Long time);

   Integer queryJobByNameAndGroupName(String jobName,String jobGroupName);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.demo.job.mapper.JobAndTriggerMapper">

	<resultMap id="jobdetail" type="com.demo.job.entity.JobAndTrigger">
		<result property="jobName"  jdbcType="VARCHAR"  column="JOB_NAME"/>
		<result property="jobGroup"  jdbcType="VARCHAR"  column="JOB_GROUP"/>
		<result property="jobClassName"  jdbcType="VARCHAR"  column="JOB_CLASS_NAME"/>
		<result property="triggerName"  jdbcType="VARCHAR"  column="TRIGGER_NAME"/>
		<result property="triggerGroup"  jdbcType="VARCHAR"  column="TRIGGER_GROUP"/>
		<result property="prevFireTime"  jdbcType="VARCHAR"  column="PREV_FIRE_TIME"/>
		<result property="nextFireTime"  jdbcType="VARCHAR"  column="NEXT_FIRE_TIME"/>
		<result property="cronExpression"  jdbcType="VARCHAR"  column="CRON_EXPRESSION"/>
		<result property="triggerState"  jdbcType="VARCHAR"  column="TRIGGER_STATE"/>
	</resultMap>


	<select id="queryJobByNameAndGroupName" resultType="java.lang.Integer">
		SELECT COUNT(1) FROM qrtz_job_details WHERE JOB_NAME = #{jobName} AND JOB_GROUP = #{jobGroupName} AND SCHED_NAME='DEMO_JOBS_1.0'
	</select>


	<select id="getJobAndTriggerDetails" resultMap="jobdetail">
			SELECT
				a.JOB_NAME,
				a.JOB_GROUP,
				a.JOB_CLASS_NAME,
				a.JOB_DATA,
				b.TRIGGER_NAME,
				b.TRIGGER_GROUP,
				case
				WHEN   b.PREV_FIRE_TIME =-1
				THEN '-'
				else to_char(to_timestamp(b.PREV_FIRE_TIME/1000),'yyyy-MM-dd HH24:MI:SS')  END as PREV_FIRE_TIME,
				to_char(to_timestamp(b.NEXT_FIRE_TIME/1000),'yyyy-MM-dd HH24:MI:SS') as NEXT_FIRE_TIME,
			    b.TRIGGER_STATE,
				c.CRON_EXPRESSION,
				c.TIME_ZONE_ID
			FROM
				qrtz_job_details a
				LEFT JOIN qrtz_triggers b ON a.JOB_NAME = b.JOB_NAME
				AND b.TRIGGER_GROUP = a.JOB_GROUP
				LEFT JOIN qrtz_cron_triggers c ON b.TRIGGER_NAME = c.TRIGGER_NAME
				AND b.TRIGGER_GROUP = c.TRIGGER_GROUP
    </select>

	<update id="updateTriggerPreTriggerTime" parameterType="java.lang.Long">

		update qrtz_triggers set PREV_FIRE_TIME = #{time}

	</update>

</mapper>

entity

package com.demo.job.entity;

import lombok.Data;

/**
 * @description:
 * @author: jiangjie
 * @date: 2019/12/24
 */
@Data
public class JobAndTrigger {

	private String jobName;

	private String jobGroup;

	private String jobClassName;

	private String triggerName;

	private String triggerGroup;

	private String cronExpression;

	private String triggerState;

	private String prevFireTime;

    private String nextFireTime;
}

  • 新增service与controller
package com.demo.job.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.demo.job.singleton.SystemCache;
import com.demo.job.constants.CommonConstant;
import com.demo.job.entity.JobAndTrigger;
import com.demo.job.entity.TaskInfo;
import com.demo.job.mapper.JobAndTriggerMapper;
import com.demo.job.service.IJobService;
import com.demo.job.vo.Result;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
public class JobServiceImpl implements IJobService {

    private static Logger log = LoggerFactory.getLogger(JobServiceImpl.class);

    @Autowired
    private Scheduler scheduler;

    @Resource
    private JobAndTriggerMapper jobAndTriggerMapper;


    @Override
    public Result getJobAndTriggerList(int pageNum, int pageSize) {
        Map<String, Object> map = new HashMap<>();
        try {
            PageHelper.startPage(pageNum, pageSize);
            List<JobAndTrigger> list = jobAndTriggerMapper.getJobAndTriggerDetails();
            PageInfo<JobAndTrigger> page = new PageInfo<JobAndTrigger>(list);
            List<TaskInfo> taskInfoList = (List<TaskInfo>) SystemCache.getInstance().getCacheMap().get(CommonConstant.TASK_CACHE_KEY);
            map.put("JobAndTriggerList", page.getList());
            map.put("number", page.getTotal());
            map.put("taskList", taskInfoList);
        } catch (Exception e) {
            e.printStackTrace();
            Result.FAIL("查询失败");
        }
        return Result.OK(map);
    }

    @Override
    public Result createJobAndTrigger(String jobClassName, String jobName, String jobGroupName,String cronExpression) {

        //构建job信息
        JobDetail jobDetail;
        try {

            jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobName, jobGroupName).build();

            //表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(jobName, jobGroupName)
                    .withSchedule(scheduleBuilder).build();

            scheduler.scheduleJob(jobDetail, trigger);

            return Result.OK();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("创建定时任务失败 ", e);
            return Result.FAIL("创建定时任务失败");
        }
    }


    @Override
    public Result pauseJob(String jobName, String jobGroupName) {
        try {
            scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));
            return Result.OK();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("暂停定时任务失败", e);
            return Result.FAIL("暂停定时任务失败");

        }
    }

    @Override
    public Result resumeJob(String jobClassName, String jobGroupName) {
        try {
            scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
            return Result.OK();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("恢复定时任务失败", e);
            return Result.FAIL("恢复定时任务失败");
        }
    }

    @Override
    public Result rescheduleJob(String jobName, String jobGroupName, String cronExpression) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
            CronTrigger cronTrigger = rebulidTriggerKey(jobName, jobGroupName, cronExpression);
            scheduler.rescheduleJob(triggerKey, cronTrigger);
            return Result.OK();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("更新定时任务失败", e);
            return Result.FAIL("更新定时任务失败");
        }
    }

    @Override
    public Result deleteJob(String jobClassName, String jobGroupName) {

        try {
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
            scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
            return Result.OK();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("删除定时任务失败", e);
            return Result.FAIL("删除定时任务失败");
        }

    }

    @Override
    public Result triggerJobAtOnce(String jobName, String jobGroupName) {
        JobKey jobKey = new JobKey(jobName, jobGroupName);
        try {
            scheduler.triggerJob(jobKey);
            return Result.OK();
        } catch (SchedulerException e) {
            log.error("立即执行任务时发生异常", e);
            return Result.FAIL("立即执行任务时发生异常");

        }
    }

    @Override
    public Result validateJobNameAndGroupName(String jobName, String jobGroupName) {
        return Result.OK(jobAndTriggerMapper.queryJobByNameAndGroupName(jobName, jobGroupName) == 0);
    }


    private Job getClass(String classname) throws Exception {
        Class<?> clazz = Class.forName(classname);
        return (Job) clazz.newInstance();
    }

    private CronTrigger rebulidTriggerKey(String jobName, String jobGroupName, String cronExpression) {

        //重置crontrigger
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
        // 表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        CronTrigger trigger = null;
        try {
            trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        // 按新的cronExpression表达式重新构建trigger
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        return trigger;
    }

}
 
package com.demo.job.controller;

import com.demo.job.service.IJobService;
import com.demo.job.vo.JobAddOrUpdateReqVo;
import com.demo.job.vo.JobOptReqVo;
import com.demo.job.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping(value = "/job")
public class JobController {
    @Autowired
    private IJobService jobService;


    @ResponseBody
    @PostMapping(value = "/addjob")
    public Result addjob(@RequestBody JobAddOrUpdateReqVo jobAddOrUpdateReqVo) {

        return jobService.createJobAndTrigger( jobAddOrUpdateReqVo.getJobClassName(),
                jobAddOrUpdateReqVo.getJobName(),
                jobAddOrUpdateReqVo.getJobGroupName(),
                jobAddOrUpdateReqVo.getCronExpression());

    }


    @PostMapping(value = "/pausejob")
    public Result pausejob(@RequestBody JobOptReqVo jobOptReqVo) {
        return jobService.pauseJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
    }


    @PostMapping(value = "/resumejob")
    public Result resumejob(@RequestBody JobOptReqVo jobOptReqVo) {
        return jobService.resumeJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
    }


    @PostMapping(value = "/reschedulejob")
    public Result rescheduleJob(@RequestBody JobAddOrUpdateReqVo jobAddOrUpdateReqVo) {

        return jobService.rescheduleJob(
                jobAddOrUpdateReqVo.getJobName(),
                jobAddOrUpdateReqVo.getJobGroupName(),
                jobAddOrUpdateReqVo.getCronExpression());

    }

    @PostMapping(value = "/deletejob")
    public Result deletejob(@RequestBody JobOptReqVo jobOptReqVo) {
        return jobService.deleteJob(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
    }

    @GetMapping(value = "/queryjob")
    public Result queryjob(@RequestParam(value = "pageNum") Integer pageNum, @RequestParam(value = "pageSize") Integer pageSize) {
        return jobService.getJobAndTriggerList(pageNum, pageSize);
    }


    @PostMapping(value = "/triggerjob")
    public Result triggerJobAtOnce(@RequestBody JobOptReqVo jobOptReqVo) {
        return jobService.triggerJobAtOnce(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
    }

    @PostMapping(value = "/validateJobNameAndGroupName")
    public Result validateJobNameAndGroupName(@RequestBody JobOptReqVo jobOptReqVo) {
        return jobService.validateJobNameAndGroupName(jobOptReqVo.getJobName(), jobOptReqVo.getJobGroupName());
    }


}

  1. 自定义任务名称实现
  • 新增注解定义task任务名称

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TaskNode {

    String taskName();

}
  • 新增系统缓存,用单例维护一个map缓存。
public class SystemCache {

    private SystemCache() {

    }

    private static SystemCache sysCache = new SystemCache();

    private Map<String, Object> mapCache = new HashMap();

    public static SystemCache getInstance() {
        return sysCache;
    }

    public Map<String, Object> getCacheMap() {
        return mapCache;
    }


}

  • 新增ApplicationRunner监听器系统启动时扫描task包下所有任务并放入缓存,查询任务列表时从缓存取出返回
@Component
public class AppStaredListener implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //获取系统缓存
        Map<String, Object> cacheMap = SystemCache.getInstance().getCacheMap();
        //获取task包下的所有任务
        Set<Class<?>> taskSet = ClassUtil.getClasses("com.demo.job.task");
        List<TaskInfo> taskInfoList = new ArrayList<>();
        taskSet.stream().forEach(t -> {
            TaskNode taskNode = t.getAnnotation(TaskNode.class);
            if (taskNode != null) {
                TaskInfo taskInfo = new TaskInfo();
                taskInfo.setTaskClassName(t.getName());
                taskInfo.setTaskName(taskNode.taskName());
                taskInfoList.add(taskInfo);
            }
        });
        //将定时任务信息放入缓存中
        cacheMap.put(CommonConstant.TASK_CACHE_KEY, taskInfoList);

    }
}

6.新增任务

  • 新增定时任务,加上注解 execute 为具体任务逻辑
@TaskNode(taskName = "普通任务2")
public class Demo2Task implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        System.out.println("我是普通任务2");

    }
}

demo启动

运行JobApplication 访问http://127.0.0.1:8088/JobPage.html 新建定时任务并立即执行
在这里插入图片描述

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!