springboot+quartz配置实现动态定时任务

一、背景

项目中需要用到定时任务模块,记录springboot使用quartz实现动态定时任务,根据数据库的数据来自定义定时任务,主要记录配置代码实现,本机测试是可以实现动态定时任务的。

二、QuartzTask类-定时任务对象

package com.unnet.yjs.entity;

import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.FieldFill;
import com.baomidou.mybatisplus.enums.FieldStrategy;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:30
 *
 *
 **/

@Setter
@Getter
@ToString
@TableName("quartz_task")
@ApiModel
public class QuartzTask extends Model<QuartzTaskLog> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 实体编号(唯一标识)
     */

    protected Long id;
    /**
     * 任务调度参数key
     */
    @TableField(exist = false)
    public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
    /**
     * 任务名称
     */
    @ApiModelProperty(example = "Quartz-Hello")
    private String name;
    /**
     * 任务表达式
     */
    @ApiModelProperty(example = "3 * * * * ? ")
    private String cron;
    /**
     * 执行的类
     */
    @TableField("target_bean")
    @ApiModelProperty(example = "helloTask")
    private String targetBean;
    /**
     * 执行方法
     */
    @TableField("trget_method")
    @ApiModelProperty(example = "executeMethod")
    private String trgetMethod;
    /**
     * 执行参数
     */
    @ApiModelProperty(example = "Hello-Word!")
    private String params;
    /**
     * 任务类型-
     */
    @TableField("quartz_type")
    @ApiModelProperty(example = "API_GROUPS")
    private String quartzType;
    /**
     * 任务状态 0:正常  1:暂停
     */
    @ApiModelProperty(example = "0")
    private Integer status;

    /**
     *  创建者
     */
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    @ApiModelProperty(hidden = true)
    protected Long createId;

    /**
     * 创建日期
     */
    @TableField(value = "create_date", fill = FieldFill.INSERT)
    @ApiModelProperty(hidden = true)
    protected Date createDate;

    /**
     * 更新者
     */
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(hidden = true)
    protected Long updateId;

    /**
     * 更新日期
     */
    @TableField(value = "update_date", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(hidden = true)
    protected Date updateDate;

    /**
     * 删除标记(Y:正常;N:删除;A:审核;)
     */
    @TableField(value = "del_flag")
    @ApiModelProperty(example = "0",hidden = true)
    protected Boolean delFlag = false;

    /**
     * 备注
     */
    @TableField(strategy= FieldStrategy.IGNORED)
    @ApiModelProperty(hidden = true)
    protected String remarks = "remarks";

    /**
     * 主键值
     */
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

四、QuartzTaskLog类-定时任务日志对象

package com.unnet.yjs.entity;

import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.FieldFill;
import com.baomidou.mybatisplus.enums.FieldStrategy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:30
 *
 *
 **/
@Setter
@Getter
@ToString
@TableName("quartz_task_log")
public class QuartzTaskLog extends Model<QuartzTaskLog> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 实体编号(唯一标识)
     */

    protected Long id;
    /**
     * 任务ID
     */
    @TableField("job_id")
    private Long jobId;
    /**
     * 定时任务名称
     */
    private String name;
    /**
     * 定制任务执行类
     */
    @TableField("target_bean")
    private String targetBean;
    /**
     * 定时任务执行方法
     */
    @TableField("trget_method")
    private String trgetMethod;
    /**
     * 执行参数
     */
    private String params;
    /**
     * 任务状态
     */
    private Integer status;
    /**
     * 异常消息
     */
    private String error;
    /**
     * 执行时间
     */
    private Integer times;

    /**
     *  创建者
     */
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    @ApiModelProperty(hidden = true)
    protected Long createId;

    /**
     * 创建日期
     */
    @TableField(value = "create_date", fill = FieldFill.INSERT)
    @ApiModelProperty(hidden = true)
    protected Date createDate;

    /**
     * 更新者
     */
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(hidden = true)
    protected Long updateId;

    /**
     * 更新日期
     */
    @TableField(value = "update_date", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(hidden = true)
    protected Date updateDate;

    /**
     * 删除标记(Y:正常;N:删除;A:审核;)
     */
    @TableField(value = "del_flag")
    @ApiModelProperty(example = "0",hidden = true)
    protected Boolean delFlag;

    /**
     * 备注
     */
    @TableField(strategy= FieldStrategy.IGNORED)
    @ApiModelProperty(hidden = true)
    protected String remarks;

    /**
     * 主键值
     */
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

五、QuartzTaskService和QuartzTaskServiceImpl类-定时任务Service

package com.unnet.yjs.service;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.IService;
import com.unnet.yjs.entity.QuartzTask;

import java.util.List;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:30
 * 定时任务 服务类
 *
 **/
public interface QuartzTaskService extends IService<QuartzTask> {

    /**
     * 根据ID,查询定时任务
     */
    QuartzTask queryObject(Long jobId);

    /**
     * 分页查询定时任务列表
     */
    Page<QuartzTask> queryList(EntityWrapper<QuartzTask> wrapper, Page<QuartzTask> page);

    /**
     * 保存定时任务
     */
    void saveQuartzTask(QuartzTask quartzTask);

    /**
     * 更新定时任务
     */
    void updateQuartzTask(QuartzTask quartzTask);

    /**
     * 批量删除定时任务
     */
    void deleteBatchTasks(List<Long> ids);

    /**
     * 批量更新定时任务状态
     */
    int updateBatchTasksByStatus(List<Long> ids, Integer status);

    /**
     * 立即执行
     */
    void run(List<Long> jobIds);

    /**
     * 暂停运行
     */
    void paush(List<Long> jobIds);

    /**
     * 恢复运行
     */
    void resume(List<Long> jobIds);
}
package com.unnet.yjs.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.unnet.yjs.controller.api.RootController;
import com.unnet.yjs.dao.QuartzTaskDao;
import com.unnet.yjs.entity.QuartzTask;
import com.unnet.yjs.service.QuartzTaskService;
import com.unnet.yjs.util.Constants;
import com.unnet.yjs.util.quartz.ScheduleUtils;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:31
 *
 * 定时任务 服务实现类
 * @author wangl
 * @since 2018-01-24
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class QuartzTaskServiceImpl extends ServiceImpl<QuartzTaskDao, QuartzTask> implements QuartzTaskService {
    private static final Logger LOGGER = LoggerFactory.getLogger(QuartzTaskService.class);

    @Resource
    private Scheduler scheduler;

    /**
     * 项目启动时,初始化定时器
     */
    @PostConstruct
    public void init() {
        EntityWrapper<QuartzTask> wrapper = new EntityWrapper<>();
        wrapper.eq("del_flag", false);
        wrapper.eq("quartz_type", Constants.QUARTZ_API_GROUPS);
        List<QuartzTask> scheduleJobList = selectList(wrapper);
        for (QuartzTask scheduleJob : scheduleJobList) {
            CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getId());
            //如果不存在,则创建
            if (cronTrigger == null) {
                LOGGER.info("Quartz创建定时任务成功,任务:" + scheduleJob);
                ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
            } else {
                LOGGER.info("Quartz更新定时任务成功,任务:" + scheduleJob);
                ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
            }
        }
    }

    @Override
    public QuartzTask queryObject(Long jobId) {
        return baseMapper.selectById(jobId);
    }

    @Override
    public Page<QuartzTask> queryList(EntityWrapper<QuartzTask> wrapper, Page<QuartzTask> page) {
        return selectPage(page, wrapper);
    }

    @Override
    public void saveQuartzTask(QuartzTask quartzTask) {
        baseMapper.insert(quartzTask);
        LOGGER.info("Quartz创建定时任务成功,任务:" + quartzTask);
        ScheduleUtils.createScheduleJob(scheduler, quartzTask);
    }

    @Override
    public void updateQuartzTask(QuartzTask quartzTask) {
        baseMapper.updateById(quartzTask);
        LOGGER.info("Quartz更新定时任务成功,任务:" + quartzTask);
        ScheduleUtils.updateScheduleJob(scheduler, quartzTask);
    }

    @Override
    public void deleteBatchTasks(List<Long> ids) {
        for (Long id : ids) {
            LOGGER.info("Quartz批量删除定时任务成功,任务ID:" + id);
            ScheduleUtils.deleteScheduleJob(scheduler, id);
        }
        deleteBatchIds(ids);
    }

    @Override
    public int updateBatchTasksByStatus(List<Long> ids, Integer status) {
        List<QuartzTask> list = selectBatchIds(ids);
        for (QuartzTask task : list) {
            task.setStatus(status);
        }
        updateBatchById(list);
        return 0;
    }

    @Override
    public void run(List<Long> jobIds) {
        for (Long jobId : jobIds) {
            LOGGER.info("Quartz批量立即运行定时任务成功,任务ID:" + jobId);
            ScheduleUtils.run(scheduler, queryObject(jobId));
        }
    }

    @Override
    public void paush(List<Long> jobIds) {
        for (Long jobId : jobIds) {
            LOGGER.info("Quartz批量暂停运行定时任务成功,任务ID:" + jobId);
            ScheduleUtils.pauseJob(scheduler, jobId);
        }
        updateBatchTasksByStatus(jobIds, Constants.QUARTZ_STATUS_PUSH);
    }

    @Override
    public void resume(List<Long> jobIds) {
        for (Long jobId : jobIds) {
            LOGGER.info("Quartz批量恢复运行定时任务成功,任务ID:" + jobId);
            ScheduleUtils.resumeJob(scheduler, jobId);
        }

        updateBatchTasksByStatus(jobIds, Constants.QUARTZ_STATUS_NOMAL);
    }
}

六、QuartzTaskLogService和QuartzTaskLogServiceImpl类-定时任务日志Service

package com.unnet.yjs.service;

import com.baomidou.mybatisplus.service.IService;
import com.unnet.yjs.entity.QuartzTaskLog;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:30
 * 任务执行日志 服务类
 *
 **/

public interface QuartzTaskLogService extends IService<QuartzTaskLog> {
}
package com.unnet.yjs.service.impl;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.unnet.yjs.dao.QuartzTaskLogDao;
import com.unnet.yjs.entity.QuartzTaskLog;
import com.unnet.yjs.service.QuartzTaskLogService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:31
 * 任务执行日志 服务实现类
 *
 **/

@Service
@Transactional(rollbackFor = Exception.class)
public class QuartzTaskLogServiceImpl extends ServiceImpl<QuartzTaskLogDao, QuartzTaskLog> implements QuartzTaskLogService {

}

七、QuartzTaskController类-定时任务Controller

package com.unnet.yjs.controller.api.v1;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.unnet.yjs.annotation.HttpMethod;
import com.unnet.yjs.entity.QuartzTask;
import com.unnet.yjs.service.QuartzTaskService;
import com.unnet.yjs.util.Constants;
import com.unnet.yjs.util.LayerData;
import com.unnet.yjs.util.RestResponse;
import io.swagger.annotations.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import java.util.List;
import java.util.Map;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:39
 *
 * 定时任务  前端控制器
 *
 * @author wangl
 * @since 2018-01-24
 */
@RestController
@Api(tags = "QuartzTaskController",description = "定时任务模块API")
@RequestMapping(Constants.V1_API_PREFIX+"/admin/quartzTask")
public class QuartzTaskController {
    private static final Logger LOGGER = LoggerFactory.getLogger(QuartzTaskController.class);

    @Resource
    private QuartzTaskService quartzTaskService;

    @PostMapping("list")
    @ApiOperation(value = "定时任务列表",httpMethod = com.unnet.yjs.annotation.HttpMethod.GET)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "p_page", value = "页数", defaultValue = "1", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "p_limit", value = "每页条数", defaultValue = "10", dataType = "Integer", paramType = "query")
    })
    public LayerData<QuartzTask> list(@RequestParam(value = "p_page",defaultValue = "1")Integer page,
                                      @RequestParam(value = "p_page",defaultValue = "10")Integer limit,
                                      ServletRequest request){
        Map map = WebUtils.getParametersStartingWith(request, "s_");
        LayerData<QuartzTask> layerData = new LayerData<>();
        EntityWrapper<QuartzTask> wrapper = new EntityWrapper<>();
        wrapper.eq("del_flag",false);
        if(!map.isEmpty()){
            String name = (String) map.get("name");
            if(StringUtils.isNotBlank(name)) {
                wrapper.like("name",name);
            }else{
                map.remove("name");
            }

            String status = (String) map.get("status");
            if(StringUtils.isNotBlank(status)) {
                wrapper.eq("status",status);
            }else{
                map.remove("status");
            }

        }
        Page<QuartzTask> pageData = quartzTaskService.queryList(wrapper,new Page<>(page,limit));
        layerData.setData(pageData.getRecords());
        layerData.setCount(pageData.getTotal());
        return layerData;
    }


    @PostMapping("add")
    @ResponseBody
    @ApiOperation(value = "新增定时任务",httpMethod = HttpMethod.POST)
    public RestResponse add(@RequestBody @ApiParam QuartzTask quartzTask){
        quartzTaskService.saveQuartzTask(quartzTask);
        return RestResponse.success();
    }


    @PostMapping("edit")
    @ApiOperation(value = "更新定时任务",httpMethod = HttpMethod.POST)
    public RestResponse edit(@RequestBody @ApiParam QuartzTask quartzTask){
        if(null == quartzTask.getId() || 0 == quartzTask.getId()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskService.updateQuartzTask(quartzTask);
        return RestResponse.success();
    }

    @PostMapping("delete")
    @ApiOperation(value = "删除定时任务",httpMethod = HttpMethod.POST)
    public RestResponse delete(@RequestParam(value = "ids[]",required = false)List<Long> ids){
        if(null == ids || 0 == ids.size()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskService.deleteBatchTasks(ids);
        return RestResponse.success();
    }

    /**
     * 暂停选中的定时任务
     * @param ids 任务ID List
     */
    @PostMapping("paush")
    @ApiOperation(value = "暂停定时任务",httpMethod = HttpMethod.POST)
    public RestResponse paush(@RequestParam(value = "ids[]",required = false)List<Long> ids){
        if(null == ids || 0 == ids.size()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskService.paush(ids);
        return RestResponse.success();
    }

    /**
     * 恢复选中的定时任务运行
     * @param ids 任务ID List
     */
    @PostMapping("resume")
    @ApiOperation(value = "恢复定时任务",httpMethod = HttpMethod.POST)
    public RestResponse resume(@RequestParam(value = "ids[]",required = false)List<Long> ids){
        if(null == ids || 0 == ids.size()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskService.resume(ids);
        return RestResponse.success();
    }

    /**
     * 立即执行选中的定时任务
     * @param ids 任务ID List
     */
    @PostMapping("run")
    @ApiOperation(value = "运行定时任务",httpMethod = HttpMethod.POST)
    public RestResponse run(@RequestParam(value = "ids[]",required = false)List<Long> ids){
        if(null == ids || 0 == ids.size()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskService.run(ids);
        return RestResponse.success();
    }

}

八、QuartzTaskLogController类-定时任务日志Controller

package com.unnet.yjs.controller.api.v1;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.unnet.yjs.entity.QuartzTaskLog;
import com.unnet.yjs.service.QuartzTaskLogService;
import com.unnet.yjs.util.Constants;
import com.unnet.yjs.util.LayerData;
import com.unnet.yjs.util.RestResponse;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.WebUtils;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import java.util.Map;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:39
 *
 *
 * 任务执行日志  前端控制器
 * @author wangl
 * @since 2018-01-25
 */
@Controller
@Api(tags = "QuartzTaskLogController",description = "定时任务日志API")
@RequestMapping(Constants.V1_API_PREFIX+"/admin/quartzTaskLog")
public class QuartzTaskLogController {
    private static final Logger LOGGER = LoggerFactory.getLogger(QuartzTaskLogController.class);

    @Resource
    private QuartzTaskLogService quartzTaskLogService;

    @ResponseBody
    public LayerData<QuartzTaskLog> list(@RequestParam(value = "page",defaultValue = "1")Integer page,
                                         @RequestParam(value = "limit",defaultValue = "10")Integer limit,
                                         ServletRequest request){
        Map map = WebUtils.getParametersStartingWith(request, "s_");
        LayerData<QuartzTaskLog> layerData = new LayerData<>();
        EntityWrapper<QuartzTaskLog> wrapper = new EntityWrapper<>();
        wrapper.eq("del_flag",false);
        if(!map.isEmpty()){
            String name = (String) map.get("name");
            if(StringUtils.isNotBlank(name)) {
                wrapper.like("name",name);
            }else{
                map.remove("name");
            }

        }
        Page<QuartzTaskLog> pageData = quartzTaskLogService.selectPage(new Page<>(page,limit),wrapper);
        layerData.setData(pageData.getRecords());
        layerData.setCount(pageData.getTotal());
        return layerData;
    }

    @PostMapping("add")
    @ResponseBody
    public RestResponse add(QuartzTaskLog quartzTaskLog){
        quartzTaskLogService.insert(quartzTaskLog);
        return RestResponse.success();
    }


    @PostMapping("edit")
    @ResponseBody
    public RestResponse edit(QuartzTaskLog quartzTaskLog){
        if(null == quartzTaskLog.getId() || 0 == quartzTaskLog.getId()){
            return RestResponse.failure("ID不能为空");
        }
        quartzTaskLogService.updateById(quartzTaskLog);
        return RestResponse.success();
    }

    @PostMapping("delete")
    @ResponseBody
    public RestResponse delete(@RequestParam(value = "id",required = false)Long id){
        if(null == id || 0 == id){
            return RestResponse.failure("ID不能为空");
        }
        QuartzTaskLog quartzTaskLog = quartzTaskLogService.selectById(id);
        quartzTaskLog.setDelFlag(true);
        quartzTaskLogService.updateById(quartzTaskLog);
        return RestResponse.success();
    }

}

九、Quartz相关类

package com.unnet.yjs.util.quartz;

import com.unnet.yjs.entity.QuartzTask;
import com.unnet.yjs.entity.QuartzTaskLog;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:35
 *
 * 类ScheduleJob的功能描述:
 * 定时任务
 * @date 2017-08-25 11:48:34
 */
public class ScheduleJob extends QuartzJobBean {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private final ThreadLocal<ExecutorService> service = ThreadLocal.withInitial(Executors::newSingleThreadExecutor);

    @Override
    protected void executeInternal(JobExecutionContext context) {
        QuartzTask scheduleJob = (QuartzTask) context.getMergedJobDataMap().get(QuartzTask.JOB_PARAM_KEY);
        String param = scheduleJob.getParams();
        //数据库保存执行记录
        QuartzTaskLog log = new QuartzTaskLog();
        log.setJobId(scheduleJob.getId());
        log.setTargetBean(scheduleJob.getTargetBean());
        log.setTrgetMethod(scheduleJob.getTrgetMethod());
        log.setParams(param);
        log.setName("执行定时任务【"+scheduleJob.getName()+"】");
        if(StringUtils.isNotBlank(param) && StringUtils.isNumeric(param)){
            log.setCreateId(Long.valueOf(param));
            log.setUpdateId(Long.valueOf(param));
        }else{
            //定义死
            log.setCreateId(1L);
            log.setUpdateId(1L);
        }
        log.setCreateDate(new Date());
        //任务开始时间
        long startTime = System.currentTimeMillis();
        
        try {
            //执行任务
            logger.info("任务准备执行,任务ID:" + scheduleJob.getId());
            ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getTargetBean(),
                    scheduleJob.getTrgetMethod(), scheduleJob.getParams());
            Future<?> future = service.get().submit(task);
            
            future.get();
            
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            log.setTimes((int)times);
            //任务状态    0:成功    1:失败
            log.setStatus(0);
            
            logger.info("任务执行完毕,任务ID:" + scheduleJob.getId() + "  总共耗时:" + times + "毫秒");
        } catch (Exception e) {
            logger.error("任务执行失败,任务ID:" + scheduleJob.getId(), e);
            
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            log.setTimes((int)times);

            //任务状态    0:成功    1:失败
            log.setStatus(1);
            log.setError(e.getMessage());
        }finally {
            log.insert();
        }
    }
}
package com.unnet.yjs.util.quartz;

import com.unnet.yjs.exception.MyException;
import com.unnet.yjs.util.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:35
 *
 * 类ScheduleRunnable的功能描述:
 * 执行定时任务
 * @date 2017-08-25 16:18:02
 */
public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;
    
    ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {

        this.target = SpringUtil.getBean(beanName);
        this.params = params;
        
        if(StringUtils.isNotBlank(params)){
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        }else{
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(method);
            if(StringUtils.isNotBlank(params)){
                method.invoke(target, params);
            }else{
                method.invoke(target);
            }
        }catch (Exception e) {
            throw new MyException("执行定时任务失败", e);
        }
    }

}
package com.unnet.yjs.util.quartz.task;

import com.xiaoleilu.hutool.date.DateUtil;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:49
 *
 *
 **/
@Component("helloTask")
public class HelloTask {

    public void executeMethod(String params){
        System.out.println(DateUtil.format(new Date(),"yyy-MM-dd hh:mm:ss") + "->hello." + params);
    }
}
package com.unnet.yjs.util.quartz;

import com.unnet.yjs.entity.QuartzTask;
import com.unnet.yjs.exception.MyException;
import com.unnet.yjs.util.Constants;
import org.quartz.*;


/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 * @author lyg   19-7-11 下午10:35
 *
 * 类ScheduleUtils的功能描述:
 * 定时任务工具类
 * @author Administrator
 * @date 2017-08-25 16:18:10
 */
public class ScheduleUtils {
    private final static String JOB_NAME = "TASK_API_GROUPS_";
    private final static String TRIGGER_NAME = "TRIGGER_API_GROUPS_";

    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(Long jobId) {
        return TriggerKey.triggerKey(TRIGGER_NAME + jobId);
    }
    
    /**
     * 获取jobKey
     */
    public static JobKey getJobKey(Long jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }

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

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

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

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

            //放入参数,运行时的方法可以获取
            jobDetail.getJobDataMap().put(QuartzTask.JOB_PARAM_KEY, scheduleJob);
            
            scheduler.scheduleJob(jobDetail, trigger);
            
            //暂停任务
            if(scheduleJob.getStatus().intValue() == Constants.QUARTZ_STATUS_PUSH.intValue()){
                pauseJob(scheduler, scheduleJob.getId());
            }
        } catch (SchedulerException e) {
            throw new MyException("创建定时任务失败", e);
        }
    }
    
    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, QuartzTask scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getId());

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

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

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

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

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

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

