当前位置: 首页 > news >正文

从零搭建微服务项目Pro(第1-3章——Quartz定时任务模块整合)

前言:

在企业项目中,往往有定时任务发布的需求,比如每天晚9点将今日数据备份一次,或每月一号将上月的销售数据邮件发送给对应的工作人员。显然这些操作不可能是人工到时间点调用一次接口,需要编写专门的模块完成任务的调度。

Quartz 是一个功能强大的开源任务调度框架,广泛应用于 Java 应用程序中。它允许开发者根据时间表(如固定时间、间隔时间或 Cron 表达式)来调度任务。Quartz 提供了丰富的 API 和灵活的配置选项,适用于从简单的任务调度到复杂的分布式任务调度场景。

尽管quartz库本身提供很多方法,但在定时任务中,外界往往只能感知到任务的启动与暂停,以及原始quartz库对于每一种定时任务的实现都需要新添加一种实现接口,不方便拓展的同时程序的耦合度较高。经过前面两章的迭代已经成功开发了数据库持久化、便于维护、日志记录的定时任务模块,具体可参考下方链接,本章将介绍如何将模块整合至微服务项目中。

从零搭建微服务项目Pro(第1-2章——Quartz实现定时任务模块优化)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145933078?spm=1001.2014.3001.5501

本章最终源码链接如下:

wlf728050719/SpringCloudPro1-3https://github.com/wlf728050719/SpringCloudPro1-3以及本专栏会持续更新微服务项目,每一章的项目都会基于前一章项目进行功能的完善,欢迎小伙伴们关注!同时如果只是对单章感兴趣也不用从头看,只需下载前一章项目即可,每一章都会有前置项目准备部分,跟着操作就能实现上一章的最终效果,当然如果是一直跟着做可以直接跳过这一部分。专栏目录链接如下,其中Base篇为基础微服务搭建,Pro篇为复杂模块实现。

从零搭建微服务项目(全)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145799620


一、Quartz系统介绍

核心概念

  1. Job: 定义要执行的任务,实现 Job 接口并重写 execute 方法。

  2. JobDetail: 包含 Job 的详细信息,如名称、组名等。

  3. Trigger: 定义任务的触发条件,如开始时间、结束时间、重复频率等。

  4. Scheduler: 负责调度任务,将 JobDetail 和 Trigger 绑定并管理任务执行。

  5. JobDataMap: 任务数据映射,用于在任务执行时传递数据。

底层实现
  1. 任务存储

    Quartz 支持内存存储(RAMJobStore)和数据库存储(JDBCJobStore)。RAMJobStore 将任务存储在内存中,适合简单的应用场景;JDBCJobStore 将任务存储在数据库中,适合分布式和持久化的场景。
  2. 线程池

    Quartz 使用线程池来执行任务。默认情况下,Quartz 使用 SimpleThreadPool,开发者可以配置线程池的大小以适应不同的负载。
  3. 触发器调度

    Quartz 使用 QuartzSchedulerThread 来检查触发器并根据时间表调度任务。该线程会定期检查触发器,并根据触发器的状态决定是否执行任务。
  4. 任务执行

    当触发器触发时,Quartz 会从线程池中获取一个线程来执行任务。任务执行过程中,Quartz 会处理任务的并发性、异常处理等。

Scheduler 是 Quartz 框架的核心接口,负责管理和调度任务(Job)和触发器(Trigger)。以下是 Scheduler 接口中常用的方法及其说明:

1. 调度器生命周期管理

这些方法用于启动、暂停、恢复和关闭调度器。

方法名说明
void start()启动调度器,开始执行任务。调度器启动后,触发器会根据时间表触发任务。
void startDelayed(int seconds)延迟指定秒数后启动调度器。
void standby()暂停调度器,暂停后触发器不会触发任务,但任务和触发器的状态会保留。
void shutdown()关闭调度器,停止所有任务执行。
void shutdown(boolean waitForJobsToComplete)关闭调度器,并决定是否等待正在执行的任务完成。true 表示等待任务完成后再关闭。

2. 任务和触发器管理

这些方法用于添加、删除、暂停、恢复任务和触发器。

方法名说明
void scheduleJob(JobDetail jobDetail, Trigger trigger)将任务(JobDetail)和触发器(Trigger)绑定,并添加到调度器中。
Date scheduleJob(Trigger trigger)添加触发器,并返回触发器首次触发的时间。
void addJob(JobDetail jobDetail, boolean replace)添加任务到调度器中。replace 为 true 时,如果任务已存在则替换。
boolean deleteJob(JobKey jobKey)根据任务键(JobKey)删除任务。返回 true 表示删除成功。
boolean deleteJobs(List<JobKey> jobKeys)批量删除任务。
void pauseJob(JobKey jobKey)暂停指定任务。
void pauseJobs(GroupMatcher<JobKey> matcher)暂停匹配指定组的所有任务。
void resumeJob(JobKey jobKey)恢复指定任务。
void resumeJobs(GroupMatcher<JobKey> matcher)恢复匹配指定组的所有任务。
void pauseTrigger(TriggerKey triggerKey)暂停指定触发器。
void pauseTriggers(GroupMatcher<TriggerKey> matcher)暂停匹配指定组的所有触发器。
void resumeTrigger(TriggerKey triggerKey)恢复指定触发器。
void resumeTriggers(GroupMatcher<TriggerKey> matcher)恢复匹配指定组的所有触发器。

3. 任务和触发器查询

这些方法用于查询调度器中的任务和触发器。

