玩转 Spring Boot 集成篇(任务动态管理代码篇)

2022-02-25 09:51:32 浏览数 (1)

在日常项目研发中,定时任务可谓是必不可少的一环,如果面对任务执行周期固定,业务简单的场景,可直接使用 Spring Boot 内置注解方式实现任务;而如果考虑更为复杂的管理任务信息,在可以通过集成 Quartz 等开源轮子来助力业务研发。

本次主要分享一下 Spring Boot 集成 Quartz 任务框架后,如何实现任务的动态管理,更能够让研发人员专注业务任务的研发,那么就要逐一解决如下疑问。

疑问:是否可以通过 API 动态创建任务呢?

疑问:是否可以通过 API 编辑任务的执行时间呢?

疑问:是否可以通过 API 暂停/恢复任务呢?

疑问:是否可以通过 API 删除任务呢?

疑问:是否可以通过页面完成任务的 CRUD 呢?

考虑到下面的操作是一个大工程,为了方便,重新开启一个 Spring Boot 项目,为了进一步熟练使用 Spring Boot 相关各种 starter,本次选用 MyBatis 作为持久层框架。

1. 核心管理代码

1.1 任务控制器

定义 TaskController,提供用户操作任务的相关 API,例如查询任务列表、添加任务、暂停任务、恢复任务、删除任务。

代码语言:javascript复制
package com.example.demo.quartz.controller;

import com.example.demo.quartz.common.Result;
import com.example.demo.quartz.service.TaskInfoService;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 定时任务管理
 **/
@RestController
@RequestMapping("/task")
public class TaskController {

    @Autowired
    private TaskInfoService taskInfoService;

    /**定时器列表*/
    @PostMapping("/list")
    public Result list(@RequestBody TaskInfoReq reqVo) {
        return taskInfoService.selectTaskListByPage(reqVo);
    }

    /**定时器修改*/
    @PostMapping("/edit")
    public Result edit(@RequestBody TaskInfoReq reqVo) {
        return taskInfoService.updateJob(reqVo);
    }

    /**暂停任务*/
    @PostMapping("/pause")
    public Result pause(Integer taskId) {
        return taskInfoService.pauseJob(taskId);
    }

    /**增加任务*/
    @PostMapping("/add")
    public Result add(@RequestBody TaskInfoReq taskInfoReq) {
        return taskInfoService.addJob(taskInfoReq);
    }

    /**恢复任务*/
    @PostMapping("/resume")
    public Result resume(Integer taskId) {
        return taskInfoService.resumeJob(taskId);
    }

    /**删除任务*/
    @PostMapping("/del")
    public Result delete(@RequestBody TaskInfoReq reqVo) {
        return taskInfoService.delete(reqVo);
    }
}

1.2 任务管理

TaskManager 任务管理器,主要接收业务指令,来完成对 Quartz 容器进行操作。

代码语言:javascript复制
package com.example.demo.quartz.task;

import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.utils.SpringContextUtils;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 任务管理
 * 1、添加任务 2、更新任务 3、暂停任务 4、恢复任务
 **/
