参考了若依框架,将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
{
@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+多库