Quartz - 定时任务框架集成

news2024/9/21 16:48:11

参考了若依框架,将quartz定时任务框架集成到自己的项目当中。

目录

  • 一、Quartz概述
  • 二、库表创建
    • 1.Quartz关键表(11张)
      • 表SQL
    • 2.自定义业务表(2张)
      • 表SQL
  • 三、代码示例
    • 1.依赖引入
    • 2.类文件
      • 1)定时任务配置类
      • 2)定时任务工具类
      • 3)定时任务调度类
      • 4)定时任务业务类
  • 四、难题
  • 参考文章

一、Quartz概述

Quartz是一个开源的、功能强大的、可插拔的任务调度框架,能在Java应用程序中实现定时任务的创建和执行,广泛应用于各种需要定时或周期性执行人物的场景。

Quartz的主要特点:

  1. 灵活性:支持多种触发器类型(延迟触发、固定时间间隔触发、基于日历的复杂触发模式),允许开发者根据需求定制任务的执行频率和时间。
  2. 持久性:可以将任务和触发器的状态保存在数据库中,即使应用重启,也能回复之前的状态继续执行任务。
  3. 集群支持:在集群环境中,可以保证任务的正确分发和执行,避免重复执行或漏执行的情况。
  4. 插件架构:采用插件架构涉及,使得扩展和定制变得容易。开发者可以根据需要添加自定义的插件来增加架构的功能。
  5. 跨平台:Quartz是纯Java编写的,因此可以在任务支持Java的平台上运行,包括Window、Linux、Unix等。

二、库表创建

1.Quartz关键表(11张)

Quartz在数据库中创建了一系列表来存储任务调度相关的信息,包括job的详细信息、触发器的信息等。

表名备注
qrtz_blob_triggers存储以Blob类型存储的触发器信息表
qrtz_calendars日历信息表,用于指定一个时间范围
qrtz_cron_triggers存储以Cron类型存储的触发器信息表,Cron触发器则根据Cron表达式来触发
qrtz_fired_triggers存储已触发的触发器表
qrtz_job_details任务详细信息表,这个表存储了每个Job的详细信息,包括Job的名称、描述、组名、类名等。这些信息是调度器调度Job的基础。
qrtz_locks存储的悲观锁信息表(如果使用了悲观锁)
qrtz_paused_trigger_grps存储暂停掉的触发器信息表表
qrtz_scheduler_state存储调度器状态表
qrtz_simple_triggers存储简单触发器的信息表,简单触发器在指定的间隔后触发
qrtz_simprop_triggers同步机制的行锁表
qrtz_triggers存储触发器详细信息表,包括触发器的名称、组名、触发器的类型、开始时间、结束时间等。

表SQL

CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);


CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);


2.自定义业务表(2张)

通过自定义业务表,操作定时任务框架的执行状态和记录定时任务执行日志。

表名备注
sys_cron_job定时任务调度表
sys_cron_job_log定时任务调度日志表

表SQL

CREATE TABLE `sys_job` (
  `job_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID',
  `job_name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',
  `job_group` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',
  `invoke_target` varchar(500) COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串',
  `cron_expression` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'cron执行表达式',
  `misfire_policy` varchar(20) COLLATE utf8mb4_general_ci DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
  `concurrent` char(1) COLLATE utf8mb4_general_ci DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)',
  `status` char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '状态(0正常 1暂停)',
  `create_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '备注信息',
  PRIMARY KEY (`job_id`,`job_name`,`job_group`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务调度表';

CREATE TABLE `sys_job_log` (
  `job_log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务日志ID',
  `job_name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称',
  `job_group` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名',
  `invoke_target` varchar(500) COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串',
  `job_message` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '日志信息',
  `status` char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '执行状态(0正常 1失败)',
  `exception_info` varchar(2000) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '异常信息',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`job_log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务调度日志表';

三、代码示例

1.依赖引入

<!-- quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.3</version>
        </dependency>

2.类文件

需要将ruoyi-quartz中的以下所有类文件移植到自己的项目中
在这里插入图片描述

1)定时任务配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * 定时任务配置(单机部署建议默认走内存,如需集群需要创建qrtz数据库表/打开类注释)
 *
 * @author ruoyi
 */