@Component
public class TaskManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(TaskManager.class);
    public static final String JOB_DEFAULT_GROUP_NAME = "JOB_DEFAULT_GROUP_NAME";
    public static final String TRIGGER_DEFAULT_GROUP_NAME = "TRIGGER_DEFAULT_GROUP_NAME";

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private SpringContextUtils springContextUtils;

    /**
     * 添加任务
     */
    public boolean addJob(TaskInfoReq taskInfoReq) {
        boolean flag = true;
        if (!CronExpression.isValidExpression(taskInfoReq.getCron())) {
            LOGGER.error("定时任务表达式有误:{}", taskInfoReq.getCron());
            return false;
        }
        try {
            String className = springContextUtils.getBean(taskInfoReq.getJobName()).getClass().getName();
            JobDetail jobDetail = JobBuilder.newJob().withIdentity(new JobKey(taskInfoReq.getJobName(), JOB_DEFAULT_GROUP_NAME))
                    .ofType((Class<Job>) Class.forName(className))
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .forJob(jobDetail)
                    .withSchedule(CronScheduleBuilder.cronSchedule(taskInfoReq.getCron()))
                    .withIdentity(new TriggerKey(taskInfoReq.getJobName(), TRIGGER_DEFAULT_GROUP_NAME))
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (Exception e) {
            LOGGER.error("添加定时任务异常:{}", e.getMessage(), e);
            flag = false;
        }
        return flag;
    }

    /**
     * 更新任务
     */
    public boolean updateJob(TaskInfo taskInfo) {
        boolean flag = true;
        try {
            JobKey jobKey = new JobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME);
            TriggerKey triggerKey = new TriggerKey(taskInfo.getJobName(), TRIGGER_DEFAULT_GROUP_NAME);
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) {
                Trigger newTrigger = TriggerBuilder.newTrigger()
                        .forJob(jobDetail)
                        .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCron()))
                        .withIdentity(triggerKey)
                        .build();
                scheduler.rescheduleJob(triggerKey, newTrigger);
            } else {
                LOGGER.info("更新任务失败,任务不存在,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron());
            }
            LOGGER.info("更新任务成功,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron());
        } catch (SchedulerException e) {
            LOGGER.error("更新定时任务失败:{}", e.getMessage(), e);
            flag = false;
        }
        return flag;
    }

    /**
     * 暂停任务
     */
    public boolean pauseJob(TaskInfo taskInfo) {
        try {
            scheduler.pauseJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME));
            LOGGER.info("任务暂停成功:{}", taskInfo.getId());
            return true;
        } catch (SchedulerException e) {
            LOGGER.error("暂停定时任务失败:{}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 恢复任务
     */
    public boolean resumeJob(TaskInfo taskInfo) {
        try {
            scheduler.resumeJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME));
            LOGGER.info("任务恢复成功:{}", taskInfo.getId());
            return true;
        } catch (SchedulerException e) {
            LOGGER.error("恢复定时任务失败:{}", e.getMessage(), e);
            return false;
        }
    }
}

1.3 启动管理

1.3.1 QuartzManager

Spring Boot 容器启动时,加载启动所有任务。

代码语言:javascript复制
package com.example.demo.quartz.config;

import com.example.demo.quartz.common.EnumTaskEnable;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.service.TaskInfoService;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

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

@Component
public class QuartzManager {

    private Logger logger = LoggerFactory.getLogger(QuartzManager.class);

    @Autowired
    private Scheduler scheduler;
    @Autowired
    private SpringJobFactory springJobFactory;
    @Autowired
    private TaskInfoService taskInfoService;

    @PostConstruct
    public void start() {
        //启动所有任务
        try {
            scheduler.setJobFactory(springJobFactory);
            // scheduler.clear();
            List<TaskInfo> tasks = taskInfoService.selectTasks();
            for (TaskInfo taskInfo : tasks) {
                if (EnumTaskEnable.START.getCode().equals(taskInfo.getStatus()) && !StringUtils.isEmpty(taskInfo.getCron())) {
                    TaskInfoReq data=new TaskInfoReq();
                    BeanUtils.copyProperties(taskInfo,data);
                    taskInfoService.addJob(data);
                }
            }
            logger.info("定时任务启动完成");
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException("定时任务初始化失败");
        }
    }
}

1.3.2 SpringJobFactory

代码语言:javascript复制
package com.example.demo.quartz.config;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * 解决spring bean注入Job的问题
 */
@Component
public class SpringJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

2. 支撑代码(表、entity、dao、service、utils)

支撑代码主要完成数据库的 CRUD 操作,实现方式很多种,不局限于 MyBatis,主要是抽象思想:能往数据库插入任务记录、查询任务记录就行。

2.1 任务信息表

代码语言:javascript复制
CREATE TABLE `SC_TASK_INFO` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `cron` varchar(32) DEFAULT NULL COMMENT '定时执行',
  `job_name` varchar(256) DEFAULT NULL COMMENT '任务名称',
  `status` char(1) DEFAULT '0' COMMENT '任务开启状态 0-关闭 2-开启',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT COMMENT='定时任务表';

2.2 实体类

代码语言:javascript复制
package com.example.demo.quartz.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