ScheduleUtils封装了Quartz的定时任务相关操作;ReflectionUtils封装了反射的相关操作;ScheduleRunnable类实现了Runable接口,重写run方法,使用反射来调用定时任务类的方法;ScheduleJob类重写执行方法,实现定时任务的自定义任务逻辑,定时任务会按间隔执行这个方法;HelloTask类具体的定时任务类和具体的执行方法;

十、Springboot配置Quartz的配置类

package com.unnet.yjs.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;


/**
 *  Email: love1208tt@foxmail.com
 *  Copyright (c) 2018. missbe
 *  @author lyg  19-5-21 下午9:43
 *
 **/

@Configuration
public class QuartzConfig {

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driver-class-name}")
    private String driver;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.druid.initialSize}")
    private String initialSize;

    @Value("${spring.datasource.druid.maxActive}")
    private String maxActive;

    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setMaxActive(Integer.valueOf(maxActive));
        /*Druid日志记录*/
        druidDataSource.setFilters("stat,wall,log4j");
        druidDataSource.setInitialSize(Integer.valueOf(initialSize));
        return druidDataSource;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws SQLException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setDataSource(dataSource());
        //quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "OneClickApiGroupsScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        //线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "25");
        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", "20000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");

        prop.put("org.quartz.jobStore.misfireThreshold", "60000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        schedulerFactoryBean.setQuartzProperties(prop);

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

        return schedulerFactoryBean;
    }
}

