可视化定时任务,quartz集成全解析

简介: 在日常的工作中,定时任务也是一个非常常见的需求,可以使用注解实现,但是使用这种方式有几个问题,首先就是如果修改比较麻烦,而且没有提供特定的页面对定时任务进行可视化管理。所以quartz就应运而生。本文将介绍如何实现springboot与quartz的整合。


在日常的工作中,定时任务也是一个非常常见的需求,可以使用注解实现,但是使用这种方式有几个问题,首先就是如果修改比较麻烦,而且没有提供特定的页面对定时任务进行可视化管理。所以quartz就应运而生。本文将介绍如何实现springboot与quartz的整合。

1.目录结构

image.png

  1. 配置类 配置SchedulerFactory
  2. 固化定时任务和日志的业务类
  3. 定时任务类
  4. 运行配置

2.原理

image.png

  1. scheduler是一个计划调度器容器(总部),容器里面可以盛放众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行。
  2. JobDetail是一个可执行的工作,它本身可能是有状态的。
  3. Trigger代表一个调度参数的配置,什么时候去调。
  4. 当JobDetail和Trigger在scheduler容器上注册后,形成了装配好的作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。
  5. scheduler是个容器,容器中有一个线程池,用来并行调度执行每个作业,这样可以提高容器效率

3.表结构

自带有11张系统表和2张业务表(自建),其中绿色为自己建立的表

image.png

4.整合springboot

1.pom文件

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>

2.ScheduleConfig

配置工厂类的参数。

@Configuration
public class ScheduleConfig {
  @Bean
  public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
    SchedulerFactoryBean factory = new SchedulerFactoryBean();
    factory.setDataSource(dataSource);
    // quartz参数
    Properties prop = new Properties();
    prop.put("org.quartz.scheduler.instanceName", "QUARTZ");
    prop.put("org.quartz.scheduler.instanceId", "AUTO");
    // 线程池配置
    prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
    prop.put("org.quartz.threadPool.threadCount", "20");
    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", "15000");
    prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
    prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
    // sqlserver 启用
    prop.put("org.quartz.jobStore.misfireThreshold", "12000");
    prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
    factory.setQuartzProperties(prop);
    factory.setSchedulerName("QUARTZ");
    // 延时启动
    factory.setStartupDelay(1);
    factory.setApplicationContextSchedulerContextKey("applicationContextKey");
    // 可选,QuartzScheduler
    // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
    factory.setOverwriteExistingJobs(true);
    // 设置自动启动,默认为true
    factory.setAutoStartup(true);
    return factory;
  }
}

3.业务类

主要是为页面提供接口。

@RestController
@RequestMapping("/qrtzJob")
public class QrtzJobController {
    @Autowired
    private QrtzJobService qrtzJobService;
    /**
     * 检查表达式
     * 
     * @param cron
     * @return
     */
    @GetMapping("/check")
    public boolean checkCron(String cron) {
        try {
            return CronExpression.isValidExpression(cron);
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 通过对象查询数据
     *
     * @param qrtzJobVO 请求对象
     * @return 数据集合
     */
    @GetMapping("/selectQrtzJob")
    @ApiOperation(value = "通过实体查询", notes = "通过实体查询")
    public Result<List<QrtzJobVO>>
        selectQrtzJob(@ApiParam(name = "入参对象", value = "实体", required = false) QrtzJobVO qrtzJobVO) {
        Result<List<QrtzJobVO>> resultVO = new Result<>();
        // 业务查询
        List<QrtzJobVO> resultListQrtzJob = qrtzJobService.queryQrtzJobAll(qrtzJobVO);
        resultVO.setData(resultListQrtzJob);
        return resultVO;
    }
    /**
     * 通过对象增加数据
     *
     * @param qrtzJobVO 请求对象
     * @return
     */
    @PostMapping("/createQrtzJob")
    @ApiOperation(value = "通过实体添加", notes = "通过实体添加")
    public Result
        createQrtzJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody QrtzJobVO qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.createScheduleJob(qrtzJobVO);
        return resultVO;
    }
    /**
     * 通过对象修改
     *
     * @param qrtzJobVO 请求对象
     * @return
     */
    @PutMapping("/updateQrtzJob")
    @ApiOperation(value = "通过实体修改", notes = "通过实体修改")
    public Result
        updateQrtzJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody QrtzJobVO qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.updateScheduleJob(qrtzJobVO);
        return resultVO;
    }
    /**
     * 通过对象删除
     *
     * @param qrtzJobVO 请求对象
     * @return
     */
    @DeleteMapping("/deleteQrtzJob")
    @ApiOperation(value = "通过实体删除", notes = "通过实体删除")
    public Result
        deleteQrtzJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody QrtzJobVO qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.deleteScheduleJob(qrtzJobVO);
        return resultVO;
    }
    /**
     * 运行定时任务
     * 
     * @param qrtzJobVO
     * @return
     */
    @GetMapping("/runJob")
    public Result
        runJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody List<QrtzJobVO> qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.run(qrtzJobVO);
        return resultVO;
    }
    /**
     * 暂停定时任务
     * 
     * @param qrtzJobVO
     * @return
     */
    @GetMapping("/pauseJob")
    public Result
        pauseJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody List<QrtzJobVO> qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.pause(qrtzJobVO);
        return resultVO;
    }
    @GetMapping("resumeJob")
    public Result
        resumeJob(@ApiParam(name = "入参对象", value = "实体", required = false) @RequestBody List<QrtzJobVO> qrtzJobVO) {
        Result resultVO = new Result();
        // 业务处理
        qrtzJobService.resume(qrtzJobVO);
        return resultVO;
    }
}

