项目中遇到了关于定时任务的需求,在要求实现定时任务的前提下,同时要求对任务进行动态配置,简言之就是可以随时对定时任务进行增删改查,并且能够实时生效。
在之前的多个项目中也做过类似的功能,因为比较简单,通常都是从网上拿过来直接用,一般网上写的都是通过properties配置文件和XML文件进行实现,但是用此种方法无法实现动态配置的需求。在网上找了一些关于动态配置定时任务的文章,结合实际情况,最终实现了该需求。在此记录下来,便于日后再次遇到同样需求,同时也进行下分享,希望能帮助更多的人。
具体的细节此处暂时不说,因为目前也没有时间做特别深入的了解,后面如果有时间会把自己的理解随时更新上来。
废话不多说,具体实现步骤如下:
1、Quartz依赖引入。由于Springboot2.0加入了针对Quartz的自动化配置,省去了很多繁琐的步骤,所以我们将之前准备采用的quartz相关依赖删除,替换为spring-boot-starter-quartz,如下所示:
<!--quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2、初始化Quartz使用的数据库表。每个quartz的版本引入的数据库表数量应该是略有差异的。本文只以自己项目中引入的为准。建库脚本可以在http://www.quartz-scheduler.org/downloads/ 下载对应版本的tar.gz,对应脚本文件在\src\org\quartz\impl\jdbcjobstore目录下可以找到,选择自己数据库的脚本,本项目用到的是oracle数据库,如下所示:
--
-- A hint submitted by a user: Oracle DB MUST be created as "shared" and the
-- job_queue_processes parameter must be greater than 2
-- However, these settings are pretty much standard after any
-- Oracle install, so most users need not worry about this.
--
-- Many other users (including the primary author of Quartz) have had success
-- runing in dedicated mode, so only consider the above as a hint ;-)
--
delete from qrtz_fired_triggers;
delete from qrtz_simple_triggers;
delete from qrtz_simprop_triggers;
delete from qrtz_cron_triggers;
delete from qrtz_blob_triggers;
delete from qrtz_triggers;
delete from qrtz_job_details;
delete from qrtz_calendars;
delete from qrtz_paused_trigger_grps;
delete from qrtz_locks;
delete from qrtz_scheduler_state;
drop table qrtz_calendars;
drop table qrtz_fired_triggers;
drop table qrtz_blob_triggers;
drop table qrtz_cron_triggers;
drop table qrtz_simple_triggers;
drop table qrtz_simprop_triggers;
drop table qrtz_triggers;
drop table qrtz_job_details;
drop table qrtz_paused_trigger_grps;
drop table qrtz_locks;
drop table qrtz_scheduler_state;
CREATE TABLE qrtz_job_details
(
SCHED_NAME VARCHAR2(120) NOT NULL,
JOB_NAME VARCHAR2(200) NOT NULL,
JOB_GROUP VARCHAR2(200) NOT NULL,
DESCRIPTION VARCHAR2(250) NULL,
JOB_CLASS_NAME VARCHAR2(250) NOT NULL,
IS_DURABLE VARCHAR2(1) NOT NULL,
IS_NONCONCURRENT VARCHAR2(1) NOT NULL,
IS_UPDATE_DATA VARCHAR2(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
JOB_DATA BLOB NULL,
CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE qrtz_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
JOB_NAME VARCHAR2(200) NOT NULL,
JOB_GROUP VARCHAR2(200) NOT NULL,
DESCRIPTION VARCHAR2(250) NULL,
NEXT_FIRE_TIME NUMBER(13) NULL,
PREV_FIRE_TIME NUMBER(13) NULL,
PRIORITY NUMBER(13) NULL,
TRIGGER_STATE VARCHAR2(16) NOT NULL,
TRIGGER_TYPE VARCHAR2(8) NOT NULL,
START_TIME NUMBER(13) NOT NULL,
END_TIME NUMBER(13) NULL,
CALENDAR_NAME VARCHAR2(200) NULL,
MISFIRE_INSTR NUMBER(2) NULL,
JOB_DATA BLOB NULL,
CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE qrtz_simple_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
REPEAT_COUNT NUMBER(7) NOT NULL,
REPEAT_INTERVAL NUMBER(12) NOT NULL,
TIMES_TRIGGERED NUMBER(10) NOT NULL,
CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_cron_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
CRON_EXPRESSION VARCHAR2(120) NOT NULL,
TIME_ZONE_ID VARCHAR2(80),
CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_simprop_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
STR_PROP_1 VARCHAR2(512) NULL,
STR_PROP_2 VARCHAR2(512) NULL,
STR_PROP_3 VARCHAR2(512) NULL,
INT_PROP_1 NUMBER(10) NULL,
INT_PROP_2 NUMBER(10) NULL,
LONG_PROP_1 NUMBER(13) NULL,
LONG_PROP_2 NUMBER(13) NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR2(1) NULL,
BOOL_PROP_2 VARCHAR2(1) NULL,
CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_blob_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
BLOB_DATA BLOB NULL,
CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_calendars
(
SCHED_NAME VARCHAR2(120) NOT NULL,
CALENDAR_NAME VARCHAR2(200) NOT NULL,
CALENDAR BLOB NOT NULL,
CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE qrtz_paused_trigger_grps
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_fired_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
ENTRY_ID VARCHAR2(95) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
INSTANCE_NAME VARCHAR2(200) NOT NULL,
FIRED_TIME NUMBER(13) NOT NULL,
SCHED_TIME NUMBER(13) NOT NULL,
PRIORITY NUMBER(13) NOT NULL,
STATE VARCHAR2(16) NOT NULL,
JOB_NAME VARCHAR2(200) NULL,
JOB_GROUP VARCHAR2(200) NULL,
IS_NONCONCURRENT VARCHAR2(1) NULL,
REQUESTS_RECOVERY VARCHAR2(1) NULL,
CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE qrtz_scheduler_state
(
SCHED_NAME VARCHAR2(120) NOT NULL,
INSTANCE_NAME VARCHAR2(200) NOT NULL,
LAST_CHECKIN_TIME NUMBER(13) NOT NULL,
CHECKIN_INTERVAL NUMBER(13) NOT NULL,
CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE qrtz_locks
(
SCHED_NAME VARCHAR2(120) NOT NULL,
LOCK_NAME VARCHAR2(40) NOT NULL,
CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME);
create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP);
create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE);
create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME);
create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP);
3、配置文件编写。可以作为单独配置文件引入,也可直接配置到application.properties中(当然你也可以用yml格式)。本项目直接放到application.properties当中。如下所示:
#######################################quartz设置开始######################################
spring.quartz.job-store-type=jdbc
#使用自己的配置文件
spring.quartz.properties.org.quartz.jobStore.useProperties=false
#默认或是自己改名字都行
spring.quartz.properties.org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果使用集群,instanceId必须唯一,设置成AUTO
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=10
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#存储方式使用JobStoreTX,也就是数据库
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#是否使用集群(如果项目只部署到 一台服务器,就不用了)
spring.quartz.properties.org.quartz.jobStore.isClustered = false
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000
#数据库中quartz表的表名前缀
spring.quartz.properties.org.quartz.jobStore.tablePrefix = QRTZ_
#######################################quartz设置结束######################################
4、由于是动态配置定时任务,还需要自己建一张表,后面会说此表用途,如下所示:
create table T_SCHEDULEJOB
(
JOBNAME VARCHAR2(200) not null,
JOBGROUP VARCHAR2(200) not null,
JOBSTATUS CHAR(1),
CRONEXPRESSION VARCHAR2(100),
REMARK VARCHAR2(200),
JOBTASKTYPE VARCHAR2(2),
APIURL VARCHAR2(100),
PARAMS VARCHAR2(500),
JOBTYPE CHAR(1),
TRIGGERNAME VARCHAR2(200),
TRIGGERGROUP VARCHAR2(200),
ISNOWRUN CHAR(1),
STARTDATE DATE,
ENDDATE DATE,
JOBCLASSNAME VARCHAR2(200),
CREATETIMESTAMP VARCHAR2(20),
UPDATETIMESTAMP VARCHAR2(20),
REMOVETAG CHAR(1)
)
-- Add comments to the table
comment on table T_SCHEDULEJOB
is '定时任务调度';
-- Add comments to the columns
comment on column T_SCHEDULEJOB.JOBNAME
is '任务名称';
comment on column T_SCHEDULEJOB.JOBGROUP
is '任务分组';
comment on column T_SCHEDULEJOB.JOBSTATUS
is '任务状态, 0禁用 1启用';
comment on column T_SCHEDULEJOB.CRONEXPRESSION
is '任务cron表达式';
comment on column T_SCHEDULEJOB.REMARK
is '任务描述';
comment on column T_SCHEDULEJOB.JOBTASKTYPE
is '任务调度类型,0:接口,1:存储过程';
comment on column T_SCHEDULEJOB.APIURL
is '接口地址';
comment on column T_SCHEDULEJOB.PARAMS
is '参数';
comment on column T_SCHEDULEJOB.JOBTYPE
is '任务类型,0:周期性,1:一次性';
comment on column T_SCHEDULEJOB.TRIGGERNAME
is '触发器名字';
comment on column T_SCHEDULEJOB.TRIGGERGROUP
is '触发器分组';
comment on column T_SCHEDULEJOB.ISNOWRUN
is '是否立即运行,0:否,1:是';
comment on column T_SCHEDULEJOB.STARTDATE
is '生效日期';
comment on column T_SCHEDULEJOB.ENDDATE
is '失效日期';
comment on column T_SCHEDULEJOB.JOBCLASSNAME
is '执行类名';
comment on column T_SCHEDULEJOB.CREATETIMESTAMP
is '任务创建时间';
comment on column T_SCHEDULEJOB.UPDATETIMESTAMP
is '任务更新时间';
comment on column T_SCHEDULEJOB.REMOVETAG
is '删除标记';
5、代码层面。
定时任务实体类,如下所示(省略get/set方法):
public class Job extends JobKey implements Serializable {
@ApiModelProperty(value="任务状态, 0禁用 1启用 2删除",name="jobstatus")
private String jobstatus;
@ApiModelProperty(value="任务cron表达式",name="cronexpression")
private String cronexpression;
@ApiModelProperty(value="任务描述",name="remark")
private String remark;
@ApiModelProperty(value="任务调度类型,0:接口,1:存储过程",name="jobtasktype")
private String jobtasktype;
@ApiModelProperty(value="接口地址",name="apiurl")
private String apiurl;
@ApiModelProperty(value="参数",name="params")
private String params;
@ApiModelProperty(value="任务类型,0:周期性,1:一次性",name="jobtype")
private String jobtype;
@ApiModelProperty(value="触发器名字",name="triggername")
private String triggername;
@ApiModelProperty(value="触发器分组",name="triggergroup")
private String triggergroup;
@ApiModelProperty(value="执行类名",name="jobclassname")
private String jobclassname;
@ApiModelProperty(value="是否立即运行,0:否,1:是",name="isnowrun")
private String isnowrun;
@ApiModelProperty(value="任务生效日期",name="startdate")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date startdate;
@ApiModelProperty(value="任务失效日期",name="enddate")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date enddate;
private String createtimestamp;
private String updatetimestamp;
private String removetag;
private static final long serialVersionUID = 1L;
}
public class JobKey extends BaseModel implements Serializable {
@ApiModelProperty(value="任务名称",name="jobname")
private String jobname;
@ApiModelProperty(value="任务分组",name="jobgroup")
private String jobgroup;
private static final long serialVersionUID = 1L;
}
实现层,如下所示:
@Autowired
private Scheduler scheduler;
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = BaseException.class)
public void saveTask(Job record) throws BaseException {
/*
* 1.将任务记录插入T_SCHEDULEJOB 2.将任务交由Scheduler安排触发 3.如果是立即执行,则首先触发一次任务
*/
try {
record.setTriggername("trigger" + record.getJobname());
record.setTriggername(record.getJobgroup());
record.setJobstatus("1");
this.save(record);
} catch (BaseException e) {
throw new BaseException("定时任务插入数据库失败");
}
try {
Class cls = Class.forName(record.getJobclassname());
cls.newInstance();
// 构建job信息
JobDetail job = JobBuilder.newJob(cls).withIdentity(record.getJobname(), record.getJobgroup())
.withDescription(record.getRemark()).build();
// 触发时间点
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(record.getCronexpression());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger" + record.getJobname(), record.getJobgroup()).startNow()
.withSchedule(cronScheduleBuilder).build();
// 传递的参数
job.getJobDataMap().put("jobtasktype", record.getJobtasktype());
job.getJobDataMap().put("jobtype", record.getJobtype());
job.getJobDataMap().put("apiurl", record.getApiurl());
job.getJobDataMap().put("params", record.getParams());
// 交由Scheduler安排触发
scheduler.scheduleJob(job, trigger);
if ("1".equals(record.getIsnowrun())) { // 如果是立即运行则首先触发一次任务
JobKey key = new JobKey(record.getJobname(), record.getJobgroup());
scheduler.triggerJob(key, job.getJobDataMap());
}
} catch (Exception e) {
throw new BaseException("新增任务失败");
}
}
@Override
public void trigger(Job record) throws BaseException {
long temp = System.currentTimeMillis();
try {
Class cls = Class.forName(record.getJobclassname());
cls.newInstance();
// 构建job信息
JobDetail job = JobBuilder.newJob(cls).withIdentity(record.getJobname()+temp, record.getJobgroup())
.withDescription(record.getRemark()).build();
// 触发时间点
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0 0 2 * * ? *");
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger" + record.getJobname()+temp, record.getJobgroup()).startNow()
.withSchedule(cronScheduleBuilder).build();
// 传递的参数
job.getJobDataMap().put("jobtasktype", record.getJobtasktype());
job.getJobDataMap().put("jobtype", record.getJobtype());
job.getJobDataMap().put("apiurl", record.getApiurl());
job.getJobDataMap().put("params", record.getParams());
scheduler.scheduleJob(job, trigger);
JobKey key = new JobKey(record.getJobname()+temp, record.getJobgroup());
scheduler.triggerJob(key, job.getJobDataMap());
} catch (Exception e) {
throw new BaseException("触发任务失败");
}
//此处做延时处理,否则移除任务时偶尔会nullexception
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TriggerKey triggerKey = TriggerKey.triggerKey(record.getJobname()+temp, record.getJobgroup());
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(record.getJobname()+temp, record.getJobgroup()));
logger.info("移除临时触发:" + JobKey.jobKey(record.getJobname()+temp));
} catch (Exception e) {
throw new BaseException("移除临时触发任务失败");
}
}
@Override
public void pause(Job record) throws BaseException {
try {
record.setJobstatus("0");
this.update(record);
// JobKey key = new JobKey(record.getJobname(), record.getJobgroup());
// scheduler.pauseJob(key);
//如果暂停后恢复,会立刻执行一次,所以暂停时先删掉定时任务,恢复是新增
TriggerKey triggerKey = TriggerKey.triggerKey(record.getJobname(), record.getJobgroup());
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(record.getJobname(), record.getJobgroup()));
logger.info("暂停任务:" + JobKey.jobKey(record.getJobname()));
} catch (Exception e) {
throw new BaseException("暂停任务失败");
}
}
@Override
public void resume(Job record) throws BaseException {
try {
record.setJobstatus("1");
this.update(record);
// JobKey key = new JobKey(record.getJobname(), record.getJobgroup());
// scheduler.resumeJob(key);
//新增任务
Class cls = Class.forName(record.getJobclassname());
cls.newInstance();
// 构建job信息
JobDetail job = JobBuilder.newJob(cls).withIdentity(record.getJobname(), record.getJobgroup())
.withDescription(record.getRemark()).build();
// 触发时间点
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(record.getCronexpression());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger" + record.getJobname(), record.getJobgroup()).startNow()
.withSchedule(cronScheduleBuilder).build();
// 传递的参数
job.getJobDataMap().put("jobtasktype", record.getJobtasktype());
job.getJobDataMap().put("jobtype", record.getJobtype());
job.getJobDataMap().put("apiurl", record.getApiurl());
job.getJobDataMap().put("params", record.getParams());
// 交由Scheduler安排触发
scheduler.scheduleJob(job, trigger);
} catch (Exception e) {
throw new BaseException("恢复任务失败");
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = BaseException.class)
public void remove(String jobname, String jobgroup) throws BaseException {
try {
com.chinastock.quartz.model.JobKey jobKey = new com.chinastock.quartz.model.JobKey();
jobKey.setJobname(jobname);
jobKey.setJobgroup(jobgroup);
this.deleteByPrimaryKey(jobKey);
TriggerKey triggerKey = TriggerKey.triggerKey(jobname, jobgroup);
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(jobname, jobgroup));
logger.info("removeJob:" + JobKey.jobKey(jobname));
} catch (Exception e) {
throw new BaseException("移除任务失败");
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = BaseException.class)
public void updateTask(Job record) throws BaseException {
/*
* 1.更新 2.删除任务 新建任务
*/
//更新
try {
this.update(record);
//移除任务
TriggerKey triggerKey = TriggerKey.triggerKey(record.getJobname(), record.getJobgroup());
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(record.getJobname(), record.getJobgroup()));
logger.info("removeJob:" + JobKey.jobKey(record.getJobname()));
//新增任务
Class cls = Class.forName(record.getJobclassname());
cls.newInstance();
// 构建job信息
JobDetail job = JobBuilder.newJob(cls).withIdentity(record.getJobname(), record.getJobgroup())
.withDescription(record.getRemark()).build();
// 触发时间点
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(record.getCronexpression());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger" + record.getJobname(), record.getJobgroup()).startNow()
.withSchedule(cronScheduleBuilder).build();
// 传递的参数
job.getJobDataMap().put("jobtasktype", record.getJobtasktype());
job.getJobDataMap().put("jobtype", record.getJobtype());
job.getJobDataMap().put("apiurl", record.getApiurl());
job.getJobDataMap().put("params", record.getParams());
// 交由Scheduler安排触发
scheduler.scheduleJob(job, trigger);
if ("1".equals(record.getIsnowrun())) { // 如果是立即运行则首先触发一次任务
JobKey key = new JobKey(record.getJobname(), record.getJobgroup());
scheduler.triggerJob(key, job.getJobDataMap());
}
} catch (Exception e) {
throw new BaseException("更新任务失败");
}
}
任务执行类。如下所示:
@Service
public class ApiJob implements Job, Serializable {
public static Logger logger = LoggerFactory.getLogger(ApiJob.class);
@Autowired
private Scheduler scheduler;
@Autowired
private JobMapper mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=BaseException.class)
public void execute(JobExecutionContext context) throws JobExecutionException {
// 取得job详情
JobDetail jobDetail = context.getJobDetail();
// 取得job名称
String jobName = jobDetail.getKey().getName();
String jobgroup = jobDetail.getKey().getGroup();
logger.info("=========定时任务 | 任务分组:" + jobgroup + ",任务名称:" + jobName + ",调用接口服务执行开始=========");
logger.info("定时任务名称: " + jobName);
// 取得job的类
logger.info("定时任务执行类名: " + jobDetail.getJobClass());
// 取得jobgroup名称
logger.info("任务组名: " + jobgroup);
// 取得job开始时间
logger.info(jobName + " 开始时间: " + DateUtils.format(context.getFireTime(), "yyyy-MM-dd HH:mm:ss"));
// 取得job下次触发时间
logger.info("下次执行时间: " + DateUtils.format(context.getNextFireTime(), "yyyy-MM-dd HH:mm:ss"));
JobDataMap dataMap = jobDetail.getJobDataMap();
String jobtasktype = dataMap.getString("jobtasktype");
String jobtype = dataMap.getString("jobtype");
String apiurl = dataMap.getString("apiurl");
String params = dataMap.getString("params");
logger.info("jobtasktype: " + jobtasktype);
logger.info("jobtype: " + jobtype);
logger.info("apiurl: " + apiurl);
logger.info("params: " + params);
String result = HttpUtil.doHttpPost(Constants.SUM_URL+apiurl, params);
logger.info("定时任务接口调用返回结果:" + result);
/*
* 1.判断如果为一次性执行,则执行后,将job状态置为禁用,并移除任务
*/
if("1".equals(jobtype) && null != context.getNextFireTime()) {
com.chinastock.quartz.model.Job job = new com.chinastock.quartz.model.Job();
job.setJobname(jobName);
job.setJobgroup(jobgroup);
job.setJobstatus("0");
int success = mapper.updateByPrimaryKeySelective(job);
if (success <= 0) {
logger.error("任务组:" + jobgroup + ",任务名称:" + jobName + ",移除任务错误{}", job.toString());
throw new JobExecutionException("移除任务失败");
}
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobgroup);
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(jobName, jobgroup));
logger.info("移除任务:" + JobKey.jobKey(jobName));
} catch (Exception e) {
logger.info("任务组:" + jobgroup + ",任务名称:" + jobName + ",执行移除任务失败");
throw new JobExecutionException("移除任务失败");
}
}
logger.info("=========定时任务 | 任务分组:" + jobgroup + ",任务名称:" + jobName + ",调用接口服务执行结束=========");
}
大体上的代码是如此。就不一一贴出来了。
中间遇到几个问题,也说一下。
1、一开始做这个需求的时候,网上找了下,有些说的不是很清楚,加上自己理解也不到位,所以一直认为动态配置是直接修改quartz原有的10几张表。后来突然想明白了,建了一个中间表T_SCHEDULEJOB,目的是通过中间表存储的相关字段为定时任务做维护。看过代码应该可以知道,对定时任务的动态配置,关键是在于jobname、jobgroup、cronexpression(cron表达式)这几个字段,而T_SCHEDULEJOB表的其他字段则是根据需求来定;
2、cron表达式,是直接通过前段的组件直接转换而来,此处就不多说了;
3、关于暂停任务和恢复任务,做到这里的时候,出现个问题,就是如果使用quartz自己的pasueJob和resumeJob,在resumeJob的时候,触发器会立即执行一次任务。这样显然不是我们所愿意看到的,网上找了下资料,说的是在配置触发时间点的时候加上
.withMisfireHandlingInstructionDoNothing
——不触发立即执行
——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
使用此方法后,立即执行的问题确实解决了,可是在本地调试的时候,在重启应用后,却发现,会把停止时间内没有运行的任务补上,比如设置为间隔10秒跑一次,应用重启时间如果是1分钟,那么重启后,系统会把这1分钟里没跑的6次都跑一遍。所以当时一不做二不休,索性在暂停任务时候直接将任务移除,恢复任务时候再重新新增,算是绕了个弯路解决了这个问题;
4、关于立即触发任务,就是新增任务,直接在jobname后加入了时间戳来区分每个新增的任务。新增执行后,做了一个延时处理,避免会出现nullException的问题。
以上就是大致的实现过程及解决方案,欢迎大家讨论指正。