@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", "RuoyiScheduler");
        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.springframework.scheduling.quartz.LocalDataSourceJobStore");
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

        // sqlserver 启用
        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        factory.setQuartzProperties(prop);
		// 修改为对应名称
        factory.setSchedulerName("RuoyiScheduler");
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // 可选,QuartzScheduler
        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        // 设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

2)定时任务工具类

import cn.ffcs.up.rbac.constants.ScheduleConstants;
import cn.ffcs.up.rbac.entity.TsCronJob;

import cn.ffcs.up.rbac.job.TaskException;

import org.quartz.*;

/**
 * 定时任务工具类
 * 
 * @author ruoyi
 *
 */
public class ScheduleUtils
{

    /**
     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
     */
    public static final String[] JOB_WHITELIST_STR = { "cn.ffcs.up.rbac.task" };

    /**
     * 定时任务违规的字符
     */
    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
            "org.springframework", "org.apache" };

    /**
     * 得到quartz任务类
     *
     * @param sysJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(TsCronJob sysJob)
    {
        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }

    /**
     * 构建任务触发对象
     */
    public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
    {
        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 构建任务键对象
     */
    public static JobKey getJobKey(Long jobId, String jobGroup)
    {
        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, TsCronJob job) throws SchedulerException, TaskException
    {
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
                .withSchedule(cronScheduleBuilder).build();

        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
        // 分布式数据库需要给trigger的jobdata设置默认值
        trigger.getJobDataMap().put(ScheduleConstants.TRIGGER_DEFAULT, "TRIGGER_DEFAULT");

        // 判断是否存在
        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(jobId, jobGroup));
        }

        // 判断任务是否过期
        if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
        {
            // 执行调度任务
            scheduler.scheduleJob(jobDetail, trigger);
        }

        // 暂停任务
        if (job.getExecutionStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
        {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
    }

    /**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(TsCronJob job, CronScheduleBuilder cb)
            throws TaskException
    {
        switch (job.getMisfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
                        + "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);
        }
    }

    /**
     * 检查包名是否为白名单配置
     * 
     * @param invokeTarget 目标字符串
     * @return 结果
     */
    public static boolean whiteList(String invokeTarget)
    {
        String packageName = StringUtils.substringBefore(invokeTarget, "(");
        int count = StringUtils.countMatches(packageName, ".");
        if (count > 1)
        {
            return StringUtils.containsAnyIgnoreCase(invokeTarget, JOB_WHITELIST_STR);
        }
        Object obj = CloudAdminUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
        String beanPackageName = obj.getClass().getPackage().getName();
        return StringUtils.containsAnyIgnoreCase(beanPackageName, JOB_WHITELIST_STR)
                && !StringUtils.containsAnyIgnoreCase(beanPackageName, JOB_ERROR_STR);
    }
}

3)定时任务调度类

import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;

/**
 * 定时任务调度测试
 * 
 * @author ruoyi
 */
@Component("ryTask")
public class RyTask
{
    public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
    {
        System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
    }

    public void ryParams(String params)
    {
        System.out.println("执行有参方法:" + params);
    }

    public void ryNoParams()
    {
        System.out.println("执行无参方法");
    }
}

4)定时任务业务类

import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.service.ISysJobService;
import com.ruoyi.quartz.util.CronUtils;
import com.ruoyi.quartz.util.ScheduleUtils;

/**
 * 调度任务信息操作处理
 * 
 * @author ruoyi
 */
@Controller
@RequestMapping("/monitor/job")
public class SysJobController extends BaseController
{
    private String prefix = "monitor/job";

    @Autowired
    private ISysJobService jobService;

    @RequiresPermissions("monitor:job:view")
    @GetMapping()
    public String job()
    {
        return prefix + "/job";
    }

    @RequiresPermissions("monitor:job:list")
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list(SysJob job)
    {
        startPage();
        List<SysJob> list = jobService.selectJobList(job);
        return getDataTable(list);
    }