@Data
public class TaskInfo implements Serializable {
    private Integer id;
    private String cron;
    private String jobName;
    private String status;
    private Date createTime;
    private Date updateTime;
}

2.3 TaskInfoDao 定义

代码语言:javascript复制
package com.example.demo.quartz.dao;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface TaskInfoDao {
    TaskInfo selectByJobName(String jobName);
    List<TaskInfo> selectAll();
    List<TaskInfo> selectTaskInfos(TaskInfoReq taskInfo);
    int deleteByPrimaryKey(Integer id);
    int insertSelective(TaskInfo record);
    TaskInfo selectByPrimaryKey(Integer id);
    int updateByPrimaryKeySelective(TaskInfo record);
}

2.4 TaskInfoService 定义

代码语言:javascript复制
package com.example.demo.quartz.service;
import com.example.demo.quartz.common.Result;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.vo.TaskInfoReq;
import java.util.List;

/**
 * 定时任务接口
 **/
public interface TaskInfoService {
    /**获取任务列表分页*/
    Result selectTaskListByPage(TaskInfoReq taskInfoReq);
    /**添加定时任务*/
    Result addJob(TaskInfoReq taskInfoReq);
    /**更新任务*/
    Result updateJob(TaskInfoReq taskInfoReq);
    /**暂停任务*/
    Result pauseJob(Integer taskId);
    /**恢复任务*/
    Result resumeJob(Integer taskId);
    /**获取所有任务*/
    List<TaskInfo> selectTasks();
    /**删除任务*/
    Result delete(TaskInfoReq reqVo);
}

2.5 TaskInfoServiceImpl 业务实现类定义

代码语言:javascript复制
package com.example.demo.quartz.service.impl;

import com.example.demo.quartz.common.CodeMsg;
import com.example.demo.quartz.common.EnumTaskEnable;
import com.example.demo.quartz.common.ResponseFactory;
import com.example.demo.quartz.common.Result;
import com.example.demo.quartz.dao.TaskInfoDao;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.service.TaskInfoService;
import com.example.demo.quartz.task.TaskManager;
import com.example.demo.quartz.vo.TaskInfoReq;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 定时任务业务实现
 **/
@Service
public class TaskInfoServiceImpl implements TaskInfoService {

    private static final Logger LOGGER = LoggerFactory.getLogger(TaskInfoServiceImpl.class);

    @Resource
    private TaskInfoDao taskInfoDao;

    @Resource
    private TaskManager taskManager;

    @Override
    public Result selectTaskListByPage(TaskInfoReq taskInfoReq) {
        PageHelper.startPage(taskInfoReq.getPageCurrent(), taskInfoReq.getPageSize());
        List<TaskInfo> list = taskInfoDao.selectTaskInfos(taskInfoReq);
        PageInfo<TaskInfo> pageInfo = new PageInfo<>(list);
        return ResponseFactory.build(pageInfo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result updateJob(TaskInfoReq taskInfoReq) {
        if (!CronExpression.isValidExpression(taskInfoReq.getCron())) {
            LOGGER.error("更新任务失败,表达式有误:{}", taskInfoReq.getCron());
            return ResponseFactory.build(CodeMsg.TASK_CRON_ERROR);
        }
        TaskInfo isExistData = taskInfoDao.selectByJobName(taskInfoReq.getJobName());
        //当任务存在,则更改失败
        if ((!Objects.isNull(isExistData)) && (!isExistData.getId().equals(taskInfoReq.getId()))) {
            return ResponseFactory.build(CodeMsg.TASK_CRON_DOUBLE);
        }
        TaskInfo data = taskInfoDao.selectByPrimaryKey(taskInfoReq.getId());
        if (data == null) {
            return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES);
        }

        BeanUtils.copyProperties(taskInfoReq, data);
        data.setUpdateTime(new Date());
        taskInfoDao.updateByPrimaryKeySelective(data);

        if (!taskManager.updateJob(data)) {
            return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
        }
        return ResponseFactory.build();
    }

    @Override
    public Result pauseJob(Integer taskId) {
        TaskInfo data = taskInfoDao.selectByPrimaryKey(taskId);
        if (data == null) {
            return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES);
        }
        if (!taskManager.pauseJob(data)) {
            return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
        }
        data.setStatus(EnumTaskEnable.STOP.getCode());
        taskInfoDao.updateByPrimaryKeySelective(data);
        return ResponseFactory.build();
    }