数据源可以自己配置,属性名可以修改; schedulerFactoryBean.setAutoStartup(true);这个方法要高设为true,否则定时任务不会启动;

十一、测试

  • 新增定时任务


    image.png
  • 定时任务执行日志
2019-07-14 15:00:25.781  INFO 25724 --- [nio-9080-exec-6] com.unnet.yjs.service.QuartzTaskService  : Quartz创建定时任务成功,任务:QuartzTask(id=9, name=Quartz-Hello, cron=3 * * * * ? , targetBean=helloTask, trgetMethod=executeMethod, params=Hello-Word!, quartzType=API_GROUPS, status=0, createId=1563087625757, createDate=Sun Jul 14 15:00:25 CST 2019, updateId=1563087625757, updateDate=Sun Jul 14 15:00:25 CST 2019, delFlag=false, remarks=remarks)
 Time:20 ms - ID:com.unnet.yjs.dao.QuartzTaskDao.insert
 Execute SQL:
    INSERT 
    INTO
        quartz_task
        ( `name`,  cron,  target_bean,  trget_method,  params,  quartz_type,  `status`,create_by,create_date,update_by,update_date,  del_flag,remarks )  
    VALUES
        ( 'Quartz-Hello',  '3 * * * * ? ',  'helloTask',  'executeMethod',  'Hello-Word!',  'API_GROUPS',  0,1563087625757,'2019-07-14 15:00:25.755',1563087625757,'2019-07-14 15:00:25.757',  0,'remarks' )

2019-07-14 15:01:03.030  INFO 25724 --- [eduler_Worker-1] com.unnet.yjs.util.quartz.ScheduleJob    : 任务准备执行,任务ID:9
2019-07-14 03:01:03->hello.Hello-Word!
2019-07-14 15:01:03.053  INFO 25724 --- [eduler_Worker-1] com.unnet.yjs.util.quartz.ScheduleJob    : 任务执行完毕,任务ID:9  总共耗时:23毫秒
2019-07-14 15:01:03.056  INFO 25724 --- [eduler_Worker-1] c.unnet.yjs.config.SysMetaObjectHandler  : 正在调用该insert填充字段方法
 Time:12 ms - ID:com.unnet.yjs.dao.QuartzTaskLogDao.insert
 Execute SQL:
    INSERT 
    INTO
        quartz_task_log
        ( job_id,  `name`,  target_bean,  trget_method,  params,  `status`,    times,create_by,create_date,update_by,update_date,  remarks )  
    VALUES
        ( 9,  '执行定时任务【Quartz-Hello】',  'helloTask',  'executeMethod',  'Hello-Word!',  0,    23,1,'2019-07-14 15:01:03.03',1,'2019-07-14 15:01:03.056',  null )
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容