方法名说明
JobDetail getJobDetail(JobKey jobKey)根据任务键(JobKey)获取任务详情(JobDetail)。
List<? extends Trigger> getTriggersOfJob(JobKey jobKey)获取与指定任务关联的所有触发器。
Trigger getTrigger(TriggerKey triggerKey)根据触发器键(TriggerKey)获取触发器。
Set<JobKey> getJobKeys(GroupMatcher<JobKey> matcher)获取匹配指定组的所有任务键。
Set<TriggerKey> getTriggerKeys(GroupMatcher<TriggerKey> matcher)获取匹配指定组的所有触发器键。
SchedulerMetaData getMetaData()获取调度器的元数据,如调度器名称、版本、运行状态等。

4. 任务和触发器状态检查

这些方法用于检查任务和触发器的状态。

方法名说明
boolean checkExists(JobKey jobKey)检查指定任务是否存在。
boolean checkExists(TriggerKey triggerKey)检查指定触发器是否存在。
boolean isJobGroupPaused(String groupName)检查指定任务组是否处于暂停状态。
boolean isTriggerGroupPaused(String groupName)检查指定触发器组是否处于暂停状态。

5. 任务和触发器操作

这些方法用于直接操作任务和触发器。

方法名说明
void triggerJob(JobKey jobKey)立即触发指定任务。
void triggerJob(JobKey jobKey, JobDataMap data)立即触发指定任务,并传递额外的任务数据(JobDataMap)。
void interrupt(JobKey jobKey)中断正在执行的任务。
boolean unscheduleJob(TriggerKey triggerKey)移除指定触发器。
boolean unscheduleJobs(List<TriggerKey> triggerKeys)批量移除触发器。

6. 调度器状态检查

这些方法用于检查调度器的运行状态。

方法名说明
boolean isStarted()检查调度器是否已启动。
boolean isShutdown()检查调度器是否已关闭。
boolean isInStandbyMode()检查调度器是否处于暂停状态(standby mode)。

7. 调度器监听器管理

这些方法用于管理调度器的监听器。

方法名说明
void addJobListener(JobListener listener)添加任务监听器。
void addTriggerListener(TriggerListener listener)添加触发器监听器。
void addSchedulerListener(SchedulerListener listener)添加调度器监听器。
boolean removeJobListener(String name)移除指定名称的任务监听器。
boolean removeTriggerListener(String name)移除指定名称的触发器监听器。

二、前置项目准备

本章使用的微服务项目博客链接如下,内有对应项目源码。

从零搭建微服务项目Base(第7章——微服务网关模块基础实现)-CSDN博客https://blog.csdn.net/wlf2030/article/details/1456645271.从github下载对应项目解压,重命名为Pro1_3打开。

2.重命名模块为Pro1_3。

3.父工程pom.xml中<name>改成Pro1_3。

4.选择环境为dev,并重新加载maven。

5.启动nacos(安装和启动见第三章)。

6.进入nacos网页 配置管理->配置列表确认有这些yaml文件。

(如果不是一直跟着专栏做自然是没有的,需要看第四章的环境隔离和配置拉取,记得把父工程pom文件中namespace的值与nacos中命名空间生成的保持一致)

7.配置数据源,更换两服务的resources下yml文件的数据库配置,数据库sql见第一章数据库准备部分。

测试数据库连接 属性->点击数据源->测试连接->输入用户名密码

8.添加运行配置 服务->加号->运行配置类型->spring boot。

启动服务,测试接口。

(只有请求参数中包含token才放行)


三、Quartz模块配置

1.新建spring boot模块。

2.选择导入quartz模块(不导入也行,反正后面要换pom文件)

删除quartz模块下多余文件,只保留src以及pom.xml

父pom中加入quartz模块

查看maven结构是否如下,并选择配置文件为dev(如果不是在本专栏Base第0章有解决方法)

惯例在父pom文件中为新添加的模块配置不同环境下端口

删去quartz模块resources目录下原有配置文件,并创建application.yml内容如下:

server:
  port: @quartz.port@
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: @namespace@
  application:
    name: quartz

将quartz模块下pom内容替换如下:

<?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>cn.bit</groupId>
        <artifactId>Pro1_3</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <artifactId>quartz</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quartz</name>
    <description>quartz</description>

    <dependencies>
        <!-- quartz-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- nacos客户端依赖包 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

四、Demo测试

创建两个类,目录结构如下

DateJob内容如下:

package cn.bit.quartz.entity;

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class DateJob implements Job {
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
       System.out.println(new Date());
    }

}

QuartzController内容如下:

package cn.bit.quartz.controller;

import cn.bit.quartz.entity.DateJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/quartz")
public class QuartzController {
    @GetMapping("/test")
    public String test() throws SchedulerException {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail job = JobBuilder.newJob(DateJob.class).withIdentity("job1", "group1").build();

        Trigger trigger1 = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1").startNow()
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(5)
                        .withRepeatCount(5))
                .build();

        scheduler.scheduleJob(job, trigger1);
        scheduler.start();
        return "ok";
    }
}

 QuartzApplication设置排除默认数据源设置

启动 Quartz服务,并访问对应接口。

控制台间隔五秒输出当前时间。


五、核心包构建

0.创建数据库对应表以及修改application.yml文件,这里是创建了一个叫db_quartz的库

server:
  port: @quartz.port@
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_quartz?useSSL=false
    username: root
    password: 15947035212
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: quartz
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: cn.bit.pro1_1.core.mapper

数据库sql如下:

