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二、创建数据库操作三、选择数据库模版四、配置数据库标识五、配置数据库标识六、创建数据库 配置监听一、打开创建监听程序二、打开创建监听程序三、重新配置监听程序四、选择监听程序…...

【ubuntu24.04】docker pull 配置
Docker 镜像加速器 的方式,看起来不行。阿里云的要先登录。手动拉取tar包的方式,官方dockerhub看起来本身没提供。docker pull 的 代理与 环境变量的代理不同因此,docker pull gitlab/gitlab-ce:17.3.0-ce.0 使用了全局代理也会失败参考官方文档: Use a proxy server with …...

《机器学习》—— 通过下采样方法实现银行贷款分类问题
文章目录 一、什么是下采样方法?二、通过下采样方法实现银行贷款分类问题三、下采样的优缺点 一、什么是下采样方法? 机器学习中的下采样(Undersampling)方法是一种处理不平衡数据集的有效手段,特别是在数据集中某些类…...

Synchronized重量级锁原理和实战(五)
在JVM中,每个对象都关联这一个监视器,这里的对象包含可Object实例和Class实例.监视器是一个同步工具,相当于一个凭证,拿到这个凭证就可以进入临界区执行操作,没有拿到凭证就只能阻塞等待.重量级锁通过监视器的方式保证了任何时间内只允许一个线程通过监视器保护的临界区代码. …...

linux常用网络工具汇总三
linux常用网络工具汇总 6. 抓包工具6.1 wireshark安装界面介绍使用过滤器TCP协议示例关于wireshark的缺点 6.2 tcpdump命令格式关键字使用关于tcpdump的缺点 6.3 fiddler6.4 burpsuite 6. 抓包工具 6.1 wireshark Wireshark(前称Ethereal)是一个网络封…...

Linux中nano编辑器详解
nano 是一个简单的文本编辑器,通常预装在大多数 Linux 发行版中。它非常适合初学者使用,因为它有一个用户友好的界面和易于理解的命令集。下面是对 nano 编辑器的详细说明。 启动 nano 要启动 nano 并打开一个文件进行编辑,你可以在终端中输…...

26-vector arraylist和linkedlist的区别
Vector, ArrayList, 和 LinkedList 是Java中常见的三种列表实现,它们各自具有不同的特点和适用场景。 同步性与线程安全: Vector 是同步的,即线程安全的,它的所有方法都是同步的,可以由两个线程安全地访问…...

20-redis穿透击穿雪崩
Redis中的缓存穿透、缓存击穿和缓存雪崩是三种常见的缓存问题: 缓存穿透:指缓存和数据库中都没有的数据,但用户还是源源不断地发起请求,导致每次请求都会直接访问数据库,从而可能压垮数据库。缓存击穿&…...

Docker使用教程
Docker 名词解释 镜像(image):Docker镜像就是一个模板,可以通过这个模板来创建容器服务。容器(container):Docker利用容器技术,独立运行一个或者一组应用,通过镜像创建…...

poi-tl循环放图片+文字说明
这几天有个任务,服务端导出word要求从数据库取到多张图片,然后输出到word中,并且说明一共几张,当前是第几张。 网上翻了很久也没有找到示例,不过最终难题还是得到了攻克。 因为之前的代码是有一个导出的map,…...

数据结构之树的存储结构
一、顺序存储结构 顺序存储结构通常用于表示完全二叉树。在这种存储方式中,树中的节点被存储在一个连续的数组中。对于完全二叉树,如果父节点的索引是i(假设从0开始计数),那么它的左子节点的索引是2i1,右子…...