Quartz - 定时任务框架集成
参考了若依框架,将quartz定时任务框架集成到自己的项目当中。
目录
- 一、Quartz概述
- 二、库表创建
- 1.Quartz关键表(11张)
- 表SQL
- 2.自定义业务表(2张)
- 表SQL
- 三、代码示例
- 1.依赖引入
- 2.类文件
- 1)定时任务配置类
- 2)定时任务工具类
- 3)定时任务调度类
- 4)定时任务业务类
- 四、难题
- 参考文章
一、Quartz概述
Quartz是一个开源的、功能强大的、可插拔的任务调度框架,能在Java应用程序中实现定时任务的创建和执行,广泛应用于各种需要定时或周期性执行人物的场景。
Quartz的主要特点:
- 灵活性:支持多种触发器类型(延迟触发、固定时间间隔触发、基于日历的复杂触发模式),允许开发者根据需求定制任务的执行频率和时间。
- 持久性:可以将任务和触发器的状态保存在数据库中,即使应用重启,也能回复之前的状态继续执行任务。
- 集群支持:在集群环境中,可以保证任务的正确分发和执行,避免重复执行或漏执行的情况。
- 插件架构:采用插件架构涉及,使得扩展和定制变得容易。开发者可以根据需要添加自定义的插件来增加架构的功能。
- 跨平台: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
{@Beanpublic 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);// 设置自动启动,默认为truefactory.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表达式构建一个新的triggerCronTrigger 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";@Autowiredprivate ISysJobService jobService;@RequiresPermissions("monitor:job:view")@GetMapping()public String job(){return prefix + "/job";}@RequiresPermissions("monitor:job:list")@PostMapping("/list")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic boolean checkCronExpressionIsValid(SysJob job){return jobService.checkCronExpressionIsValid(job.getCronExpression());}/*** Cron表达式在线生成*/@GetMapping("/cron")public String cron(){return prefix + "/cron";}/*** 查询cron表达式近5次的执行时间*/@GetMapping("/queryCronExpression")@ResponseBodypublic 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+多库
相关文章:
Quartz - 定时任务框架集成
参考了若依框架,将quartz定时任务框架集成到自己的项目当中。 目录 一、Quartz概述二、库表创建1.Quartz关键表(11张)表SQL 2.自定义业务表(2张)表SQL 三、代码示例1.依赖引入2.类文件1)定时任务配置类2&am…...
GoModule
GOPATH 最早的就是GOPATH构建模式, go get下载的包都在path中的src目录下 src目录是源代码存放目录。 package mainimport ("net/http""github.com/gorilla/mux" )func main() {r : mux.NewRouter()r.HandleFunc("/hello", func(w h…...
SQL - 数据库管理
保障数据库安全的用户账户和权限问题,当在工作环境中使用MySQL的时候,我们需要创建其他用户账户,并赋予它们特定权限。创建一个用户 create user wolf127.0.0.1 identified by 1234; create user wolf127.0.0.1 identified by 1234;-- 无 …...
密码学之AES算法
文章目录 1. AES简介1.1 AES算法的历史背景1.2 AES算法的应用领域 2. AES加解密流程图2. AES算法原理2.1 AES加密过程2.2 AES解密过程 1. AES简介 1.1 AES算法的历史背景 AES算法,全称为Advanced Encryption Standard(高级加密标准)&#x…...
GitHub每日最火火火项目(8.20)
项目名称:goauthentik / authentik 项目介绍:authentik 是一款提供认证功能的工具,它就像是一个强大的粘合剂,能够满足您在认证方面的各种需求。无论是在安全验证、用户身份管理还是访问控制等方面,它都能发挥重要作用…...
(五)Flink Sink 数据输出
经过上面的 Transformation 操作之后,最终形成用户所需要的结果数据集。通常情况下,用户希望将结果数据输出到外部存储介质或者传输到下游的消息中间件中,在 Flink 中,将 DataStream 数据输出到外部系统的过程被定义为 Sink 操作。 目录 (一)基本数据输出 (二)第三方…...
Spring 注入、注解及相关概念补充
一、Spring DI 的理解 DI ( Dependency Inject,中文释义:依赖注入)是对 IOC 概念不同角度的描述,是指应用程序在运行时,每一个 bean 对象都依赖 IOC 容器注入到当前 bean 对象所需要的另一个 bean 对象。(例如…...
【Linux多线程】线程安全的单例模式
文章目录 1. 单例模式 与 设计模式1.1 单例模式1.2 设计模式1.3 饿汉实现模式 与 懒汉实现模式1.4 饿汉模式① 饿汉模式的特点② 饿汉式单例模式的实现③ 饿汉式单例模式的优缺点④ 适用场景 1.5 懒汉模式① 懒汉式单例模式的特点② 懒汉式单例模式的实现③ 懒汉式单例模式的优…...
基于jqury和canvas画板技术五子棋游戏设计与实现(论文+源码)_kaic
摘 要 网络五子棋游戏如今面临着一些新的挑战和机遇。一方面,网络游戏需要考虑到网络延迟和带宽等因素,保证游戏的实时性和稳定性。另一方面,网络游戏需要考虑到游戏的可玩性和趣味性,以吸引更多的玩家参与。本文基于HTML5和Canv…...
指针 (四)
一 . 指针的使用和传值调用 (1)strlen 的模拟实现 库函数 strlen 的功能是求字符串长度,统计的是字符串中 \0 之前的字符个数,函数原格式如下: 我们的参数 str 接收到一个字符串的起始地址,然后开始统计…...
便利店(超市)管理系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家,服务很多代码文档,百分百好评,战绩可查!!入职于互联网大厂,可以交流,共同进步。有保障的售后 代码参考数据库参…...
Excel中的“块”操作
在Excel中,有offset、index、indirect三个对“区域”操作的函数,是较高版本Excel中“块”操作的利器。 (笔记模板由python脚本于2024年08月20日 19:25:21创建,本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Pytho…...
yolo V8训练 长条状目标
1、说明 目标数据集合中有很多长条状图片,如果直接Resize 会严重拉伸,因此采用把长条图像裁剪成2段,然后将裁剪后的2段图片拼接在一起。 2、代码 2.1 C 代码 (部署,模型推理时C ) #include <stdio.h…...
数据结构与算法 - 设计
1. LRU缓存 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值࿰…...
62 网络设备的暗藏的操控者SNMP
一 SNMP 简介 SNMP(Simple Network Management Protocol,简单网络管理协议)广泛用于网络设备的远程管理和操作。SNMP允许管理员通过NMS对网络上不同厂商、不同物理特性、采用不同互联技术的设备进行管理,包括状态监控、数据采集和故障处理。 二 SNMP 网络架构 NMS(Netwo…...
华硕飞行堡垒键盘全部失灵【除电源键】
华硕飞行堡垒FX53VD键盘全部失灵【除电源键】 前言一、故障排查二、发现问题三、使用方法总结 前言 版本型号: 型号 ASUS FX53VD(华硕-飞行堡垒) 板号:GL553VD 故障情况描述: 键盘无法使用,键盘除开机键外…...
前端字符串将其分割成长度为 32 的子字符串数组
技巧分享:将字符串切割后,对list数据进行数据处理 要实现这个需求,可以编写一个简单的 JavaScript 函数来处理字符串并将其分割成长度为 32 的子字符串数组。下面是一个具体的实现示例: function splitStringIntoChunks(str) {l…...
小学二年级数学精选试题
小学二年级数学精选试题...
练习题 - 探索正则表达式re功能
在编程的世界里,正则表达式(Regular Expression, 简称re)是一种强大的工具,它能帮助我们有效地处理文本数据。从简单的查找到复杂的字符串操作,正则表达式都能轻松应对。特别是在数据清理、文本分析以及自动化处理等场景中,正则表达式更是不可或缺的利器。本篇文章将深入…...
【Oracle 11G 配置使用教程1】
Oracle11G配置使用教程1 引言图像方式创建数据库一、打开 Database Configuration Assistant二、创建数据库操作三、选择数据库模版四、配置数据库标识五、配置数据库标识六、创建数据库 配置监听一、打开创建监听程序二、打开创建监听程序三、重新配置监听程序四、选择监听程序…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
