springboot基于quartz实现自定义时间的定时调度

一、Quartz引言

        Quartz是一套轻量级的任务调度框架,只需要定义了 Job(任务),Trigger(触发器)和 Scheduler(调度器),即可实现一个定时调度能力。支持基于数据库的集群模式,可以做到任务幂等执行。

        由于项目需要一个在web页面可以操作的任务调度管理,springboot的调度注解@Scheduled需要配置值,无法运行时更改。所以他们都无法在运行时通过代码或者其他自定义的方式实现动态调度。因此我选择使用Quartz来进行开发

二、依赖引入

<!--quartz定时任务-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

三、创建定时任务的表结构

CREATE TABLE `scheduled_job` (
`id`  bigint NOT NULL AUTO_INCREMENT COMMENT '主键' ,
`bean_name`  varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'bean名称' ,
`method_name`  varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法名称' ,
`method_params`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法参数' ,
`cron_expression`  varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'cron表达式' ,
`job_status`  int NULL DEFAULT NULL COMMENT '状态(1正常 0暂停)' ,
`remark`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注' ,
`create_time`  timestamp NULL DEFAULT NULL COMMENT '创建时间' ,
`update_time`  timestamp NULL DEFAULT NULL COMMENT '更新时间' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
COMMENT='定时任务管理表'
ROW_FORMAT=DYNAMIC
;

四、实体类

@TableName("schedule_job")
@Data
public class ScheduleJob implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 任务id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * spring bean名称
     */
    @TableField("name")
    private String name;

    /**
     * 方法名
     */
    @TableField("method_name")
    private String methodName;

    /**
     * cron表达式
     */
    @TableField("cron")
    private String cron;

    /**
     * 任务状态 0:正常 1:暂停 -1:删除
     */
    @TableField("status")
    private Integer status;

    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 参数
     */
    @TableField("params")
    private String params;


    
}

五、接口定义

public interface ScheduleJobService extends IService<ScheduleJob> {

    /**
     * 保存定时任务
     *
     */
    void add(ScheduleJobParam param);

    /**
     * 更新定时任务
     *
     */
    void delete(ScheduleJobParam param);

    /**
     * 更新
     *
     */
    void update(ScheduleJobParam param);

    /**
     * 查询单条数据,Specification模式
     *
     */
    ScheduleJobResult findBySpec(ScheduleJobParam param);

    /**
     * 查询列表,Specification模式
     *
     */
    List<ScheduleJobResult> findListBySpec(ScheduleJobParam param);

    /**
     * 查询分页数据,Specification模式
     *
     */
     LayuiPageInfo findPageBySpec(ScheduleJobParam param);


    /**
     * 启用定时器
     * @param jobIds
     */
    void run(List<Long> jobIds);


    /**
     * 暂停定时器
     * @param jobIds
     */
    void pause(List<Long> jobIds);

    /**
     * 恢复定时器
     * @param jobIds
     */
    void resume(List<Long> jobIds);


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

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

}

六、接口实现

@Service
public class ScheduleJobServiceImpl extends ServiceImpl<ScheduleJobMapper, ScheduleJob> implements ScheduleJobService {

    @Autowired
    private Scheduler scheduler;