4.运行配置

ScheduleUtils:将新添加的trigger,jobDetail放入scheduler中。

public class ScheduleUtils {
    private static final Logger log = LoggerFactory.getLogger(ScheduleUtils.class);
    private static final String JOB_NAME_PREFIX = "TASK_";
    private static TriggerKey getTriggerKey(Long jobId) {
        return TriggerKey.triggerKey("TASK_" + jobId);
    }
    private static JobKey getJobKey(Long jobId) {
        return JobKey.jobKey("TASK_" + jobId);
    }
    public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
        try {
            return (CronTrigger)scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            log.error("获取Cron表达式失败", e);
            return null;
        }
    }
    public static void createScheduleJob(Scheduler scheduler, QrtzJobVO scheduleJob) {
        try {
            JobDetail jobDetail = JobBuilder.newJob(com.job.util.ScheduleJob.class)
                .withIdentity(getJobKey(scheduleJob.getJobId())).build();
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();
            CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger()
                .withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build();
            jobDetail.getJobDataMap().put("JOB_PARAM_KEY", scheduleJob);
            scheduler.scheduleJob(jobDetail, trigger);
            if (scheduleJob.getStatus().equals(CommonConstant.QUARTZ_PAUSE)) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException e) {
            log.error("创建定时任务失败", e);
        }
    }
    public static void updateScheduleJob(Scheduler scheduler, QrtzJobVO scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();
            CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());
            if (trigger == null) {
                throw new SchedulerException("获取Cron表达式失败");
            }
            trigger =
                (CronTrigger)trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            trigger.getJobDataMap().put("JOB_PARAM_KEY", scheduleJob);
            scheduler.rescheduleJob(triggerKey, trigger);
            if (scheduleJob.getStatus().equals(CommonConstant.QUARTZ_PAUSE)) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException e) {
            log.error("更新定时任务失败", e);
        }
    }
    public static void run(Scheduler scheduler, QrtzJobVO scheduleJob) {
        try {
            JobDataMap dataMap = new JobDataMap();
            dataMap.put("JOB_PARAM_KEY", scheduleJob);
            scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
        } catch (SchedulerException e) {
            log.error("执行定时任务失败", e);
        }
    }
    public static void pauseJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("暂停定时任务失败", e);
        }
    }
    public static void resumeJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("恢复定时任务失败", e);
        }
    }
    public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("删除定时任务失败", e);
        }
    }
}

5.ScheduleJob

用于执行定时任务。

public class ScheduleJob extends QuartzJobBean {
    private static final Logger log = LoggerFactory.getLogger(ScheduleJob.class);
    private ExecutorService service = Executors.newSingleThreadExecutor();
    @Override
    protected void executeInternal(JobExecutionContext context) {
        QrtzJobVO scheduleJob = (QrtzJobVO)context.getMergedJobDataMap().get("JOB_PARAM_KEY");
        QrtzJobLogService scheduleJobLogService = (QrtzJobLogService) SpringContextUtil.getBean(QrtzJobLogService.class);
        QrtzJobLogVO jobLog = new QrtzJobLogVO();
        jobLog.setJobId(scheduleJob.getJobId());
        jobLog.setBeanName(scheduleJob.getBeanName());
        jobLog.setMethodName(scheduleJob.getMethodName());
        if (CommonUtil.isEmpty(scheduleJob.getParams())) {
            jobLog.setParams("");
        } else {
            jobLog.setParams(scheduleJob.getParams());
        }
        long startTime = System.currentTimeMillis();
        try {
            log.info("任务准备执行,任务ID:{}", scheduleJob.getJobId());
            ScheduleRunnable task =
                new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(), scheduleJob.getParams());
            Future<?> future = this.service.submit(task);
            future.get();
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes(Long.valueOf(times));
            jobLog.setStatus("0");
            log.info("任务执行完毕,任务ID:{} 总共耗时:{} 毫秒", scheduleJob.getJobId(), Long.valueOf(times));
        } catch (Exception e) {
            log.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes(Long.valueOf(times));
            jobLog.setStatus("1");
            jobLog.setError(StringUtils.substring(e.toString(), 0, 2000));
        } finally {
            scheduleJobLogService.insertQrtzJobLog(jobLog);
        }
    }
}