    @Override
    public Result resumeJob(Integer taskId) {
        TaskInfo data = taskInfoDao.selectByPrimaryKey(taskId);
        if (data == null) {
            return ResponseFactory.build(CodeMsg.TASK_NOT_EXITES);
        }
        if (!taskManager.resumeJob(data)) {
            return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
        }
        data.setStatus(EnumTaskEnable.START.getCode());
        taskInfoDao.updateByPrimaryKeySelective(data);
        return ResponseFactory.build();
    }

    @Override
    public Result addJob(TaskInfoReq taskInfoReq) {
        if (!taskManager.addJob(taskInfoReq)) {
            return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
        }
        TaskInfo data = taskInfoDao.selectByJobName(taskInfoReq.getJobName());
        //当任务不存在,则返回成功插入
        if (Objects.isNull(data)) {
            data = new TaskInfo();
            BeanUtils.copyProperties(taskInfoReq, data);
            data.setCreateTime(new Date());
            taskInfoDao.insertSelective(data);
            return ResponseFactory.build();
        } else {
            return ResponseFactory.build(CodeMsg.TASK_CRON_DOUBLE);
        }

    }

    @Override
    public Result delete(TaskInfoReq reqVo) {
        try {
            //TODO 删除任务只是做了暂停,如果是 Quartz Jdbc 模式下添加重复任务可能加不进去,并没有真正删除(可自行调整)
            Result result = this.pauseJob(reqVo.getId());
            //只有暂停成功的任务才能删除
            if (CodeMsg.SUCCESS == result.getCode()) {
                taskInfoDao.deleteByPrimaryKey(reqVo.getId());
                return ResponseFactory.build();
            } else {
                return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
            }
        } catch (Exception e) {
            return ResponseFactory.build(CodeMsg.TASK_EXCEPTION);
        }
    }

    @Override
    public List<TaskInfo> selectTasks() {
        return taskInfoDao.selectAll();
    }
}

2.6 TaskInfoMapper.xml 文件

