1.配置文件
java
spring:
datasource:
name: mopsDruidDataSource #数据源名字
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/itsm?useSSL=false&serverTimezone=UTC
username: root
password: root
druid:
#初始化时建立物理连接的个数
initial-size: 5
#最小连接池数量
min-idle: 5
#最大连接池数量 maxIdle已经不再使用
max-active: 20
#获取连接时最大等待时间,单位毫秒
max-wait: 60000
#用来检测连接是否有效的sql
validation-query: SELECT 1
2.配置类
java
import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.utils.ConnectionProvider;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class DruidQuartzConnectionProvider2 implements ConnectionProvider {
private final DruidDataSource dataSource;
public DruidQuartzConnectionProvider2(DruidDataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
// 直接从 Spring 数据源获取连接(无需手动配置 URL 等)
return dataSource.getConnection();
}
@Override
public void shutdown() {
// 关闭连接池(由 Spring 管理,通常无需手动调用)
}
@Override
public void initialize() throws SQLException {
// 移除 dataSource.init(),依赖 Spring 初始化
// dataSource.init();
}
// quartz:
// auto-startup: true
// # 指定使用数据库作为任务存储方式,这使得多个节点可以共享任务和触发器信息
// job-store-type: jdbc
// jdbc:
// # 在应用启动时初始化或更新 Quartz 的数据库表结构。对于生产环境,建议设置为 never 或者手动管理数据库初始化,以避免意外覆盖现有数据
// initialize-schema: never
// properties:
// org:
// quartz:
// scheduler:
// # 集群中所有节点共用的名字
// instanceName: MyClusteredScheduler
// # 设置为 AUTO 表示 Quartz 自动为每个实例生成一个唯一的实例ID
// instanceId: AUTO
// threadPool:
// # 定义了 Quartz 线程池中的线程数和优先级,用于执行作业
// threadCount: 25
// threadPriority: 5
// jobStore:
// # 作为事务管理的持久化类,并且指定了标准的 JDBC 代理类、数据源名称、表前缀和集群模式启用状态
// class: org.quartz.impl.jdbcjobstore.JobStoreTX
// driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
// tablePrefix: QRTZ_
// # 启用了集群支持,确保多节点间任务的协调
// isClustered: true
// clusterCheckinInterval: 20000
// useProperties: false
// #指定数据源名字
// dataSource: mopsDruidDataSource
}
3.配置类
java
import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.spi.TriggerFiredBundle;
import org.quartz.utils.DBConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.annotation.PostConstruct;
import java.util.Properties;
@Configuration
@Order(1)
public class QuartzConfig {
@Autowired
private DruidDataSource dataSource;
@Autowired
private ApplicationContext applicationContext; // 注入 Spring 上下文
/**
* 由于quartz报一直找不到数据源,不知道为什么?,所以需要手动添加数据源
* @date 2025/5/12 9:46
*/
@PostConstruct
public void initDataSourceProvider() {
DBConnectionManager.getInstance().addConnectionProvider("mopsDruidDataSource", new DruidQuartzConnectionProvider2(dataSource));
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setQuartzProperties(quartzProperties());
factory.setAutoStartup(true);
factory.setOverwriteExistingJobs(true);
// 关键:将 ApplicationContext 转换为 ConfigurableApplicationContext
ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext;
AutowireCapableBeanFactory autowireCapableBeanFactory = configurableContext.getAutowireCapableBeanFactory();
// 配置 JobFactory,让 Quartz 使用 Spring 的 Bean 工厂创建 Job 实例
factory.setJobFactory(new AdaptableJobFactory() {
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 1. 创建 Job 实例(由 Quartz 默认工厂创建)
Object jobInstance = super.createJobInstance(bundle);
// 2. 让 Spring 的 AutowireCapableBeanFactory 自动注入依赖
autowireCapableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
});
return factory;
}
@Bean
public Scheduler scheduler(SchedulerFactoryBean factory) {
return factory.getScheduler();
}
private Properties quartzProperties() {
Properties properties = new Properties();
properties.setProperty("org.quartz.scheduler.instanceName", "MyClusteredScheduler");
properties.setProperty("org.quartz.scheduler.instanceId", "AUTO");
properties.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
properties.setProperty("org.quartz.threadPool.threadCount", "25");
properties.setProperty("org.quartz.threadPool.threadPriority", "5");
properties.setProperty("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
properties.setProperty("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
properties.setProperty("org.quartz.jobStore.tablePrefix", "QRTZ_");
properties.setProperty("org.quartz.jobStore.isClustered", "true");
properties.setProperty("org.quartz.jobStore.clusterCheckinInterval", "20000");
properties.setProperty("org.quartz.jobStore.useProperties", "false");
properties.setProperty("org.quartz.jobStore.dataSource", "mopsDruidDataSource");
properties.setProperty("org.quartz.scheduler.skipUpdateCheck", "true"); // 跳过版本检查
return properties;
}
}
4.任务类
java
import com.enterprise.itsm.util.LogUtil;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class DynamicBackupJob implements Job {
private static final Logger log = LoggerFactory.getLogger(DynamicBackupJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LogUtil.info(log, "DynamicBackupJob-run", "开始执行定时任务");
JobDataMap dataMap = context.getMergedJobDataMap();
String syncF5TaskJson = dataMap.getString("backupTask");
// SyncF5DeviceCronConfigPO configPO = JSONUtil.toBean(syncF5TaskJson, SyncF5DeviceCronConfigPO.class);
//备份任务真正执行起来的逻辑
}
}
5.调度开启
java
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* 动态备份任务调度服务
* 负责在应用启动时加载数据库中的备份配置,并动态注册到 Quartz 调度器中
*/
@Slf4j
@Component
public class BackupSchedulerService implements CommandLineRunner {
@Resource
private Scheduler scheduler;
@Override
public void run(String... args) {
try {
log.info("【备份调度器】服务启动,开始初始化动态备份任务...");
// 调度所有备份任务,你也可以查询所有的任务,然后循环调用
scheduleAllBackupTasks();
// 检查触发器状态(可选)
checkTriggerStatus();
} catch (SchedulerException e) {
log.error("【备份调度器】调度器初始化失败", e);
}
}
/**
* 调度所有备份任务
* 1. 获取所有唯一的备份任务组
* 2. 遍历并逐个调度具体的设备备份任务
*/
public void scheduleAllBackupTasks() throws SchedulerException {
log.info("开始调度所有备份任务,当前时间: {}", System.currentTimeMillis());
try {
// 对单个备份任务进行调度
scheduleSingleBackupTask(config);
} catch (Exception e) {
log.error("【任务调度失败】任务组: {}, 异常信息: {}", backupName, e.getMessage(), e);
}
}
/**
* 调度单个备份任务(含防重校验)
*/
private void scheduleSingleBackupTask(BackupConfig config) throws SchedulerException {
log.info("开始调度设备IP为: {} 的备份任务", config.getManagementIp());
String jobId = "job-" + config.getId();
String triggerId = "trigger-" + config.getId();
JobKey jobKey = new JobKey(jobId);
TriggerKey triggerKey = new TriggerKey(triggerId);
try {
// 1. 防止重复添加任务
if (scheduler.checkExists(jobKey)) {
log.info("【跳过】任务 {} 已存在,不再重复调度。", jobId);
return;
}
// 2. 创建 JobDetail,并通过 JobDataMap 传递业务参数
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("backupTask", JSONUtil.toJsonStr(config));
JobDetail job = JobBuilder.newJob(DynamicBackupJob.class)
.withIdentity(jobKey)
.setJobData(jobDataMap)
.build();
// 3. 创建 Trigger,绑定 Cron 表达式
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(config.getCronExpression()))
.build();
// 4. 注册到调度器
scheduler.scheduleJob(job, trigger);
log.info("【成功调度】任务ID: {}, Cron: {}", config.getId(), config.getCronExpression());
} catch (Exception e) {
log.error("【任务调度失败】任务ID: {}, 异常: {}", config.getId(), e.getMessage(), e);
throw e;
}
}
/**
* 检查触发器状态(排查异常状态的触发器)
*/
public void checkTriggerStatus() throws SchedulerException {
List<String> triggerGroups = scheduler.getTriggerGroupNames();
for (String group : triggerGroups) {
Collection<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(group));
for (TriggerKey triggerKey : triggerKeys) {
Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
if (state != Trigger.TriggerState.NORMAL) {
log.warn("触发器 {} 状态异常: {}", triggerKey.getName(), state);
}
}
}
}
/**
* 清除所有已注册的备份任务
* 彻底删除作业与触发器,防止服务重启后任务堆积
*/
public void clearAllScheduledJobs() throws SchedulerException {
// 1. 删除所有触发器
for (String triggerGroupName : scheduler.getTriggerGroupNames()) {
GroupMatcher<TriggerKey> triggerGroupMatcher = GroupMatcher.triggerGroupEquals(triggerGroupName);
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(triggerGroupMatcher);
for (TriggerKey triggerKey : triggerKeys) {
scheduler.pauseTrigger(triggerKey);
boolean triggerDeleted = scheduler.unscheduleJob(triggerKey);
if (triggerDeleted) {
log.info("已删除触发器: {}(组:{})", triggerKey.getName(), triggerKey.getGroup());
}
}
}
// 2. 删除所有作业
for (String jobGroupName : scheduler.getJobGroupNames()) {
GroupMatcher<JobKey> jobGroupMatcher = GroupMatcher.jobGroupEquals(jobGroupName);
Set<JobKey> jobKeys = scheduler.getJobKeys(jobGroupMatcher);
for (JobKey jobKey : jobKeys) {
scheduler.pauseJob(jobKey);
boolean jobDeleted = scheduler.deleteJob(jobKey);
if (jobDeleted) {
log.info("已删除作业: {}(组:{})", jobKey.getName(), jobKey.getGroup());
}
}
}
log.info("所有备份任务清理完成");
}
}
6.当然,也可以对任务进行增删改查,开放成api接口
比如: // 既然新增了备份任务配置,那么需要向Quartz,注册,并调度这个任务
java
/**
* 调度单个备份任务(防残留、防重复,确保新任务生效)
*/
private void scheduleSingleBackupTask(DeviceBackupConfigPO config) throws SchedulerException {
Long taskId = config.getId();
String jobId = "job-" + taskId;
String triggerId = "trigger-" + taskId;
JobKey jobKey = new JobKey(jobId);
TriggerKey triggerKey = new TriggerKey(triggerId);
try {
// 额外清理:防止极端情况下的残留(触发器/作业未删干净)
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey);
LogUtil.info(log, "scheduleSingleBackupTask", "【清理残留】触发器:{}(任务ID:{})", triggerId, taskId);
}
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
LogUtil.info(log, "scheduleSingleBackupTask", "【清理残留】作业:{}(任务ID:{})", jobId, taskId);
}
// 1. 构建JobDetail(携带任务参数)
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("backupTask", JSONUtil.toJsonStr(config)); // 任务参数转JSON(避免序列化问题)
JobDetail jobDetail = JobBuilder.newJob(DynamicBackupJob.class) // 你的任务执行类
.withIdentity(jobKey)
.setJobData(jobDataMap)
.requestRecovery(true) // 服务重启后恢复任务
.build();
// 2. 构建Trigger(基于新Cron)
CronScheduleBuilder cronSchedule = CronScheduleBuilder.cronSchedule(config.getCronExpression())
.withMisfireHandlingInstructionDoNothing(); // 错过执行时不补执行(根据业务调整)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(cronSchedule)
.build();
// 3. 调度任务
scheduler.scheduleJob(jobDetail, trigger);
LogUtil.info(log, "scheduleSingleBackupTask",
"【调度成功】任务ID:{},管理IP:{},Cron:{}",
taskId, config.getManagementIp(), config.getCronExpression());
} catch (Exception e) {
String errorMsg = String.format("【调度失败】任务ID:%s,异常:%s", taskId, e.getMessage());
LogUtil.error(log, "scheduleSingleBackupTask", errorMsg, e);
throw new SchedulerException(errorMsg, e);
}
}
如果删除了一个任务,
java
@Override
public boolean deleteBackupConfig(List<Long> ids) {
try {
List<NetworkDeviceBackupConfigPO> list = networkDeviceBackupConfigService.listByIds(ids);
for (NetworkDeviceBackupConfigPO po : list) {
String jobId = "job-" + po.getId();
String triggerId = "trigger-" + po.getId();
JobKey jobKey = new JobKey(jobId);
TriggerKey triggerKey = new TriggerKey(triggerId);
// 检查并删除触发器
if (scheduler.checkExists(triggerKey)) {
scheduler.unscheduleJob(triggerKey); // 删除触发器
LogUtil.info(log, "deleteBackupConfig", "【成功删除】触发器: {}", triggerId);
} else {
LogUtil.info(log, "deleteBackupConfig", "【跳过删除】未找到对应的触发器ID: {}", triggerId);
}
// 检查并删除作业
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey); // 删除作业
LogUtil.info(log, "deleteBackupConfig", "【成功删除】作业: {}", jobId);
} else {
LogUtil.info(log, "deleteBackupConfig", "【跳过删除】未找到对应的作业ID: {}", jobId);
}
}
return networkDeviceBackupConfigService.removeBatchByIds(ids);
} catch (Exception e) {
LogUtil.error(log, "deleteBackupConfig", "deleteBackupConfig error:{}", e);
throw new BusinessException("删除备份任务配置出现错误:" + e.getMessage());
}
}
如果是编辑某个任务,那就先删,后增