1.新建SpringBoot项目,引入quartz相关依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
2.订单Entity类。业务预实现用户添加订单后根据订单属性项来为其推送热文邮件,这里解释下业务,用户新增订单后,会记录如下各项,其中发送频率字段(frequency)选项包括每天/每周/关闭,这个字段对每一个quartz定时器的cron表达式起了限定作用
/**
* 推送订单
* @author wuyifan
*/
@Data
public class PushOrder implements Serializable {
/**
* 订单id(数据库唯一主键)
*/
private Integer pid;
/**
* 订单名称
*/
private String workOrder;
/**
* 订单关键词
*/
private String keywords;
/**
* 收件人地址(例:[email protected])
*/
private String toAddress;
/**
* 发送频率
*/
private String frequency;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String createDate;
/**
* 更新时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String updateDate;
/**
* 任务名称(以下三个字段用来存储对应定时器信息)
*/
private String jobName;
/**
* 任务分组
*/
private String jobGroup;
/**
* cron表达式
*/
private String cronExpression;
/**
* 用户id
*/
private String uid;
}
先贴出部分quartz工具类
import org.quartz.*;
import org.quartz.Trigger.TriggerState;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 计划任务管理
*
* @author DIY
* 2018年5月16日 下午5:55:52
*/
@Service
public class QuartzManager {
public final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private Scheduler scheduler;
/**
* 添加任务
*
* @param job
*/
public void addJob(ScheduleJob job) throws Exception {
// 创建jobDetail实例,绑定Job实现类
// 指明job的名称,所在组的名称,以及绑定job类
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class)
.withIdentity(job.getJobKey())
//.withIdentity(job.getJobName(), job.getJobGroup()) //和上面2选1,效果一样,获取定时器名称和分组,是识别每一个定时器任务的key
.usingJobData(job.getDataMap()) //这里就可以获取到每个定时器的动态入参实体类,重要!
.build();
// 定义调度触发规则
// 使用cornTrigger规则
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(job.getJobName(), job.getJobGroup())// 触发器key
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.startNow()
.build();
// 把作业和触发器注册到任务调度中
scheduler.scheduleJob(jobDetail, trigger);
// 判断调度器是否启动
if (!scheduler.isStarted()) {
scheduler.start();
}
log.info(String.format("定时任务:%s.%s-已添加到调度器!", job.getJobGroup(), job.getJobName()));
}
/**
* 获取所有计划中的任务列表
*
* @return
*/
public List<ScheduleJob> getAllJob() throws SchedulerException {
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
for (JobKey jobKey : jobKeys) {
ScheduleJob job = ScheduleJob.getJobFromMap(scheduler.getJobDetail(jobKey).getJobDataMap());
jobList.add(job);
}
return jobList;
}
/**
* 所有正在运行的job
*
* @return
*/
public List<ScheduleJob> getRunningJob() throws SchedulerException {
List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
for (JobExecutionContext executingJob : executingJobs) {
ScheduleJob job = ScheduleJob.getJobFromMap(executingJob.getJobDetail().getJobDataMap());
jobList.add(job);
}
return jobList;
}
/**
* 暂停一个job
*
* @param job
*/
public void pauseJob(ScheduleJob job) throws SchedulerException {
if (scheduler.checkExists(job.getJobKey())) {
scheduler.pauseJob(job.getJobKey());
log.info(String.format("定时任务:%s.%s-已暂停!", job.getJobGroup(), job.getJobName()));
} else {
log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
}
}
/**
* 恢复一个job
*
* @param job
*/
public void resumeJob(ScheduleJob job) throws SchedulerException {
if (scheduler.checkExists(job.getJobKey())) {
scheduler.resumeJob(job.getJobKey());
log.info(String.format("定时任务:%s.%s-已重启!", job.getJobGroup(), job.getJobName()));
} else {
log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
}
}
/**
* 删除一个job
*
* @param job
*/
public void deleteJob(ScheduleJob job) throws SchedulerException {
if (scheduler.checkExists(job.getJobKey())) {
scheduler.deleteJob(job.getJobKey());
log.info(String.format("定时任务:%s.%s-已删除!", job.getJobGroup(), job.getJobName()));
} else {
log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
}
}
/**
* 立即执行job
*
* @param job
*/
public void runAJobNow(ScheduleJob job) throws SchedulerException {
if (scheduler.checkExists(job.getJobKey())) {
scheduler.triggerJob(job.getJobKey());
log.info(String.format("定时任务:%s.%s-立即启动!", job.getJobGroup(), job.getJobName()));
} else {
log.debug("Failed pause exist DynamicJob {}, because not fount JobKey [{}]", job, job.getJobKey());
}
}
/**
* 更新job时间表达式
*
* @param job
* @param jobName
* @param jobGroup
* @return
*/
public void updateJobCron(ScheduleJob job, String jobName, String jobGroup) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()).withMisfireHandlingInstructionDoNothing();
trigger = (CronTrigger) trigger.getTriggerBuilder().withIdentity(triggerKey)
.usingJobData(job.getDataMap())
.withSchedule(scheduleBuilder).build();
scheduler.rescheduleJob(triggerKey, trigger);
log.info(String.format("定时任务:%s.%s-更换新的执行时间[" + job.getCronExpression() + "]!", job.getJobGroup(), job.getJobName()));
}
public TriggerState jobIsRun(ScheduleJob job) {
TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
try {
return scheduler.getTriggerState(triggerKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
return null;
}
}
这个是定时器实体类,用来存储定时器的动态配置参数
@Data
@Component
public class ScheduleJob implements Serializable, Job {
@Autowired
private TrackTopicsService trackTopicsService;
private static ScheduleJob scheduleJob;
@PostConstruct
private void init(){
scheduleJob = this;
scheduleJob.trackTopicsService = this.trackTopicsService;
}
private static final long serialVersionUID = 1L;
private static final String JOB_MAP_KEY = "self";
public static final String STATUS_RUNNING = "1";
public static final String STATUS_NOT_RUNNING = "0";
public static final String CONCURRENT_IS = "1";
public static final String CONCURRENT_NOT = "0";
/**
* 任务名称
*/
private String jobName;
/**
* 任务分组
*/
private String jobGroup;
/**
* cron表达式
*/
private String cronExpression;
/**
* 订单id
*/
private Integer pid;
/**
* 用户id
*/
private String uid;
/**
* 订单关键词
*/
private String keywords;
/**
* 收件人
*/
private String toAddress;
/**
* 追踪频率
*/
private String frequency;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String createDate;
/**
* 更新时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private String updateDate;
/**
* 标志位(用来判断是新增还是更新)
*/
private String flag;
@JsonIgnore
private JobDataMap dataMap = new JobDataMap();
public JobDataMap getDataMap() {
if (dataMap.size() == 0) {
dataMap.put(JOB_MAP_KEY, this);
}
return dataMap;
}
public JobKey getJobKey() {
return JobKey.jobKey(jobName, jobGroup);// 任务名称和组构成任务key
}
public static ScheduleJob getJobFromMap(JobDataMap dataMap) {
return (ScheduleJob) dataMap.get(JOB_MAP_KEY);
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//该实现后的方法是定时器的入口
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@Override
@Transactional(rollbackFor = {
Throwable.class})
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("定时器执行:START>>>>>>>>>>>>>>>>>");
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
System.out.println("当前执行JOB:" + dataMap.get(JOB_MAP_KEY));
//每一个转换后的定时器实体类都会获取到执行前设置的参数
ScheduleJob sj = (ScheduleJob) dataMap.get(JOB_MAP_KEY);
//这里将定时要执行的具体任务放到service层集中管理
scheduleJob.trackTopicsService.quartzJob(sj);
System.out.println("定时器执行:OVER>>>>>>>>>>>>>>>>>>");
}
@Override
public String toString() {
return "ScheduleJob{" +
"jobName='" + jobName + '\'' +
", jobGroup='" + jobGroup + '\'' +
", jobStatus='" + jobStatus + '\'' +
", cronExpression='" + cronExpression + '\'' +
", description='" + description + '\'' +
", beanClass='" + beanClass + '\'' +
", isConcurrent='" + isConcurrent + '\'' +
", springBean='" + springBean + '\'' +
", methodName='" + methodName + '\'' +
", pid='" + pid + '\'' +
", keywords='" + keywords + '\'' +
", toAddress='" + toAddress + '\'' +
", frequency='" + frequency + '\'' +
", trackAmount='" + trackAmount + '\'' +
", createDate='" + createDate + '\'' +
", updateDate='" + updateDate + '\'' +
", flag='" + flag + '\'' +
'}';
}
}
3.接下来是Controller,Service,Dao三层,底层sql编写用的Mybatis,是基础的增删改查,现避免文章过于冗长,只放Service层实现代码,具体可至下方拉取gitee主页项目源码
@Service
public class TrackTopicsServiceImpl implements TrackTopicsService {
@Autowired
private TrackTopicsDao trackTopicsDao;
@Autowired
private QuartzManager quartzManager;
@Override
@Transactional(rollbackFor = {
Throwable.class})
public String quartzJob(ScheduleJob sj) {
/**
* 这里是定时器执行具体任务实现
* 我这里主要操作是调取热文获取接口,再调用邮件发送接口,最后将发送记录也保存到数据库,包括定时
* 任务信息,cron表达式,发送成功与否code和msg等字段
*
*/
...
...
}
/**
* 修改订单
*/
@Override
public int editPushOrder(PushOrder po) throws Exception {
..
..
// 任务名称
String jobName = UUID.randomUUID().toString();
// 任务所属分组
String jobGroup = ScheduleJob.class.getName();
//将原定时器删除再添加,本人试过job的更新方法,定时器信息存储至数据库时无法同步更新,固放弃
if (!(p.getJobName().equals("") && p.getJobGroup().equals(""))) {
quartzManager.deleteJob(job);
}
job.setJobName(jobName);
job.setJobGroup(jobGroup);
quartzManager.addJob(job);
poi.setJobName(jobName);
poi.setJobGroup(jobGroup);
trackTopicsDao.editPushOrder(poi);
return count;
}
/**
* 删除订单
*/
@Override
public int delPushOrder(Integer pid) throws Exception {
R r = null;
int count = 0;
//删除订单记录前取出job信息,需操作对应定时任务
List<PushOrder> list = trackTopicsDao.getOwnPushOrders("", String.valueOf(pid));
PushOrder p = list.get(0);
count = trackTopicsDao.delPushOrder(pid);
if (count > 0) {
ScheduleJob sj = new ScheduleJob();
sj.setJobName(p.getJobName());
sj.setJobGroup(p.getJobGroup());
quartzManager.deleteJob(sj);
}
return count;
}
/**
* 添加订单
*/
@Override
public int addPushOrder(PushOrder po) throws Exception {
R r = null;
int count = 0;
Integer pid = 0;
// 任务名称
String jobName = UUID.randomUUID().toString();
// 任务所属分组
String jobGroup = ScheduleJob.class.getName();
po.setJobName(jobName);
po.setJobGroup(jobGroup);
ScheduleJob job = Utils.initScheduleJob(po, "1");
po.setCronExpression(job.getCronExpression());
//在一开始就插入表达式,任务名称等字段是存在一个问题,如果订单生成,定时器还未触发
//更新订单表这些信息时,服务停止或重启,则之前订单的job信息会丢失
count = trackTopicsDao.addPushOrder(po);
//添加订单成功,准备进入定时器初始化
if (count > 0) {
pid = po.getPid();
job.setPid(pid);
quartzManager.addJob(job);
}
return count;
}
@Override
public void runAllJobs() throws Exception {
List<PushOrder> list = trackTopicsDao.getAllJobs();
System.out.println("<<<<<<<<<<<<<<" + "定时器初始化开始" + ">>>>>>>>>>>>>>>");
for (PushOrder po : list) {
ScheduleJob job = new ScheduleJob();
job = Utils.initScheduleJob(po, "3");
quartzManager.addJob(job);
}
System.out.println("<<<<<<<<<<<<<<" + "定时器初始化结束" + ">>>>>>>>>>>>>>>");
}
}
4.再附上启动类配置
@SpringBootApplication
@MapperScan("io.metstr.dao")
@EnableTransactionManagement
@EnableScheduling
public class DevisePartnersApplication implements CommandLineRunner {
@Autowired
private TrackTopicsService trackTopicsService;
public static void main(String[] args) {
SpringApplication.run(DevisePartnersApplication.class, args);
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//该方法是服务启动时,自动启动仍需要被执行的定时器,注意上方要实现CommandLineRunner接口,重要!
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@Override
public void run(String... args) throws Exception {
trackTopicsService.runAllJobs();
}
}