    @Log(title = "定时任务", businessType = BusinessType.EXPORT)
    @RequiresPermissions("monitor:job:export")
    @PostMapping("/export")
    @ResponseBody
    public AjaxResult export(SysJob job)
    {
        List<SysJob> list = jobService.selectJobList(job);
        ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class);
        return util.exportExcel(list, "定时任务");
    }

    @Log(title = "定时任务", businessType = BusinessType.DELETE)
    @RequiresPermissions("monitor:job:remove")
    @PostMapping("/remove")
    @ResponseBody
    public AjaxResult remove(String ids) throws SchedulerException
    {
        jobService.deleteJobByIds(ids);
        return success();
    }

    @RequiresPermissions("monitor:job:detail")
    @GetMapping("/detail/{jobId}")
    public String detail(@PathVariable("jobId") Long jobId, ModelMap mmap)
    {
        mmap.put("name", "job");
        mmap.put("job", jobService.selectJobById(jobId));
        return prefix + "/detail";
    }

    /**
     * 任务调度状态修改
     */
    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
    @RequiresPermissions("monitor:job:changeStatus")
    @PostMapping("/changeStatus")
    @ResponseBody
    public AjaxResult changeStatus(SysJob job) throws SchedulerException
    {
        SysJob newJob = jobService.selectJobById(job.getJobId());
        newJob.setStatus(job.getStatus());
        return toAjax(jobService.changeStatus(newJob));
    }

    /**
     * 任务调度立即执行一次
     */
    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
    @RequiresPermissions("monitor:job:changeStatus")
    @PostMapping("/run")
    @ResponseBody
    public AjaxResult run(SysJob job) throws SchedulerException
    {
        boolean result = jobService.run(job);
        return result ? success() : error("任务不存在或已过期!");
    }

    /**
     * 新增调度
     */
    @GetMapping("/add")
    public String add()
    {
        return prefix + "/add";
    }

    /**
     * 新增保存调度
     */
    @Log(title = "定时任务", businessType = BusinessType.INSERT)