代码语言:javascript复制
<?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.example.demo.quartz.dao.TaskInfoDao">

    <resultMap id="BaseResultMap" type="com.example.demo.quartz.entity.TaskInfo">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="cron" property="cron" jdbcType="VARCHAR"/>
        <result column="job_name" property="jobName" jdbcType="VARCHAR"/>
        <result column="status" property="status" jdbcType="CHAR"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
    </resultMap>
    <sql id="Base_Column_List">
    id, cron, job_name, status, create_time, update_time
  </sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer">
        select
        <include refid="Base_Column_List"/>
        from sc_task_info
        where id = #{id,jdbcType=INTEGER}
    </select>
    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from sc_task_info
    where id = #{id,jdbcType=INTEGER}
  </delete>
    <insert id="insert" parameterType="com.example.demo.quartz.entity.TaskInfo">
        <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into sc_task_info (cron, job_name, status,
        create_time, update_time)
        values (#{cron,jdbcType=VARCHAR}, #{jobName,jdbcType=VARCHAR}, #{status,jdbcType=CHAR},
        #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP})
    </insert>
    <insert id="insertSelective" parameterType="com.example.demo.quartz.entity.TaskInfo">
        <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into sc_task_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="cron != null">
                cron,
            </if>
            <if test="jobName != null">
                job_name,
            </if>
            <if test="status != null">
                status,
            </if>
            <if test="createTime != null">
                create_time,
            </if>
            <if test="updateTime != null">
                update_time,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="cron != null">
                #{cron,jdbcType=VARCHAR},
            </if>
            <if test="jobName != null">
                #{jobName,jdbcType=VARCHAR},
            </if>
            <if test="status != null">
                #{status,jdbcType=CHAR},
            </if>
            <if test="createTime != null">
                #{createTime,jdbcType=TIMESTAMP},
            </if>
            <if test="updateTime != null">
                #{updateTime,jdbcType=TIMESTAMP},
            </if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.example.demo.quartz.entity.TaskInfo">
        update sc_task_info
        <set>
            <if test="cron != null">
                cron = #{cron,jdbcType=VARCHAR},
            </if>
            <if test="jobName != null">
                job_name = #{jobName,jdbcType=VARCHAR},
            </if>
            <if test="status != null">
                status = #{status,jdbcType=CHAR},
            </if>
            <if test="createTime != null">
                create_time = #{createTime,jdbcType=TIMESTAMP},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime,jdbcType=TIMESTAMP},
            </if>
        </set>
        where id = #{id,jdbcType=INTEGER}
    </update>
    <update id="updateByPrimaryKey" parameterType="com.example.demo.quartz.entity.TaskInfo">
    update sc_task_info
    set cron = #{cron,jdbcType=VARCHAR},
      job_name = #{jobName,jdbcType=VARCHAR},
      status = #{status,jdbcType=CHAR},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      update_time = #{updateTime,jdbcType=TIMESTAMP}
    where id = #{id,jdbcType=INTEGER}
  </update>

    <select id="selectByJobName" resultMap="BaseResultMap"
            parameterType="java.lang.String">
          select * from sc_task_info where job_name=#{jobName}
     </select>

    <select id="selectAll" resultMap="BaseResultMap">
          select * from sc_task_info
     </select>

    <select id="selectTaskInfos" resultMap="BaseResultMap"
            parameterType="com.example.demo.quartz.vo.TaskInfoReq">
        select * from sc_task_info
        <where>
            <if test="searchKey != null and searchKey != ''">job_name like concat('%',concat(trim(#{searchKey}),'%'))
            </if>
            <if test="status != null and status != ''">and status=#{status}</if>
        </where>
    </select>
</mapper>

3. 工具类&辅助代码

3.1 TaskInfoReq 类

代码语言:javascript复制
package com.example.demo.quartz.vo;

import lombok.Data;

/**
 * 任务请求类
 **/
@Data
public class TaskInfoReq {
    /**任务编号*/
    private Integer id;
    /**任务时间表达式*/
    private String cron;
    /**任务状态*/
    private String status;
    /**任务名称*/
    private String jobName;
    /**每页显示条数*/
    private int pageSize=10;
    /**当前页数*/
    private int pageCurrent=1;
}

3.2 Result 类定义

代码语言:javascript复制
package com.example.demo.quartz.common;
import lombok.Data;

@Data
public class Result {
    private int code;
    private String msg;
    private Object retData;
}

3.3 响应工具类封装

代码语言:javascript复制
package com.example.demo.quartz.common;

/**
 * 响应工具类
 */
public class ResponseFactory {

    private static Result commonBuild(int code, String errmsg) {
        Result result = new Result();
        result.setCode(code);
        if (errmsg == null || errmsg.trim().length() == 0) {
            result.setMsg(CodeMsg.getMsg(code));
        } else {
            result.setMsg(errmsg);
        }
        return result;
    }
    
    public static Result build(int code) {
        return commonBuild(code, CodeMsg.getMsg(code));
    }
    
    public static Result build() {
        return commonBuild(CodeMsg.SUCCESS, null);
    }
    
    public static Result build(Object data) {
        Result json = commonBuild(CodeMsg.SUCCESS, null);
        json.setRetData(data);
        return json;
    }
}

3.4 任务状态枚举类

代码语言:javascript复制
package com.example.demo.quartz.common;

public enum EnumTaskEnable {

    START("2", "开启"),
    STOP("0", "关闭");

    private String code;

    private String msg;

    EnumTaskEnable(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }
}

3.5 公共返回码

代码语言:javascript复制
package com.example.demo.quartz.common;
import java.util.HashMap;
import java.util.Map;

/**
 * 公共返回码
 */
public class CodeMsg {

    private static final Map<Integer, String> MSG = new HashMap<Integer, String>();

    //系统
    public static final int SUCCESS = 200;
    public static final int ERROR = 500;

    //任务
    public static final int TASK_NOT_EXITES = 100001;
    public static final int TASK_EXCEPTION = 100002;
    public static final int TASK_CRON_ERROR = 100003;
    public static final int TASK_CRON_DOUBLE = 100004;

    static {
        //系统
        MSG.put(SUCCESS, "请求成功.");
        MSG.put(ERROR, "服务器异常.");

        //任务
        MSG.put(TASK_NOT_EXITES, "定时任务不存在");
        MSG.put(TASK_EXCEPTION, "设置定时任务失败");
        MSG.put(TASK_CRON_ERROR, "表达式有误");
        MSG.put(TASK_CRON_DOUBLE, "定时任务已经存在");
    }

    public static String getMsg(int errcode) {
        return MSG.get(errcode);
    }
}

3.6 SpringContextUtils 工具类

代码语言:javascript复制
package com.example.demo.quartz.utils;

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

@Component
@Lazy(false)
public class SpringContextUtils implements ApplicationContextAware {


    // Spring应用上下文环境
    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     *
     * @param applicationContext
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtils.applicationContext = applicationContext;
    }

    /**
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 获取对象
     * 这里重写了bean方法,起主要作用
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     */
    public static <T> T getBean(String name) {
        try {
            return (T) applicationContext.getBean(name);
        } catch (Exception e) {
            return null;
        }
    }

    public static <T> T getBean(Class<T> clazz) {
        try {
            return applicationContext.getBean(clazz);
        } catch (Exception e) {
            return null;
        }
    }
}

3.7 配置文件

3.7.1 application.properties

代码语言:javascript复制
server.port=${random.int[10000,19999]}

#server.port=15158

## 将 Quartz 持久化方式修改为 jdbc
spring.quartz.job-store-type=jdbc
## 实例名称(默认为quartzScheduler)
spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler
## 实例节点 ID 自动生成
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
## 修改存储内容使用的类
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
## 数据源信息
spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.driver=com.mysql.cj.jdbc.Driver
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.URL=jdbc:mysql://127.0.0.1:3306/quartz_jobs?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.user=root
spring.quartz.properties.org.quartz.dataSource.quartz_jobs.password=123456

## 开启集群,多个 Quartz 实例使用同一组数据库表
spring.quartz.properties.org.quartz.jobStore.isClustered=true

# MySQL 链接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/quartz_jobs?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

## MyBatis 的配置
# Mapper资源文件存放的路径
mybatis.mapper-locations=classpath*:mapper/*.xml
# Dao 接口文件存放的目录
mybatis.type-aliases-package=com.example.demo.quartz.dao
# 开启 debug,输出 SQL
logging.level.com.example.demo.dao=debug

#pagehelper propertis文件分页插件配置
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params.count=countSql

备注:考虑到部署成本问题,若是单机、内存方式存储任务信息,则只可把 Quartz 相关配置通通去掉。

3.7.2 pom.xml

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo_job</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo_job</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

4. 任务代码

定义要执行的业务逻辑任务 DongAoJob 类,这块也就是研发人员重点关注的,后续只需实现 Job 业务就行。

代码语言:javascript复制
package com.example.demo.quartz.task;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

/**
 * 定义一个调度器要执行的任务
 */
@Component
public class DongAoJob extends QuartzJobBean {

    private static final Log logger = LogFactory.getLog(DongAoJob.class);

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("幼年是盼盼,青年是晶晶,中年是冰墩墩,生活见好逐渐发福");
    }
}

5. 程序入口

代码语言:javascript复制
package com.example.demo.quartz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

6. 运行验证

其实挂个简单页面就能轻松完成页面化配置任务,本次用 Postman 方式直接调用任务管理的 API。

6.1 添加任务

此时库任务 Id 为7:

控制台:

6.2 查询任务

6.3 编辑任务

控制台输出:

6.4 暂停任务

代码语言:javascript复制
http://localhost:15158/task/pause?taskId=7

6.5 恢复任务

代码语言:javascript复制
http://localhost:15158/task/resume?taskId=7

7. 例行回顾

本文是 Spring Boot 项目集成 Quartz 来实现任务的动态管理,主要是代码,感兴趣的可以自行拿去验证改造并用于实践。

玩转 Spring Boot 集成定时任务篇就写到这里,希望大家能够喜欢。

历史系列文章:

玩转 Spring Boot 入门篇 玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP) 玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持) 玩转 Spring Boot 集成篇(Redis) 玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin) 玩转 Spring Boot 集成篇(RabbitMQ) 玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)

0 人点赞