6.ScheduleRunnable

通过反射回去需要调用的类与方法。

public class ScheduleRunnable implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(ScheduleRunnable.class);
    private Object target;
    private Method method;
    private String params;
    ScheduleRunnable(String beanName, String methodName, String params)
        throws NoSuchMethodException, SecurityException {
        this.target = SpringContextUtil.getBean(beanName);
        this.params = params;
        if (StringUtils.isNotBlank(params)) {
            this.method = this.target.getClass().getDeclaredMethod(methodName, new Class[] {String.class});
        } else {
            this.method = this.target.getClass().getDeclaredMethod(methodName, new Class[0]);
        }
    }
    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(this.method);
            if (StringUtils.isNotBlank(this.params)) {
                this.method.invoke(this.target, new Object[] {this.params});
            } else {
                this.method.invoke(this.target, new Object[0]);
            }
        } catch (Exception e) {
            log.error("执行定时任务失败", e);
        }
    }
}

5.使用

image.png

调用以上接口 ,参数如下。

   "beanName":"任务类",

    "methodName":"任务方法",

    "params":"参数",

    "cronExpression":"表达式",

image.png


相关文章
|
12月前
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
937 3
|
数据采集 安全 数据管理
深度解析:DataHub的数据集成与管理策略
【10月更文挑战第23天】DataHub 是阿里云推出的一款数据集成与管理平台,旨在帮助企业高效地处理和管理多源异构数据。作为一名已经有一定 DataHub 使用经验的技术人员,我深知其在数据集成与管理方面的强大功能。本文将从个人的角度出发,深入探讨 DataHub 的核心技术、工作原理,以及如何实现多源异构数据的高效集成、数据清洗与转换、数据权限管理和安全控制措施。通过具体的案例分析,展示 DataHub 在解决复杂数据管理问题上的优势。
1751 1
|
机器学习/深度学习 算法 前端开发
集成电路设计:从概念到实现的完整解析优雅草卓伊凡
集成电路设计:从概念到实现的完整解析优雅草卓伊凡
549 2
集成电路设计:从概念到实现的完整解析优雅草卓伊凡
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
1151 2
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
4136 65
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
缓存 安全 API
【深度解析】嵌入式第三方集成的优势、挑战与实现方案(2025版)
嵌入式第三方集成是将外部服务无缝嵌入自身系统的技术方案,通过API/SDK实现功能内嵌(如支付、会议),提升用户体验和开发效率。其核心优势包括操作流畅性、降低研发成本及快速迭代能力,但需解决接口稳定性、数据同步等挑战。实施时需注重架构设计(微服务、安全策略)和性能优化(缓存、异步处理)。未来趋势将向AI服务集成、无代码平台发展,同时安全合规要求更严格。建议选择可靠服务商、遵循最佳实践,并持续监控优化集成方案。
670 2
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
1041 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)。
2835 0
|
机器学习/深度学习 人工智能 自然语言处理
企业级API集成方案:基于阿里云函数计算调用DeepSeek全解析
DeepSeek R1 是一款先进的大规模深度学习模型,专为自然语言处理等复杂任务设计。它具备高效的架构、强大的泛化能力和优化的参数管理,适用于文本生成、智能问答、代码生成和数据分析等领域。阿里云平台提供了高性能计算资源、合规与数据安全、低延迟覆盖和成本效益等优势,支持用户便捷部署和调用 DeepSeek R1 模型,确保快速响应和稳定服务。通过阿里云百炼模型服务,用户可以轻松体验满血版 DeepSeek R1,并享受免费试用和灵活的API调用方式。
1038 12
|
机器学习/深度学习 自然语言处理 监控
智能客服系统集成技术解析和价值点梳理
在 2024 年的智能客服系统领域,合力亿捷等服务商凭借其卓越的技术实力引领潮流,它们均积极应用最新的大模型技术,推动智能客服的进步。
801 7

推荐镜像

更多
  • DNS