//    @RequiresPermissions("monitor:job:add")
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(@Validated @RequestBody SysJob job) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(job.getCronExpression()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
        }
        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
        }
        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
        }
        job.setCreateBy("0");
        return toAjax(jobService.insertJob(job));
    }

    /**
     * 修改调度
     */
    @RequiresPermissions("monitor:job:edit")
    @GetMapping("/edit/{jobId}")
    public String edit(@PathVariable("jobId") Long jobId, ModelMap mmap)
    {
        mmap.put("job", jobService.selectJobById(jobId));
        return prefix + "/edit";
    }

    /**
     * 修改保存调度
     */
    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
    @RequiresPermissions("monitor:job:edit")
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(@Validated SysJob job) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(job.getCronExpression()))
        {
            return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确");
        }
        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
        {
            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
        {
            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
        {
            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
        {
            return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
        }
        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
        {
            return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
        }
        return toAjax(jobService.updateJob(job));
    }

    /**
     * 校验cron表达式是否有效
     */
    @PostMapping("/checkCronExpressionIsValid")
    @ResponseBody
    public boolean checkCronExpressionIsValid(SysJob job)
    {
        return jobService.checkCronExpressionIsValid(job.getCronExpression());
    }

    /**
     * Cron表达式在线生成
     */
    @GetMapping("/cron")
    public String cron()
    {
        return prefix + "/cron";
    }

    /**
     * 查询cron表达式近5次的执行时间
     */
    @GetMapping("/queryCronExpression")
    @ResponseBody
    public AjaxResult queryCronExpression(@RequestParam(value = "cronExpression", required = false) String cronExpression)
    {
        if (jobService.checkCronExpressionIsValid(cronExpression))
        {
            List<String> dateList = CronUtils.getRecentTriggerTime(cronExpression);
            return success(dateList);
        }
        else
        {
            return error("表达式无效");
        }
    }
}

四、难题

在通过接口创建定时任务时,有碰到一个报错:Unknown column ‘0x’ in ‘field list’。

在这里插入图片描述
经过debug确认是插入qrtz_triggers表时报错的。

INSERT INTO QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, JOB_NAME, JOB_GROUP, DESCRIPTION, NEXT_FIRE_TIME, PREV_FIRE_TIME, TRIGGER_STATE, TRIGGER_TYPE, START_TIME, END_TIME, CALENDAR_NAME, MISFIRE_INSTR, JOB_DATA, PRIORITY) VALUES('CustomerScheduler', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

原本以为是字段类型的问题,尝试更改了数据格式,但是没有生效。经过排查,发现是是qrtz_trigger表中的job_data插入的数据为空导致的报错。
在这里插入图片描述
因为博主的项目的数据库是分布式数据库,会导致插入报错。于是在执行调度任务前,给job_data字段默认设置了值,不是空就不会出现0x,可以添加成功了。

 // 分布式数据库需要给trigger的jobdata设置默认值
        trigger.getJobDataMap().put(ScheduleConstants.TRIGGER_DEFAULT, "TRIGGER_DEFAULT");

参考文章

Ruoyi-Cloud
【疑难】Unknown column ‘0x’ in ‘field list’
Springboot+Quartz+druid+多库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2054677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

优优嗨聚集团:餐饮合作新未来引领美食产业新风尚

在快速变化的21世纪&#xff0c;餐饮行业作为民生消费的重要组成部分&#xff0c;正经历着前所未有的变革与挑战。随着消费者需求的多元化、个性化以及科技的不断进步&#xff0c;餐饮合作的新模式正悄然兴起&#xff0c;为行业带来了前所未有的发展机遇与活力。本文将探讨餐饮…

如何复现Github上的项目以及conda的常用操作指令

在GitHub上关于深度学习的项目代码通常包含多种类型的文件&#xff0c;每种文件都有其特定的作用。以下是一些常见的文件及其作用的概述&#xff1a; 一个常用的项目结构如下&#xff1a; --project_name/ &#xff1a;项目名----data/&#xff1a;数据集--------__init__.py…

vue+elementui 主题配色修改-打造个性化配色系统

上一期中利用global.css来覆盖elementui原有的配色&#xff0c;修改了按钮和消息框。这一期继续尝试修改其他的控件。 1 修改info 类型按钮 上次修改了primary按钮&#xff0c;这次修改一下info按钮&#xff0c;在global.css中添加 .el-button--info {background-color: #d9d…

deepspeed的并行模式介绍笔记

1.整体框架 2.并行模式 1.数据并行DDP 数据切分以后&#xff0c;分开单张卡训练得到参数&#xff0c;然后综合在单卡计算。 要点&#xff1a;前向计算和反向计算两步骤走并汇总。 1.前向计算 需要留一块主卡一定空间用于综合。 2.反向传播 利用前向传播的汇总参数得到各个…

深度学习基础—超参数调试

1.超参数调试顺序 在训练深度网络最难的事情之一是超参数的选择&#xff0c;如何选择合适的超参数取值&#xff1f;下面我将谈谈&#xff0c;如下是我所理解的超参数调试顺序&#xff1a; 重要性排序 超参数 Top1梯队 学习率a Top2梯队 min-batch大小&#xff0c;隐层神经…

10 VS Code 调试技巧之逐断点、逐过程、单步调试与单步跳出

目录 1 断点调试 1.1 断点调试介绍 1.2 如何设置断点 1.3 如何开启调试 2 调试类型 2.1 逐断点调试 2.2 逐过程调试 2.3 单步调试 2.4 单步跳出 1 断点调试 遇到难以捉摸的软件错误时&#xff0c;老练的程序员会推荐断点调试。通过设置断点&#xff0c;逐步跟踪…

nvidia jetson 系列开发板交叉编译方法,CUDA依赖程序

资源 Toolchain Information jetson-linux jetpack 文章目录 资源1 方案1 qemu-aarch64-static和docker 容器编译jetson2 方案2 模拟器交叉编译器2.1 应对库缺失的情况&#xff0c;进行环境准备2.1.1 模拟器(方案1)2.1.2 在jetson上面进行安装&#xff08;方案2&#xff09;2.…

如何有效清理宝塔控制面板中的垃圾文件与优化系统性能

宝塔控制面板&#xff08;BT-Panel&#xff09;作为一款流行的服务器管理软件&#xff0c;极大地简化了Linux服务器的管理任务&#xff0c;包括网站部署、数据库管理、文件操作等。然而&#xff0c;随着服务器运行时间的增长&#xff0c;系统中会积累各种临时文件、日志文件、缓…

STM32G474按钮输入和点灯

在获取到工程模板后&#xff0c;学习某个CPU的第一步通常都是IO口操作。因此按钮输入和点灯&#xff0c;就是本次学习的第一个程序。先从简单入手。 和GPIO操作有关的函数如下: __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟 __HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIOB时钟 _…

Redis7基础篇(四)

Redis管道 引入 set k1 v1.......需要往返三次 这是一个比较消耗性能的一件事情 怎么就可以一次性的将这些命令执行 就是使用mset这个方法 这个mset就相当于一个管道 把这些命令做成一个流水线的形式进行处理 解决思路 案例 两个set类型的三个哈希类型的一个list类型的 我们要…

知乎信息流广告效果如何?与其他信息流广告平台有何区别?

广告的有效触达与高效转化成为了品牌营销的核心挑战&#xff0c;知乎作为国内最大的知识分享平台&#xff0c;其信息流广告以其独特的优势脱颖而出&#xff0c;成为众多企业首选的营销工具&#xff0c;云衔科技助力企业实现高效知乎广告投放与代运营服务。 一、知乎信息流广告…

基于STM32+手机APP设计的智能停车场系统——程序源码原理图设计原理设计文档演示视频框图等(文末工程资料下载)

基于STM32手机APP设计的智能停车场系统 演示视频 基于STM32手机APP设计的智能停车场系统 元器件&#xff1a;DHT11、MQ2、STM32F103C8T6、SG90舵机、RC522频射模块、HC-SR04超声波模块、OLED、wifi模块、LED灯、蜂鸣器 功能简介 1、进出停车场时需要刷卡&#xff0c;进行一个…

深度学习-----------------------含并行连结的网络GoogLeNet

目录 含并行连结的网络GoogLeNet最好的卷积层超参数inception块inception 结构inception原始结构inception 降维11卷积核的降维功能GoogLeNet段1&2段3段4&5 Inception 的各种变种Inception V3块&#xff0c;段3Inception V3块&#xff0c;段4Inception V3块&#xff0…

突破FPGA限制:TS-M4i系列数字化仪利用GPU加速实现高效块平均处理

一、应用背景 块或分段内存平均模式常用于在不同应用当中&#xff0c;移除信号中不相干的噪声。不管是哪家的数字化仪制造商&#xff0c;几乎所有基于FPGA实现的块平均模式都会受到块或者段内存大小的限。该限制一般取决于FPGA的容量&#xff0c;最大样品量通常在32k到500k之间…

RS232(旧协议)与RS485(新协议)

RS232: RS485: RS485和RS232是两种常见的串行通信标准&#xff0c;它们在通信距离、速度、拓扑结构等方面存在显著差异。以下是它们的主要区别&#xff1a; 1. 物理层接口 RS232: 使用单端信号传输&#xff0c;即信号通过一根信号线和一根公共地线&#xff08;GND&#xff09…

外呼触发通知发送闪信(mod_cti基于FreeSWITCH)

文章目录 前言联系我们手动外呼配置方法例子一&#xff1a;接收到180或183时触发闪信发送例子二&#xff1a;挂断后触发闪信发送 自动外呼配置方法例子&#xff1a;接收到180或183时触发闪信发送 前言 在呼叫中心中间件中&#xff0c;自动外呼触发闪信发送&#xff0c;我们可以…

电销机器人引领电销变革

以前电销都是都是通过盲打&#xff0c;现在有了电话机器人的出现&#xff0c;为电销公司带来新的篇章。 我们都知道外呼中心的人员离职率始终居高不下&#xff0c;人员的培训频繁成本很高&#xff0c;外部电话水平参差不齐&#xff0c;服务态度不够稳定等问题&#xff0c;都是难…

【硬件模块】震动传感器模块

震动传感器模块实物图 DO&#xff1a;数字信号量输出&#xff0c;接单片机管脚&#xff1b; AO&#xff1a;模拟输出&#xff0c;无效&#xff0c;一般不接。 无震动&#xff0c;DO输出高电平&#xff0c;信号指示灯灭&#xff1b; 有震动&#xff0c;DO输出低电平&#xff0c;…

50道深度NLP和人工智能领域面试题+答案

编者按&#xff1a;分享一个很硬核的免费人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 可以当故事来看&#xff0c;轻松学习。 什么是自然语言处理&#xff08;NLP&#xff09;&#xff1f;自然语言处理是一种人工智能领域&#xff0c;致力于使计算机…

letcode 分类练习 BST 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先(自底向上的典型例子)

letcode 分类练习 BST 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先 BST530.二叉搜索树的最小绝对差501.二叉搜索树中的众数236. 二叉树的最近公共祖先 BST 重要性质&#xff1a;它的中序遍历是一个有序数组 530.二叉搜索树的最小绝对差 BS…