    /**
     * 项目启动时,初始化定时器
     */
    @PostConstruct
    public void init(){
        List<ScheduleJob> scheduleJobList = this.baseMapper.selectList(Wrappers.<ScheduleJob>lambdaQuery().ne(ScheduleJob::getStatus,ConstCodes.DEL));
        for(ScheduleJob scheduleJob : scheduleJobList){
            CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getId());
            //如果不存在,则创建
            if(cronTrigger == null) {
                ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
            }else {
                ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
            }
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(ScheduleJobParam param){
        ScheduleJob entity = getEntity(param);
        this.save(entity);

        ScheduleUtils.createScheduleJob(scheduler, entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(ScheduleJobParam param){
        ScheduleUtils.deleteScheduleJob(scheduler,param.getId());

        this.removeById(getKey(param));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(ScheduleJobParam param){
        ScheduleJob oldEntity = getOldEntity(param);
        ScheduleJob newEntity = getEntity(param);
        ToolUtil.copyProperties(newEntity, oldEntity);
        this.updateById(newEntity);

        ScheduleUtils.updateScheduleJob(scheduler, newEntity);

    }

    @Override
    public ScheduleJobResult findBySpec(ScheduleJobParam param){
        return null;
    }

    @Override
    public List<ScheduleJobResult> findListBySpec(ScheduleJobParam param){
        return null;
    }

    @Override
    public LayuiPageInfo findPageBySpec(ScheduleJobParam param){
        Page pageContext = getPageContext();
        QueryWrapper<ScheduleJob> objectQueryWrapper = new QueryWrapper<>();
        IPage page = this.page(pageContext, objectQueryWrapper);
        return LayuiPageFactory.createPageInfo(page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void run(List<Long> jobIds) {
        for(Long id : jobIds) {
            ScheduleUtils.run(scheduler,this.baseMapper.selectById(id));
        }
        updateBatch(jobIds,ConstCodes.NORMAL);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pause(List<Long> jobIds) {
        for(Long id : jobIds) {
            ScheduleUtils.pauseJob(scheduler, id);
        }
        //更新状态
        updateBatch(jobIds,ConstCodes.PAUSE);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resume(List<Long> jobIds) {
        for(Long id : jobIds) {
            ScheduleUtils.resumeJob(scheduler, id);
        }
        //更新状态
        updateBatch(jobIds,ConstCodes.NORMAL);
    }


    /**
     * 更新状态
     */
    @Override
    public int updateBatch(List<Long>  jobIds, int status){
        return this.baseMapper.updateBatch(jobIds,status);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(List<Long> jobIds) {
        for(Long jobId : jobIds){
            ScheduleUtils.deleteScheduleJob(scheduler, jobId);
        }
        //批量删除
        this.baseMapper.deleteBatch(jobIds);
    }

    private Serializable getKey(ScheduleJobParam param){
        return param.getId();
    }

    private Page getPageContext() {
        return LayuiPageFactory.defaultPage();
    }

    private ScheduleJob getOldEntity(ScheduleJobParam param) {
        return this.getById(getKey(param));
    }

    private ScheduleJob getEntity(ScheduleJobParam param) {
        ScheduleJob entity = new ScheduleJob();
        ToolUtil.copyProperties(param, entity);
        return entity;
    }

}

七、ScheduleUtils工具类

public class ScheduleUtils {

    private final static String JOB_NAME = "TASK_";

    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(Long jobId) {
        return TriggerKey.triggerKey(JOB_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) {
            new ServiceException(BizExceptionEnum.SCHEDULE_TRIGGER_ERROR);
        }
        return null;
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJobUtils.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(ConstCodes.JOB_PARAM_KEY, scheduleJob);

            scheduler.scheduleJob(jobDetail, trigger);

            //暂停任务
            if(scheduleJob.getStatus() == ConstCodes.PAUSE){
                pauseJob(scheduler, scheduleJob.getId());
            }
        } catch (SchedulerException e) {
            throw new ServiceException(BizExceptionEnum.SCHEDULE_CREATE_ERROR);
        }
    }

    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJob 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(ConstCodes.JOB_PARAM_KEY, scheduleJob);

            scheduler.rescheduleJob(triggerKey, trigger);

            //暂停任务
            if(scheduleJob.getStatus() == ConstCodes.PAUSE){
                pauseJob(scheduler, scheduleJob.getId());
            }

        } catch (SchedulerException e) {
            throw new ServiceException(BizExceptionEnum.SCHEDULE_UPDATE_ERROR);
        }
    }

    /**
     * 立即执行任务
     */
    public static void run(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            //参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ConstCodes.JOB_PARAM_KEY, scheduleJob);

            scheduler.triggerJob(getJobKey(scheduleJob.getId()), dataMap);
        } catch (SchedulerException e) {
            System.err.println("立即执行定时任务失败");
            throw new ServiceException(BizExceptionEnum.SCHEDULE_RUN_ERROR);
        }
    }

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

    /**
     * 恢复任务
     */
    public static void resumeJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            System.err.println("恢复定时任务失败");
            throw new ServiceException(BizExceptionEnum.SCHEDULE_RESUME_ERROR);
        }
    }

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

}
ScheduleJobUtils工具类
@Slf4j
public class ScheduleJobUtils extends QuartzJobBean {

    private ExecutorService service = Executors.newSingleThreadExecutor();

    @Autowired
    private ScheduleJobLogServiceImpl scheduleJobLogService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap()
                .get(ConstCodes.JOB_PARAM_KEY);

        //数据库保存执行记录
        ScheduleJobLog jobLog = new ScheduleJobLog();
        jobLog.setJobId(scheduleJob.getId());
        jobLog.setName(scheduleJob.getName());
        jobLog.setMethodName(scheduleJob.getMethodName());
        jobLog.setParams(scheduleJob.getParams());
        jobLog.setCreateTime(new Date());

        //任务开始时间
        long startTime = System.currentTimeMillis();
        Byte zero = 0;

        Byte one=1;
        try {
            //执行任务
            log.info("任务准备执行,任务ID:" + scheduleJob.getId());
            ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getName(),
                    scheduleJob.getMethodName(), scheduleJob.getParams());
            Future<?> future = service.submit(task);
            future.get();
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes((int)times);
            //任务状态    0:成功    1:失败
            jobLog.setStatus(ConstCodes.SUCCESS);
            log.info("任务执行完毕,任务ID:" + scheduleJob.getId() + "  总共耗时:" + times + "毫秒");
        } catch (Exception e) {
            log.error("任务执行失败,任务ID:" + scheduleJob.getId(), e);
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes((int)times);
            //任务状态    0:成功    1:失败
            jobLog.setStatus(ConstCodes.FAIL);
            jobLog.setError(StringUtils.substring(e.toString(), 0, 2000));
        }finally {
            scheduleJobLogService.save(jobLog);
        }


    }
}
ScheduleRunnable实现Runnable
public class ScheduleRunnable implements Runnable{

    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
        this.target = SpringContextHolder.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 ServiceException(BizExceptionEnum.SCHEDULE_RUN_ERROR);
        }
    }

}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要在 Spring Boot 中集成 Quartz。可以通过添加以下依赖来实现: ```xml <!-- Quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> ``` 然后,在 Spring Boot 中配置 Quartz。可以创建一个 `SchedulerFactoryBean` 实例,并设置相关属性,例如线程池大小、任务调度器等等。可以在 `application.properties` 文件中设置相关属性: ```properties # Quartz Scheduler spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always spring.quartz.properties.org.quartz.scheduler.instanceName=QuartzScheduler 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.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate spring.quartz.properties.org.quartz.jobStore.useProperties=true spring.quartz.properties.org.quartz.jobStore.dataSource=myDataSource spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ spring.quartz.properties.org.quartz.dataSource.myDataSource.driverClassName=com.mysql.jdbc.Driver spring.quartz.properties.org.quartz.dataSource.myDataSource.URL=jdbc:mysql://localhost:3306/quartz spring.quartz.properties.org.quartz.dataSource.myDataSource.user=root spring.quartz.properties.org.quartz.dataSource.myDataSource.password=root spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool spring.quartz.properties.org.quartz.threadPool.threadCount=10 spring.quartz.properties.org.quartz.threadPool.threadPriority=5 spring.quartz.properties.org.quartz.jobStore.isClustered=true spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000 spring.quartz.properties.org.quartz.jobStore.maxMisfiresToHandleAtATime=1 ``` 然后,需要创建一个 `Job` 类来执行具体的任务。例如: ```java public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 执行具体的任务逻辑 System.out.println("Hello Quartz!"); } } ``` 最后,需要创建一个 `CronTriggerFactoryBean` 实例,并设置相关属性。例如: ```java @Configuration public class QuartzConfig { @Bean public JobDetailFactoryBean myJobDetail() { JobDetailFactoryBean factory = new JobDetailFactoryBean(); factory.setJobClass(MyJob.class); factory.setDurability(true); // 任务持久化 return factory; } @Bean public CronTriggerFactoryBean myCronTrigger(@Qualifier("myJobDetail") JobDetail jobDetail) { CronTriggerFactoryBean factory = new CronTriggerFactoryBean(); factory.setJobDetail(jobDetail); factory.setCronExpression("0/5 * * * * ?"); // 每隔 5 秒触发一次 return factory; } @Bean public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("myCronTrigger") CronTrigger cronTrigger) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setTriggers(cronTrigger); return factory; } } ``` 这里的 `myJobDetail` 方法返回一个 `JobDetailFactoryBean` 实例,它指定了具体的任务类。`myCronTrigger` 方法返回一个 `CronTriggerFactoryBean` 实例,它指定了触发器的具体规则。`schedulerFactoryBean` 方法返回一个 `SchedulerFactoryBean` 实例,它将任务和触发器关联起来,并返回一个 `Scheduler` 实例,可以通过它来启动和停止任务调度器。 最后,在 Vue 中实现调用后端接口来启动和停止任务调度器。可以使用 Axios 进行 HTTP 请求。例如: ```javascript import axios from 'axios' // 启动任务调度器 export function startScheduler () { return axios.post('/scheduler/start') } // 停止任务调度器 export function stopScheduler () { return axios.post('/scheduler/stop') } ``` 这里的 `/scheduler/start` 和 `/scheduler/stop` 是后端接口的 URL。可以在后端使用 Spring Boot 的 MVC 模块来实现这两个接口,例如: ```java @RestController @RequestMapping("/scheduler") public class SchedulerController { @Autowired private Scheduler scheduler; @PostMapping("/start") public void start() throws SchedulerException { scheduler.start(); } @PostMapping("/stop") public void stop() throws SchedulerException { scheduler.shutdown(); } } ``` 这里的 `scheduler` 是通过 `SchedulerFactoryBean` 创建的 `Scheduler` 实例,可以通过 `start()` 和 `shutdown()` 方法来启动和停止任务调度器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值