create table tb_task
(
    id              int auto_increment
        primary key,
    task_name       varchar(255)  not null,
    task_group      varchar(255)  not null,
    type            int           not null,
    bean_name       varchar(255)  null,
    class_name      varchar(255)  null,
    path            varchar(255)  null,
    method_name     varchar(255)  null,
    params          varchar(255)  null,
    cron_expression varchar(255)  not null,
    description     text          null,
    status          int default 0 not null,
    result          int           null
);
create table tb_task_log
(
    id             int auto_increment
        primary key,
    task_id        int          not null,
    start_time     datetime     not null,
    execute_time   varchar(255) not null,
    result         tinyint      not null,
    message        varchar(255) not null,
    exception_info text         null
);

删去之前移除数据源的部分

数据源添加quartz对应的库并测试连接成功

数据库显示有三个

1.删去demo部分创建的两个文件,创建core包,其中包结构如下(实际即为将1_2章项目源码中的core部分粘贴进来,并适当修改,有能力的小伙伴可以直接下载上章源码进行对应修改,当然一步步按顺序做便于理解模块)

2.entity创建任务实体类和日志实体类,代码如下

package cn.bit.quartz.core.entity;

import lombok.Data;

@Data
public class Task {
    private Integer id;
    private String taskName;
    private String taskGroup;
    private Integer type;//1、java类 2、Spring Bean 3、http请求
    private String beanName;//bean名称
    private String className;//java类名
    private String path;//rest请求路径
    private String methodName;//方法名
    private String params;//方法参数
    private String cronExpression;//cron表达式
    private String description;//描述
    private Integer status;//任务当前状态
    private Integer result;//任务执行结果
}
package cn.bit.quartz.core.entity;

import lombok.Data;

import java.util.Date;

@Data
public class TaskLog {
    private Integer id;
    private Integer taskId;
    private Date startTime;
    private String executeTime;
    private Integer result;//0失败 1成功
    private String message;//日志信息
    private String exceptionInfo;//异常信息
}

3.enums包下创建任务结果、任务状态、任务类型枚举,具体如下

package cn.bit.quartz.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum Result {
    FAIL(0,"失败"),
    SUCCESS(1,"成功")
    ;
    private final Integer code;
    private final String desc;
}
package cn.bit.quartz.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum TaskStatus {
    PAUSE(0, "已发布"),
    RUNNING(1, "运行中");
    private final Integer code;
    private final String desc;
}
package cn.bit.quartz.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum TaskType {
    JAVA_CLASS(1,"java类"),
    SPRING_BEAN(2,"spring bean"),
    HTTP(3,"http");
    private final Integer code;
    private final String desc;
}

4.exception包下创建任务持久化异常和任务执行异常,具体如下

package cn.bit.quartz.core.exception;

import lombok.Getter;

@Getter
public class TaskInvokeException extends Exception {
    private final Exception exception;
    public TaskInvokeException(String message,Exception exception) {
        super(message);
        this.exception = exception;
    }
}
package cn.bit.quartz.core.exception;


import cn.bit.quartz.core.entity.Task;
import lombok.Getter;

@Getter
public class TaskRepositoryException extends RuntimeException {
    private final Task task;
    public TaskRepositoryException(String message,Task task) {
        super(message);
        this.task = task;
    }
}

5.mapper包下创建实体类的持久化接口

package cn.bit.quartz.core.mapper;

;import cn.bit.quartz.core.entity.TaskLog;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface TaskLogMapper {
    int insert(TaskLog taskLog);
    List<TaskLog> selectByTaskId(int taskId);
}
package cn.bit.quartz.core.mapper;



import cn.bit.quartz.core.entity.Task;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface TaskMapper {
    List<Task> selectAllTask();
    int updateTaskInfo(@Param("task") Task task);
    int updateTaskStatus(@Param("task") Task task);
    int insertTask(@Param("task") Task task);
    int deleteTask(@Param("task") Task task);
    int setTaskResult(@Param("task") Task task);
    Task selectTaskByNameAndGroup(@Param("name") String name, @Param("group") String group );
}

6.以及resources下对应mapper.xml,注意目录结构,与application中的mybatis配置有关。

内容分别如下:

<?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="cn.bit.quartz.core.mapper.TaskLogMapper">

    <resultMap id="TaskLogResultMap" type="cn.bit.quartz.core.entity.TaskLog">
        <id property="id" column="id"/>
        <result property="taskId" column="task_id"/>
        <result property="startTime" column="start_time"/>
        <result property="executeTime" column="execute_time"/>
        <result property="result" column="result"/>
        <result property="message" column="message"/>
        <result property="exceptionInfo" column="exception_info"/>
    </resultMap>

    <insert id="insert" parameterType="cn.bit.quartz.core.entity.TaskLog">
        INSERT INTO tb_task_log(task_id, start_time, execute_time, result, message, exception_info)
        VALUES (#{taskId}, #{startTime}, #{executeTime}, #{result}, #{message}, #{exceptionInfo})
    </insert>

    <select id="selectByTaskId" resultMap="TaskLogResultMap">
        SELECT * FROM tb_task_log WHERE task_id = #{taskId}
    </select>
</mapper>
<?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="cn.bit.quartz.core.mapper.TaskMapper">
    <resultMap id="TaskResultMap" type="cn.bit.quartz.core.entity.Task">
        <result property="id" column="id"/>
        <result property="taskName" column="task_name"/>
        <result property="taskGroup" column="task_group"/>
        <result property="type" column="type"/>
        <result property="beanName" column="bean_name"/>
        <result property="className" column="class_name"/>
        <result property="path" column="path"/>
        <result property="methodName" column="method_name"/>
        <result property="params" column="params"/>
        <result property="cronExpression" column="cron_expression"/>
        <result property="description" column="description"/>
        <result property="status" column="status"/>
        <result property="result" column="result"/>
    </resultMap>
    <insert id="insertTask">
        INSERT INTO tb_task (
            task_name,
            task_group,
            type,
            bean_name,
            class_name,
            path,
            method_name,
            params,
            cron_expression,
            description
        ) VALUES (
                     #{task.taskName},
                     #{task.taskGroup},
                     #{task.type},
                     #{task.beanName},
                     #{task.className},
                     #{task.path},
                     #{task.methodName},
                     #{task.params},
                     #{task.cronExpression},
                     #{task.description}
                 )
    </insert>
    <delete id="deleteTask">
        delete from tb_task
        where
            task_name = #{task.taskName} and
            task_group = #{task.taskGroup};
    </delete>


    <select id="selectAllTask" resultMap="TaskResultMap">
        SELECT * FROM tb_task;
    </select>
    <select id="selectTaskByNameAndGroup" resultMap="TaskResultMap">
        select * from tb_task
        where
            task_name = #{name} and
            task_group = #{group}
    </select>

    <update id="updateTaskInfo">
        UPDATE tb_task
        SET
            type = #{task.type},
            bean_name = #{task.beanName},
            class_name = #{task.className},
            path = #{task.path},
            method_name = #{task.methodName},
            params = #{task.params},
            cron_expression = #{task.cronExpression},
            description = #{task.description}
        WHERE
            task_name = #{task.taskName} and
            task_group = #{task.taskGroup};
    </update>
    <update id="updateTaskStatus">
        UPDATE tb_task
        SET
            status = #{task.status}
        WHERE
            task_name = #{task.taskName} and
            task_group = #{task.taskGroup};
    </update>
    <update id="setTaskResult">
        update tb_task
        set
            result = #{task.result}
        where
            task_name = #{task.taskName} and
            task_group = #{task.taskGroup};
    </update>
</mapper>

7.创建对应持久化服务

内容如下:

package cn.bit.quartz.core.service;



import cn.bit.quartz.core.entity.Task;

import java.util.List;


public interface TaskService {
    List<Task> selectAllTask();
    int updateTaskInfo(Task task);
    int updateTaskStatus(Task task);
    int insertTask(Task task);
    int deleteTask(Task task);
    int setTaskResult(Task task);
    Task selectTaskByNameAndGroup(String taskName, String groupName);
}
package cn.bit.quartz.core.service;



import cn.bit.quartz.core.entity.TaskLog;

import java.util.List;

public interface TaskLogService {
    int insert(TaskLog taskLog);
    List<TaskLog> selectByTaskId(int taskId);
}
package cn.bit.quartz.core.service.impl;


import cn.bit.quartz.core.entity.TaskLog;
import cn.bit.quartz.core.mapper.TaskLogMapper;
import cn.bit.quartz.core.service.TaskLogService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@AllArgsConstructor
public class TaskLogServiceImpl implements TaskLogService {
    private final TaskLogMapper taskLogMapper;
    @Override
    public int insert(TaskLog taskLog) {
        return taskLogMapper.insert(taskLog);
    }

    @Override
    public List<TaskLog> selectByTaskId(int taskId) {
        return taskLogMapper.selectByTaskId(taskId);
    }
}
package cn.bit.quartz.core.service.impl;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.mapper.TaskMapper;
import cn.bit.quartz.core.service.TaskService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@AllArgsConstructor
public class TaskServiceImpl implements TaskService {
    private TaskMapper taskMapper;
    @Override
    public List<Task> selectAllTask() {
        return taskMapper.selectAllTask();
    }

    @Override
    public int updateTaskInfo(Task task) {
        return taskMapper.updateTaskInfo(task);
    }

    @Override
    public int updateTaskStatus(Task task) {
        return taskMapper.updateTaskStatus(task);
    }

    @Override
    public int insertTask(Task task) {
        return taskMapper.insertTask(task);
    }

    @Override
    public int deleteTask(Task task) {
        return taskMapper.deleteTask(task);
    }

    @Override
    public int setTaskResult(Task task) {
        return taskMapper.setTaskResult(task);
    }

    @Override
    public Task selectTaskByNameAndGroup(String taskName, String groupName) {
        return taskMapper.selectTaskByNameAndGroup(taskName, groupName);
    }
}

8.util包下创建springbean的工具类(bean反射会用到)

package cn.bit.quartz.core.util;

import lombok.Getter;
import lombok.NonNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

@Service
public class SpringContextHolder implements ApplicationContextAware {
    @Getter
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
}

9.handler包下创建不同任务类型反射类,以及对应工厂类

package cn.bit.quartz.core.handler;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.exception.TaskInvokeException;

public interface ITaskHandler {
    void invoke(Task task) throws TaskInvokeException;
}
package cn.bit.quartz.core.handler.impl;



import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.enums.Result;
import cn.bit.quartz.core.exception.TaskInvokeException;
import cn.bit.quartz.core.handler.ITaskHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@Slf4j
@Component
public class JavaClassTaskHandler implements ITaskHandler {
    @Override
    public void invoke(Task task) throws TaskInvokeException {
        try {
            Object target;
            Class<?> clazz;
            Method method;
            Result returnValue;
            clazz = Class.forName(task.getClassName());
            target = clazz.newInstance();
            if (task.getParams() == null || task.getParams().isEmpty()) {
                method = target.getClass().getDeclaredMethod(task.getMethodName());
                ReflectionUtils.makeAccessible(method);
                returnValue = (Result) method.invoke(target);
            } else {
                method = target.getClass().getDeclaredMethod(task.getMethodName(), String.class);
                ReflectionUtils.makeAccessible(method);
                returnValue = (Result) method.invoke(target, task.getParams());
            }
            //判断业务是否执行成功
            if (returnValue == null || Result.FAIL.equals(returnValue))
                throw new TaskInvokeException("JavaClassTaskHandler方法执行失败",null);
        } catch (NoSuchMethodException e) {
            throw new TaskInvokeException("JavaClassTaskHandler找不到对应方法", e);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new TaskInvokeException("JavaClassTaskHandler执行反射方法异常", e);
        } catch (ClassCastException e) {
            throw new TaskInvokeException("JavaClassTaskHandler方法返回值定义错误", e);
        } catch (ClassNotFoundException e) {
            throw new TaskInvokeException("JavaClassTaskHandler找不到对应类", e);
        } catch (InstantiationException e) {
            throw new TaskInvokeException("JavaClassTaskHandler实例化错误", e);
        }
    }
}
package cn.bit.quartz.core.handler.impl;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.enums.Result;
import cn.bit.quartz.core.exception.TaskInvokeException;
import cn.bit.quartz.core.handler.ITaskHandler;
import cn.bit.quartz.core.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@Slf4j
@Component
public class SpringBeanTaskHandler implements ITaskHandler {
    @Override
    public void invoke(Task task) throws TaskInvokeException {
        try {
            Object target;
            Method method;
            Result returnValue;
            //上下文寻找对应bean
            target = SpringContextHolder.getApplicationContext().getBean(task.getBeanName());
            //寻找对应方法
            if(task.getParams()==null|| task.getParams().isEmpty())
            {
                method = target.getClass().getDeclaredMethod(task.getMethodName());
                ReflectionUtils.makeAccessible(method);
                returnValue = (Result) method.invoke(target);
            }
            else{
                method = target.getClass().getDeclaredMethod(task.getMethodName(), String.class);
                ReflectionUtils.makeAccessible(method);
                returnValue = (Result) method.invoke(target, task.getParams());
            }
            //判断业务是否执行成功
            if(returnValue==null || Result.FAIL.equals(returnValue))
                throw new TaskInvokeException("SpringBeanTaskHandler方法执行失败", null);
        }catch (NoSuchBeanDefinitionException e){
            throw new TaskInvokeException("SpringBeanTaskHandler找不到对应bean",e);
        } catch (NoSuchMethodException e) {
            throw new TaskInvokeException("SpringBeanTaskHandler找不到对应方法",e);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new TaskInvokeException("SpringBeanTaskHandler执行反射方法异常",e);
        } catch (ClassCastException e) {
            throw new TaskInvokeException("SpringBeanTaskHandler方法返回值定义错误",e);
        }
    }
}
package cn.bit.quartz.core.handler;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.enums.TaskType;
import cn.bit.quartz.core.handler.impl.JavaClassTaskHandler;
import cn.bit.quartz.core.handler.impl.SpringBeanTaskHandler;
import cn.bit.quartz.core.util.SpringContextHolder;
import org.springframework.stereotype.Component;

@Component
public class TaskHandlerFactory {
    public static ITaskHandler getTaskHandler(Task task) {
        ITaskHandler taskHandler = null;
        if(TaskType.SPRING_BEAN.getCode().equals(task.getType())) {
            taskHandler = SpringContextHolder.getApplicationContext().getBean(SpringBeanTaskHandler.class);
        }
        if(TaskType.JAVA_CLASS.getCode().equals(task.getType())) {
            taskHandler = SpringContextHolder.getApplicationContext().getBean(JavaClassTaskHandler.class);
        }
        return taskHandler;
    }
}

10.events包下创建任务调用事件,监听者和发布者,其中发布者实现Job接口

package cn.bit.quartz.core.events.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@ToString
@Getter
@AllArgsConstructor
public class TaskInvokeEvent {
    private final String taskName;
    private final String taskGroup;
}
package cn.bit.quartz.core.events.listener;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.entity.TaskLog;
import cn.bit.quartz.core.enums.Result;
import cn.bit.quartz.core.events.event.TaskInvokeEvent;
import cn.bit.quartz.core.exception.TaskInvokeException;
import cn.bit.quartz.core.handler.ITaskHandler;
import cn.bit.quartz.core.handler.TaskHandlerFactory;
import cn.bit.quartz.core.service.TaskLogService;
import cn.bit.quartz.core.service.TaskService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Date;

@Slf4j
@AllArgsConstructor
@Component
public class TaskInvokeListener {
    private final TaskLogService taskLogService;
    private final TaskService taskService;
    @Async
    @Order
    @EventListener(TaskInvokeEvent.class)
    public void notifyTaskInvoke(TaskInvokeEvent event) {
        //从数据库中拿取任务
        Task task = taskService.selectTaskByNameAndGroup(event.getTaskName(), event.getTaskGroup());
        log.info("任务执行事件监听,准备执行任务{}",task);
        ITaskHandler handler = TaskHandlerFactory.getTaskHandler(task);

        long startTime = System.currentTimeMillis();
        TaskLog taskLog = new TaskLog();
        taskLog.setTaskId(task.getId());
        taskLog.setStartTime(new Date());
        boolean success = true;
        try {
            handler.invoke(task);
        } catch (TaskInvokeException e) {
            log.error("{},Task:{}", e.getMessage(),task);
            success = false;
            taskLog.setMessage(e.getMessage());
            if(e.getException()!=null){
                taskLog.setExceptionInfo(e.getException().getCause().toString());
                e.getException().printStackTrace();
            }
        }
        if(success)
        {
            taskLog.setMessage("执行成功");
            taskLog.setResult(Result.SUCCESS.getCode());
            task.setResult(Result.SUCCESS.getCode());
            taskService.setTaskResult(task);
        }
        else
        {
            taskLog.setResult(Result.FAIL.getCode());
            task.setResult(Result.FAIL.getCode());
            taskService.setTaskResult(task);
        }
        long endTime = System.currentTimeMillis();
        taskLog.setExecuteTime(String.valueOf(endTime-startTime));
        taskLogService.insert(taskLog);
    }
}
package cn.bit.quartz.core.events.publisher;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.events.event.TaskInvokeEvent;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Slf4j
@Component
public class TaskInvokePublisher implements Job {

    private final ApplicationEventPublisher publisher;
    @Override
    public void execute(JobExecutionContext jobExecutionContext){
        Task task = (Task) jobExecutionContext.getJobDetail().getJobDataMap().get("task");
        //发布事件异步执行任务
        TaskInvokeEvent event =new TaskInvokeEvent(task.getTaskName(),task.getTaskGroup());
        publisher.publishEvent(event);
        log.info("任务执行事件发布:{}",event);
    }
}

11.mvc下创建对应任务控制类

package cn.bit.quartz.core.mvc;

import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.events.publisher.TaskInvokePublisher;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@AllArgsConstructor
public class TaskPool {

    public static JobKey getJobKey(@NonNull Task task) {
        return JobKey.jobKey(task.getTaskName(),task.getTaskGroup());
    }
    public static TriggerKey getTriggerKey(@NonNull Task task) {
        return TriggerKey.triggerKey(task.getTaskName(),task.getTaskGroup());
    }

    /**
     * 任务池添加任务
     * @param task
     * @param scheduler
     * @throws SchedulerException
     */
    public void addTask(@NonNull Task task,Scheduler scheduler) throws SchedulerException {
        JobKey jobKey = getJobKey(task);
        TriggerKey triggerKey = getTriggerKey(task);
        JobDetail jobDetail = JobBuilder.newJob(TaskInvokePublisher.class).withIdentity(jobKey).build();
        jobDetail.getJobDataMap().put("task",task);
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCronExpression());
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 任务池暂停并移除任务
     * @param task
     * @param scheduler
     * @throws SchedulerException
     */
    public void pauseTask(@NonNull Task task,Scheduler scheduler) throws SchedulerException {
        scheduler.pauseJob(getJobKey(task));
        scheduler.deleteJob(getJobKey(task));
    }

}
package cn.bit.quartz.core.mvc;


import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.enums.TaskStatus;
import cn.bit.quartz.core.exception.TaskRepositoryException;
import cn.bit.quartz.core.service.TaskService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;

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

@Slf4j
@Service
@AllArgsConstructor
public class TaskManger {
    private final Scheduler scheduler;
    private final TaskService taskService;
    private final TaskPool taskPool;

    /**
     * 从数据库中反序列化任务数据,保证服务器重启后恢复任务池状态
     * @throws SchedulerException
     */
    @PostConstruct
    public void init() throws SchedulerException {
        log.info("TaskManager初始化开始...");
        List<Task> tasks = taskService.selectAllTask();
        if(tasks != null && !tasks.isEmpty()) {
            for (Task task : tasks)
            {
                if(TaskStatus.RUNNING.getCode().equals(task.getStatus()))
                    taskPool.addTask(task,scheduler);
            }
            log.info("初始化加载{}项任务", tasks.size());
        }
        log.info("TaskManager初始化结束...");
    }

    /**
     * 添加暂停且未被持久化的新任务
     * @param task
     * @throws SchedulerException
     */
    public void addTask(Task task) throws SchedulerException {
        Task temp = taskService.selectTaskByNameAndGroup(task.getTaskName(),task.getTaskGroup());
        if(temp != null)
            throw new TaskRepositoryException("存在相同任务组和任务名任务",task);
        if(!TaskStatus.PAUSE.getCode().equals(task.getStatus()))
            throw new TaskRepositoryException("只能添加暂停的任务",task);
        taskService.insertTask(task);
        log.info("添加任务{}", task);
    }

    /**
     * 在任务暂停时更新任务信息
     * @param task
     * @throws SchedulerException
     */
    public void updateTask(Task task) throws SchedulerException {
        Task temp = taskService.selectTaskByNameAndGroup(task.getTaskName(),task.getTaskGroup());
        if(temp == null)
            throw new TaskRepositoryException("不存在对应相同任务组和任务名任务",task);
        if(!TaskStatus.PAUSE.getCode().equals(temp.getStatus()))
            throw new TaskRepositoryException("只能暂停时更新任务",task);
        taskService.updateTaskInfo(task);
        log.info("更新任务{}", task);
    }

    /**
     * 启动暂停中任务
     * @param task 只使用name和group字段
     * @throws SchedulerException
     */
    public void startTask(Task task) throws SchedulerException {
        Task temp = taskService.selectTaskByNameAndGroup(task.getTaskName(),task.getTaskGroup());
        if(temp == null)
            throw new TaskRepositoryException("不存在对应相同任务组和任务名任务",task);
        if(!TaskStatus.PAUSE.getCode().equals(temp.getStatus()))
            throw new TaskRepositoryException("只能启动暂停中任务",task);
        taskPool.addTask(temp,scheduler);
        //添加任务池未有异常时持久化数据
        temp.setStatus(TaskStatus.RUNNING.getCode());
        taskService.updateTaskStatus(temp);
        log.info("启动任务{}", temp);
    }

    /**
     * 暂停运行中任务
     * @param task 只使用name和group字段
     * @throws SchedulerException
     */
    public void pauseTask(Task task) throws SchedulerException {
        Task temp = taskService.selectTaskByNameAndGroup(task.getTaskName(),task.getTaskGroup());
        if(temp == null)
            throw new TaskRepositoryException("不存在对应相同任务组和任务名任务",task);
        if(!TaskStatus.RUNNING.getCode().equals(temp.getStatus()))
            throw new TaskRepositoryException("只能暂停运行中任务",task);
        taskPool.pauseTask(temp,scheduler);
        //添加任务池未有异常时持久化数据
        temp.setStatus(TaskStatus.PAUSE.getCode());
        taskService.updateTaskStatus(temp);
        log.info("暂停任务{}", temp);
    }

    /**
     * 暂停暂停中任务
     * @param task 只使用name和group字段
     * @throws SchedulerException
     */
    public void deleteTask(Task task) throws SchedulerException {
        Task temp = taskService.selectTaskByNameAndGroup(task.getTaskName(),task.getTaskGroup());
        if(temp == null)
            throw new TaskRepositoryException("不存在对应相同任务组和任务名任务",task);
        if(!TaskStatus.PAUSE.getCode().equals(temp.getStatus()))
            throw new TaskRepositoryException("只能删除暂停中任务",task);
        taskService.deleteTask(temp);
        log.info("删除任务{}", temp);
    }
}

12.以core同级创建controller包作为quartz服务对外接口,并创建类

package cn.bit.quartz.controller;

import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.mvc.TaskManger;
import lombok.AllArgsConstructor;
import org.quartz.SchedulerException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/quartz")
@AllArgsConstructor
public class QuartzController {

    private TaskManger taskManger;
    @PostMapping("/add")
    public String add(@RequestBody Task task) throws SchedulerException {
        taskManger.addTask(task);
        return "success";
    }

    @PutMapping("/update")
    public String update(@RequestBody Task task) throws SchedulerException {
        taskManger.updateTask(task);
        return "success";
    }

    @PutMapping("/start")
    public String start(@RequestBody Task task) throws SchedulerException {
        taskManger.startTask(task);
        return "success";
    }

    @PutMapping("/pause")
    public String pause(@RequestBody Task task) throws SchedulerException {
        taskManger.pauseTask(task);
        return "success";
    }

    @DeleteMapping("/delete")
    public String delete(@RequestBody Task task) throws SchedulerException {
        taskManger.deleteTask(task);
        return "success";
    }
}

六、单模块测试

在controller包下创建测试类

package cn.bit.quartz.controller;

import cn.bit.quartz.core.enums.Result;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component("test")
public class Test {
    public Result test(String param) {
        System.out.println("test "+param+" "+new Date());
        return Result.SUCCESS;
    }
}

单独启动quartz服务,分别使用postman依次发出下面请求

{
  "taskName": "task1",
  "taskGroup": "group1",
  "type": 2,
  "beanName": "test",
  "methodName": "test",
  "params": "test1",
  "cronExpression": "*/5 * * * * ?",
  "status": 0
}

控制台输出:

{
  "taskName": "task1",
  "taskGroup": "group1"
}

控制台每5s执行一次任务

{
  "taskName": "task1",
  "taskGroup": "group1"
}

控制台显示任务被暂停

(从下面开始则为将quartz整合至微服务项目中,为组件复用,会有很多模块间的导入,但同时又需要考虑到spring上下文不互通导致无法获取其他模块的bean,本文采用的方式是将公共类抽取至common模块中,其他模块均导入common模块,选择性导入common中bean,但同时可能各模块core中的实体类不在本模块中,有利有弊,以及模块的导入涵盖了Base章节的大部分内容,有一定门槛建议先了解Base章节中内容再继续,当然如果只是想知道springboot中quartz模块的使用到这里就可以不用往下看了)


七、服务整合

1.服务注册

quartz模块创建bootstrap.yml内容如下:

spring:
  application:
    name: quartz
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: @namespace@

添加后启动在nacos中能够看到服务被注册

2.网关路由

修改gateway配置文件内容如下

server:
  port: @gateway.port@
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        namespace: @namespace@
    gateway:
      routes:
        - id: user-service
          uri:
            lb://user-service
          predicates:
            - name: Path
              args:
                _genkey_0: /user/**
          filters:
            - name: AddRequestHeader
              args:
                name: source
                value: request user from gateway
        - id: order-service
          uri:
            lb://order-service
          predicates:
            - name: Path
              args:
                _genkey_0: /order/**
          filters:
            - name: AddRequestHeader
              args:
                name: source
                value: request order from gateway
        - id: quartz
          uri:
            lb://quartz
          predicates:
            - name: Path
              args:
                _genkey_0: /quartz/**

启动网关服务和quartz服务后,通过网关端口调用接口(网关有个简单的鉴权需要token = admin放行)

3.全局异常处理

common模块创建exception包用于定义全局异常,创建业务异常内容如下:

package cn.bit.common.exception;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class BizException extends RuntimeException {


    public BizException(String message) {
        super(message);
    }

    public BizException(Throwable cause) {
        super(cause);
    }

    public BizException(String message, Throwable cause) {
        super(message, cause);
    }

    public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

}

各模块的业务逻辑异常应当由各模块自己处理,为方便复用,处理类放于common模块下,common导入web依赖

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

在common模块新建handler包,以及异常处理类,内容如下,此处将Pro2-1章的jsr异常也提前添加。

package cn.bit.common.handler;

import cn.bit.common.exception.BizException;
import cn.bit.common.pojo.vo.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.nio.file.AccessDeniedException;
import java.util.List;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 全局异常.
     * @param e the e
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R handleGlobalException(Exception e) {
        log.error("全局异常信息 ex={}", e.getMessage(), e);
        return R.failed(e.getLocalizedMessage());
    }

    /**
     * AccessDeniedException
     * @param e the e
     * @return R
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public R handleAccessDeniedException(AccessDeniedException e) {
        log.error("拒绝授权异常信息 ex={}", e.getLocalizedMessage(),e);
        return R.failed(e.getLocalizedMessage());
    }

    /**
     * 业务处理类
     * @param e the e
     * @return R
     */
    @ExceptionHandler({ BizException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R bizExceptionHandler(BizException e) {
        log.warn("业务处理异常,ex = {}", e.getMessage());
        return R.failed(e.getMessage());
    }

    /**
     * validation Exception
     * @param e the e
     * @return R
     */
    @ExceptionHandler({ MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleBodyValidException(MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        StringBuilder errorMsg = new StringBuilder();
        fieldErrors.forEach(fieldError -> {errorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(" ");});
        log.warn("参数绑定异常,ex = {}",errorMsg);
        return R.failed(errorMsg.toString());
    }

    /**
     * validation Exception (以form-data形式传参)
     * @param e the e
     * @return R
     */
    @ExceptionHandler({ BindException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R bindExceptionHandler(BindException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        StringBuilder errorMsg = new StringBuilder();
        fieldErrors.forEach(fieldError -> {errorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append("\n");});
        log.warn("参数绑定异常(form-data),ex = {}",errorMsg);
        return R.failed(errorMsg.toString());
    }
}

 4.统一响应体格式以及异常统一

quartz中添加项目中common模块的依赖,从中拿取公共类

        <dependency>
            <groupId>cn.bit</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

修改quartz模块controller内容如下:

package cn.bit.quartz.controller;

import cn.bit.common.exception.BizException;
import cn.bit.common.pojo.vo.R;
import cn.bit.quartz.core.entity.Task;
import cn.bit.quartz.core.exception.TaskRepositoryException;
import cn.bit.quartz.core.mvc.TaskManger;
import lombok.AllArgsConstructor;
import org.quartz.SchedulerException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/quartz")
@AllArgsConstructor
public class QuartzController {

    private TaskManger taskManger;
    @PostMapping("/add")
    public R add(@RequestBody Task task){
        try {
            taskManger.addTask(task);
        } catch (SchedulerException | TaskRepositoryException e) {
            throw new BizException(e);
        }
        return R.ok("添加任务成功");
    }

    @PutMapping("/update")
    public R update(@RequestBody Task task){
        try {
            taskManger.updateTask(task);
        }catch (SchedulerException | TaskRepositoryException e) {
            throw new BizException(e.getMessage());
        }
        return R.ok("任务更新成功");
    }

    @PutMapping("/start")
    public R start(@RequestBody Task task){
        try {
            taskManger.startTask(task);
        }catch (SchedulerException | TaskRepositoryException e) {
            throw new BizException(e.getMessage());
        }
        return R.ok("任务启动成功");
    }

    @PutMapping("/pause")
    public R pause(@RequestBody Task task){
        try {
            taskManger.pauseTask(task);
        } catch (SchedulerException | TaskRepositoryException e) {
            throw new BizException(e.getMessage());
        }
        return R.ok("任务暂停成功");
    }

    @DeleteMapping("/delete")
    public R delete(@RequestBody Task task){
        try {
            taskManger.deleteTask(task);
        } catch (SchedulerException | TaskRepositoryException e) {
            throw new BizException(e.getMessage());
        }
        return R.ok("任务删除成功");
    }
}

并在quartz模块的application中引入异常处理类的bean

package cn.bit.quartz;

import cn.bit.common.handler.GlobalExceptionHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;


@SpringBootApplication
@Import(GlobalExceptionHandler.class)
public class QuartzApplication {

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

}

八、整合测试

启动网关和quartz模块,postman测试接口

访问网关接口,不加admin,返回401未鉴权错误码

尝试添加相同任务

启动暂停任务

启动运行任务


最后:

本来是想再添加对应的feignclient但实在想不出有什么业务需要调用order-service或user-service来开启定时任务,直接调用quartz模块不是来的更快。至此,关于quartz定时模块的研究到此正式完结撒花 ,基本框架不会变更了,后续仅做微调,以及最后希望大家多多支持!

相关文章:

  • Perl 调用 DeepSeek API 脚本
  • 云原生监控篇——全链路可观测性与AIOps实战
  • 安装即是高级版!专业版软件,
  • Hadoop之01:HDFS分布式文件系统
  • Xshell及Xftp v8.0安装与使用-生信工具050
  • 【AI学习从零至壹】pytorch基础
  • Linux安装Apache2.4.54操作步骤
  • 前端js搭建(搭建后包含cookie,弹窗,禁用f12)
  • onerror事件的理解与用法
  • 【人工智能】GPT-4 vs DeepSeek-R1:谁主导了2025年的AI技术竞争?
  • 对大模型输出的 logits 进行处理,从而控制文本的生成
  • Java---入门基础篇(下)---方法与数组
  • C++类和对象:匿名对象及连续构造拷贝编译器的优化
  • Windows下git疑难:有文件无法被跟踪
  • FPGA开发,使用Deepseek V3还是R1(1):应用场景
  • openssl下aes128算法CFB模式加解密运算实例
  • 【自学笔记】大数据基础知识点总览-持续更新
  • 机器视觉3D偏光法原理解析
  • Oracle 数据库基础入门(四):分组与联表查询的深度探索(上)
  • 8. Nginx 配合 + Keepalived 搭建高可用集群
  • 网站如何分页/免费引流推广怎么做
  • 东山县城乡规划建设局网站/营销咨询公司排名
  • 电脑上wap网站/株洲seo
  • 厦门网站建设公司电话/2020做seo还有出路吗
  • 自己的电脑建网站/做网络推广一般是什么专业
  • 深夜一个人适合看的电影/长沙网站seo哪家公司好