本文做一个记录,使用Spring Boot + Nacos + Quartz 开发一个任务调度中心,由于是公司开发,代码就不上传GitHub了。有时间再重新做一个。
工程结构:
一个client,一个serve
工程创建:
创建一个Spring Boot 工程,在创建时Server 服务加入Quartz 依赖,Client 不需要添加(SpringBoot 已经封装好的)
这里贴下我的pom.xml文件
syiti-task
com.syiti.dev
1.0.0
4.0.0
任务调度服务接口
syiti-task-server
org.springframework.boot
spring-boot-starter-quartz
org.springframework.boot
spring-boot-starter-test
test
com.syiti.dev
component-server-starter
com.syiti.dev
component-bus
1.0.0
com.syiti.dev
syiti-task-client
1.0.0
org.springframework.boot
spring-boot-maven-plugin
修改完pom文件后,修改bootstrap.yaml文件,由于集成了Nacos ,这里就只有Nacos配置信息,上代码:
server:
port: 8234
spring:
application:
name: ${artifactId}
profiles:
active: dev
cloud:
bus:
id: ${spring.application.name}
nacos:
discovery:
server-addr: ${NACOS-HOST:127.0.0.1}:${NACOS-PORT:8848}
namespace: a8028df1-5018-4ea6-b0ac-38c8510db20f
config:
file-extension: yaml
server-addr: ${spring.cloud.nacos.discovery.server-addr}
shared-dataids: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: a8028df1-5018-4ea6-b0ac-38c8510db20f
在看下Nacos 的配置:
spring:
main:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/cloud-platform?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
type: com.zaxxer.hikari.HikariDataSource
redis:
host: 127.0.0.1
port: 6379
password: 123456
rabbitmq:
host: 127.0.0.1
port: 5672
username: springcloud
password: springcloud
virtual-host: /
publisher-confirms: true
quartz:
job-store-type: jdbc
properties:
org:
quartz:
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
clusterCheckinInterval: 10000
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
isClustered: true
tablePrefix: QRTZ_
useProperties: false
scheduler:
instanceId: AUTO
instanceName: clusteredScheduler
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
关于Quartz 最主要的配置就是上述中quartz 中的配置,加上以上配置,就可以实现任务的持久化(添加任务时将任务插入数据库)并自动读取数据库信息进行定时执行任务。
关于数据库存储的表结构,Quartz的jar包中已经提供了,这里就不再赘述了,自己下下来导入数据库就行了。(其中scheduler.sql 为自行定义的表,不是Quartz 中的表)
链接:https://pan.baidu.com/s/1sejG-AguMp1u-Dsusc1r6g
提取码:wl6g
接下来进行编码
在client 中编写数据表的实体类(使用了Lombok 没有使用这个插件的可自行添加Get/Set)
package com.syiti.dev.task.client.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @author LinYoufeng
* @version 1.0.0
* @description TODO
* @date 2019/9/17 9:16
*/
@TableName("scheduler_job_logs")
@Data
public class SchedulerJobLogs implements Serializable {
@TableId(type = IdType.ID_WORKER)
private Long logId;
/**
* 任务名称
*/
private String jobName;
/**
* 任务组名
*/
private String jobGroup;
/**
* 任务执行类
*/
private String jobClass;
/**
* 任务描述
*/
private String jobDescription;
/**
* 任务触发器
*/
private String triggerClass;
/**
* 任务表达式
*/
private String cronExpression;
/**
* 运行时间
*/
private Long runTime;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 日志创建时间
*/
private Date createTime;
/**
* 任务执行数据
*/
private String jobData;
/**
* 异常
*/
private String exception;
/**
* 状态:0-失败 1-成功
*/
private Integer status;
private static final long serialVersionUID = 1L;
}
package com.syiti.dev.task.client;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* @author LinYoufeng
* @version 1.0.0
* @description 任务详情
* @date 2019/9/17 9:20
*/
@Data
public class TaskInfo implements Serializable {
private static final long serialVersionUID = -7693857859775581311L;
/**
* 增加或修改标识
*/
private int id;
/**
* 任务名称
*/
private String jobName;
/**
* 任务描述
*/
private String jobDescription;
/**
* 任务类名
*/
private String jobClassName;
/**
* 任务分组
*/
private String jobGroupName;
/**
* 任务状态
*/
private String jobStatus;
/**
* 任务类型 SimpleTrigger-简单任务,CronTrigger-表达式
*/
private String jobTrigger;
/**
* 任务表达式
*/
private String cronExpression;
/**
* 创建时间
*/
private Date createTime;
/**
* 间隔时间(毫秒)
*/
private Long milliSeconds;
/**
* 重复次数
*/
private Integer repeatCount;
/**
* 起始时间
*/
private Date startDate;
/**
* 终止时间
*/
private Date endDate;
/**
* 执行数据
*/
private Map data;
}
接下来在Server中编写相应的代码。
首先,oauth2资源服务器配置(没有使用OAuth2的同学可忽略)
package com.syiti.dev.task.server.config;
import com.syiti.dev.component.security.OpenHelper;
import com.syiti.dev.component.security.exception.OpenAccessDeniedHandler;
import com.syiti.dev.component.security.exception.OpenAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
/**
* oauth2资源服务器配置
*
* @author: CaiTi
* @date: 2018/10/23 10:31
* @description:
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private DataSource dataSource;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public RedisTokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public JdbcClientDetailsService clientDetailsService() {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
return jdbcClientDetailsService;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 构建redis获取token服务类
resources.tokenServices(OpenHelper.buildRedisTokenServices(redisConnectionFactory));
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
// 监控端点内部放行
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
// fegin访问或无需身份认证
.antMatchers(
"/job/**"
).permitAll()
.anyRequest().authenticated()
.and()
//认证鉴权错误处理,为了统一异常处理。每个资源服务器都应该加上。
.exceptionHandling()
.accessDeniedHandler(new OpenAccessDeniedHandler())
.authenticationEntryPoint(new OpenAuthenticationEntryPoint())
.and()
.csrf().disable();
}
}
接下来编写API接口
package com.syiti.dev.task.server.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.syiti.dev.component.core.model.ResultBody;
import com.syiti.dev.component.mybatis.PageParams;
import com.syiti.dev.task.client.TaskInfo;
import com.syiti.dev.task.client.entity.SchedulerJobLogs;
import com.syiti.dev.task.server.job.HttpExecuteJob;
import com.syiti.dev.task.server.service.SchedulerJobLogsService;
import com.syiti.dev.task.server.service.SchedulerService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @author LinYoufeng
* @version 1.0.0
* @description TODO
* @date 2019/9/17 9:51
*/
@RestController
public class SchedulerController {
@Autowired
private SchedulerService schedulerService;
@Autowired
private SchedulerJobLogsService schedulerJobLogsService;
/**
* 获取任务执行日志列表
*
* @param map
* @return
*/
@ApiOperation(value = "获取任务执行日志列表", notes = "获取任务执行日志列表")
@GetMapping(value = "/job/logs")
public ResultBody> getJobLogList(@RequestParam(required = false) Map map) {
IPage result = schedulerJobLogsService.findListPage(new PageParams(map));
return ResultBody.ok().data(result);
}
/**
* 获取任务列表
*
* @return
*/
@ApiOperation(value = "获取任务列表", notes = "获取任务列表")
@GetMapping(value = "/job")
public ResultBody> getJobList(@RequestParam(required = false) Map map) {
List list = schedulerService.getJobList();
IPage page = new Page();
page.setRecords(list);
page.setTotal(list.size());
return ResultBody.ok().data(page);
}
/**
* 添加远程调度任务
*
* @param jobName 任务名称
* @param jobDescription 任务描述
* @param cron cron表达式
* @param serviceId 服务名
* @param path 请求路径
* @param method 请求类型
* @param contentType 响应类型
* @return
*/
@ApiOperation(value = "添加远程调度任务", notes = "添加远程调度任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobName", value = "任务名称", required = true, paramType = "form"),
@ApiImplicitParam(name = "jobDescription", value = "任务描述", required = true, paramType = "form"),
@ApiImplicitParam(name = "cron", value = "cron表达式", required = true, paramType = "form"),
@ApiImplicitParam(name = "serviceId", value = "服务名", required = true, paramType = "form"),
@ApiImplicitParam(name = "path", value = "请求路径", required = true, paramType = "form"),
@ApiImplicitParam(name = "method", value = "请求类型", required = false, paramType = "form"),
@ApiImplicitParam(name = "contentType", value = "响应类型", required = false, paramType = "form"),
})
@PostMapping("/job/add/http")
public ResultBody addHttpJob(@RequestParam(name = "jobName") String jobName,
@RequestParam(name = "jobDescription") String jobDescription,
@RequestParam(name = "cron") String cron,
@RequestParam(name = "serviceId") String serviceId,
@RequestParam(name = "path") String path,
@RequestParam(name = "method", required = false) String method,
@RequestParam(name = "contentType", required = false) String contentType
) {
TaskInfo taskInfo = new TaskInfo();
Map data = Maps.newHashMap();
data.put("serviceId", serviceId);
data.put("method", method);
data.put("path", path);
data.put("contentType", contentType);
taskInfo.setData(data);
taskInfo.setJobName(jobName);
taskInfo.setJobDescription(jobDescription);
taskInfo.setJobClassName(HttpExecuteJob.class.getName());
taskInfo.setJobGroupName(Scheduler.DEFAULT_GROUP);
taskInfo.setCronExpression(cron);
schedulerService.addCronJob(taskInfo);
return ResultBody.ok();
}
/**
* 修改远程调度任务
*
* @param jobName 任务名称
* @param jobDescription 任务描述
* @param cron cron表达式
* @param serviceId 服务名
* @param path 请求路径
* @param method 请求类型
* @param contentType 响应类型
* @return
*/
@ApiOperation(value = "修改远程调度任务", notes = "修改远程调度任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobName", value = "任务名称", required = true, paramType = "form"),
@ApiImplicitParam(name = "jobDescription", value = "任务描述", required = true, paramType = "form"),
@ApiImplicitParam(name = "cron", value = "cron表达式", required = true, paramType = "form"),
@ApiImplicitParam(name = "serviceId", value = "服务名", required = true, paramType = "form"),
@ApiImplicitParam(name = "path", value = "请求路径", required = true, paramType = "form"),
@ApiImplicitParam(name = "method", value = "请求类型", required = false, paramType = "form"),
@ApiImplicitParam(name = "contentType", value = "响应类型", required = false, paramType = "form"),
})
@PostMapping("/job/update/http")
public ResultBody updateHttpJob(@RequestParam(name = "jobName") String jobName,
@RequestParam(name = "jobDescription") String jobDescription,
@RequestParam(name = "cron") String cron,
@RequestParam(name = "serviceId") String serviceId,
@RequestParam(name = "path") String path,
@RequestParam(name = "method", required = false) String method,
@RequestParam(name = "contentType", required = false) String contentType
) {
TaskInfo taskInfo = new TaskInfo();
Map data = Maps.newHashMap();
data.put("serviceId", serviceId);
data.put("method", method);
data.put("path", path);
data.put("contentType", contentType);
taskInfo.setData(data);
taskInfo.setJobName(jobName);
taskInfo.setJobDescription(jobDescription);
taskInfo.setJobClassName(HttpExecuteJob.class.getName());
taskInfo.setJobGroupName(Scheduler.DEFAULT_GROUP);
taskInfo.setCronExpression(cron);
schedulerService.editCronJob(taskInfo);
return ResultBody.ok();
}
/**
* 删除任务
*
* @param jobName 任务名称
* @return
*/
@ApiOperation(value = "删除任务", notes = "删除任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobName", value = "任务名称", required = true, paramType = "form")
})
@PostMapping("/job/delete")
public ResultBody deleteJob(@RequestParam(name = "jobName") String jobName) {
schedulerService.deleteJob(jobName, Scheduler.DEFAULT_GROUP);
return ResultBody.ok();
}
/**
* 暂停任务
*
* @param jobName 任务名称
* @return
*/
@ApiOperation(value = "暂停任务", notes = "暂停任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobName", value = "任务名称", required = true, paramType = "form")
})
@PostMapping("/job/pause")
public ResultBody pauseJob(@RequestParam(name = "jobName") String jobName) {
schedulerService.pauseJob(jobName, Scheduler.DEFAULT_GROUP);
return ResultBody.ok();
}
/**
* 恢复任务
*
* @param jobName 任务名称
* @return
*/
@ApiOperation(value = "恢复任务", notes = "恢复任务")
@ApiImplicitParams({
@ApiImplicitParam(name = "jobName", value = "任务名称", required = true, paramType = "form")
})
@PostMapping("/job/resume")
public ResultBody resumeJob(@RequestParam(name = "jobName") String jobName) {
schedulerService.resumeJob(jobName, Scheduler.DEFAULT_GROUP);
return ResultBody.ok();
}
}
然后是service 的实现类
package com.syiti.dev.task.server.service;
import com.google.common.collect.Lists;
import com.syiti.dev.component.security.exception.OpenAlertException;
import com.syiti.dev.task.client.TaskInfo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
/**
* @author LinYoufeng
* @version 1.0.0
* @description TODO
* @date 2019/9/17 9:52
*/
@Slf4j
@Service
public class SchedulerService {
@Autowired
private Scheduler scheduler;
/**
* 获取任务分组名称
*
* @return
*/
public List getJobGroupNames() {
try {
return scheduler.getJobGroupNames();
} catch (SchedulerException e) {
e.printStackTrace();
}
return Lists.newArrayList();
}
/**
* 获取任务列表
*
* @return
*/
public List getJobList() {
List list = new ArrayList<>();
try {
for (String groupJob : getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(groupJob))) {
List extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
String cronExpression = "";
Date createTime = null;
Long milliSeconds = 0L;
Integer repeatCount = 0;
Date startDate = null;
Date endDate = null;
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
cronExpression = cronTrigger.getCronExpression();
} else if (trigger instanceof SimpleTrigger) {
SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
milliSeconds = simpleTrigger.getRepeatInterval();
repeatCount = simpleTrigger.getRepeatCount();
startDate = simpleTrigger.getStartTime();
endDate = simpleTrigger.getEndTime();
}
TaskInfo info = new TaskInfo();
info.setData(jobDetail.getJobDataMap());
info.setJobName(jobKey.getName());
info.setJobGroupName(jobKey.getGroup());
info.setJobClassName(jobDetail.getJobClass().getName());
info.setJobDescription(jobDetail.getDescription());
info.setJobStatus(triggerState.name());
info.setCronExpression(cronExpression);
info.setCreateTime(createTime);
info.setRepeatCount(repeatCount);
info.setStartDate(startDate);
info.setMilliSeconds(milliSeconds);
info.setEndDate(endDate);
list.add(info);
}
}
}
} catch (SchedulerException e) {
e.printStackTrace();
}
return list;
}
/**
* 添加简单任务
*
* @param info
*/
public void addSimpleJob(TaskInfo info) {
String jobName = info.getJobName();
String jobClassName = info.getJobClassName();
String jobGroupName = info.getJobGroupName();
String jobDescription = info.getJobDescription();
Date createTime = new Date();
JobDataMap dataMap = new JobDataMap();
if (info.getData() != null) {
dataMap.putAll(info.getData());
}
dataMap.put("createTime", createTime);
try {
// 触发器的key值
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// job的key值
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
if (checkExists(jobName, jobGroupName)) {
throw new OpenAlertException(String.format("任务已经存在, jobName:[%s],jobGroup:[%s]", jobName, jobGroupName));
}
/* 简单调度 */
SimpleTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(triggerKey)
.startAt(info.getStartDate())
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(info.getMilliSeconds())
.withRepeatCount(info.getRepeatCount()))
.endAt(info.getEndDate()).build();
Class extends Job> clazz = (Class extends Job>) Class
.forName(jobClassName);
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobKey)
.withDescription(jobDescription).usingJobData(dataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException | ClassNotFoundException e) {
throw new OpenAlertException("任务添加失败");
}
}
/**
* 添加cron表达式任务
*
* @param info
*/
public void addCronJob(TaskInfo info) {
String jobName = info.getJobName();
String jobClassName = info.getJobClassName();
String jobGroupName = info.getJobGroupName();
String jobDescription = info.getJobDescription();
String cronExpression = info.getCronExpression();
Date createTime = new Date();
JobDataMap dataMap = new JobDataMap();
if (info.getData() != null) {
dataMap.putAll(info.getData());
}
dataMap.put("createTime", createTime);
try {
if (checkExists(jobName, jobGroupName)) {
throw new OpenAlertException(String.format("任务已经存在, jobName:[%s],jobGroup:[%s]", jobName, jobGroupName));
}
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
CronScheduleBuilder schedBuilder = CronScheduleBuilder
.cronSchedule(cronExpression)
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(schedBuilder).build();
Class extends Job> clazz = (Class extends Job>) Class
.forName(jobClassName);
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobKey)
.withDescription(jobDescription).usingJobData(dataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException | ClassNotFoundException e) {
throw new OpenAlertException("任务添加失败");
}
}
public void editSimpleJob(TaskInfo info) {
String jobName = info.getJobName();
String jobGroupName = info.getJobGroupName();
String jobDescription = info.getJobDescription();
JobDataMap dataMap = new JobDataMap();
if (info.getData() != null) {
dataMap.putAll(info.getData());
}
try {
if (!checkExists(jobName, jobGroupName)) {
throw new OpenAlertException(
String.format("Job不存在, jobName:{%s},jobGroup:{%s}",
jobName, jobGroupName));
}
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
JobKey jobKey = new JobKey(jobName, jobGroupName);
/* 简单调度 */
SimpleTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(triggerKey)
.startAt(info.getStartDate())
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(info.getMilliSeconds())
.withRepeatCount(info.getRepeatCount()))
.endAt(info.getEndDate()).build();
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
jobDetail =jobDetail.getJobBuilder().withDescription(jobDescription).usingJobData(dataMap).build();
HashSet triggerSet = new HashSet<>();
triggerSet.add(trigger);
scheduler.scheduleJob(jobDetail, triggerSet, true);
} catch (SchedulerException e) {
throw new OpenAlertException("任务修改失败");
}
}
/**
* 修改定时任务
*
* @param info
*/
public void editCronJob(TaskInfo info) {
String jobName = info.getJobName();
String jobGroupName = info.getJobGroupName();
String jobDescription = info.getJobDescription();
String cronExpression = info.getCronExpression();
JobDataMap dataMap = new JobDataMap();
if (info.getData() != null) {
dataMap.putAll(info.getData());
}
try {
if (!checkExists(jobName, jobGroupName)) {
throw new OpenAlertException(
String.format("Job不存在, jobName:{%s},jobGroup:{%s}",
jobName, jobGroupName));
}
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
JobKey jobKey = new JobKey(jobName, jobGroupName);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder
.cronSchedule(cronExpression)
.withMisfireHandlingInstructionDoNothing();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(cronScheduleBuilder).build();
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
jobDetail =jobDetail.getJobBuilder().withDescription(jobDescription).usingJobData(dataMap).build();
HashSet triggerSet = new HashSet<>();
triggerSet.add(cronTrigger);
scheduler.scheduleJob(jobDetail, triggerSet, true);
} catch (SchedulerException e) {
throw new OpenAlertException("类名不存在或执行表达式错误");
}
}
/**
* 删除定时任务
*
* @param jobName
* @param jobGroup
*/
public void deleteJob(String jobName, String jobGroup) {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
try {
if (checkExists(jobName, jobGroup)) {
scheduler.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);
}
} catch (SchedulerException e) {
throw new OpenAlertException(e.getMessage());
}
}
/**
* 暂停定时任务
*
* @param jobName
* @param jobGroup
*/
public void pauseJob(String jobName, String jobGroup) {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
try {
if (checkExists(jobName, jobGroup)) {
scheduler.pauseTrigger(triggerKey);
}
} catch (SchedulerException e) {
throw new OpenAlertException(e.getMessage());
}
}
/**
* 恢复暂停任务
*
* @param jobName
* @param jobGroup
*/
public void resumeJob(String jobName, String jobGroup) {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
try {
if (checkExists(jobName, jobGroup)) {
scheduler.resumeTrigger(triggerKey);
}
} catch (SchedulerException e) {
throw new OpenAlertException(e.getMessage());
}
}
/**
* 验证任务是否存在
*
* @param jobName
* @param jobGroup
* @return
* @throws SchedulerException
*/
private boolean checkExists(String jobName, String jobGroup)
throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
return scheduler.checkExists(triggerKey);
}
}
异步日志通知service 这里只贴实现类,接口自行根据实现类补充即可。
package com.syiti.dev.task.server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.syiti.dev.component.mybatis.PageParams;
import com.syiti.dev.task.client.entity.SchedulerJobLogs;
import com.syiti.dev.task.server.mapper.SchedulerJobLogsMapper;
import com.syiti.dev.task.server.service.SchedulerJobLogsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author LinYoufeng
* @version 1.0.0
* @description TODO
* @date 2019/9/17 9:49
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class SchedulerJobLogsServiceImpl implements SchedulerJobLogsService {
@Autowired
private SchedulerJobLogsMapper schedulerJobLogsMapper;
/**
* 分页查询
*
* @param pageParams
* @return
*/
@Override
public IPage findListPage(PageParams pageParams) {
SchedulerJobLogs query = pageParams.mapToObject(SchedulerJobLogs.class);
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.lambda()
.likeRight(ObjectUtils.isNotEmpty(query.getJobName()),SchedulerJobLogs::getJobName, query.getJobName());
return schedulerJobLogsMapper.selectPage(new Page(pageParams.getPage(),pageParams.getLimit()),queryWrapper);
}
/**
* 添加日志
*
* @param log
*/
@Override
public void addLog(SchedulerJobLogs log) {
schedulerJobLogsMapper.insert(log);
}
/**
* 更细日志
*
* @param log
*/
@Override
public void modifyLog(SchedulerJobLogs log) {
schedulerJobLogsMapper.updateById(log);
}
/**
* 根据主键获取日志
*
* @param logId
* @return
*/
@Override
public SchedulerJobLogs getLog(String logId) {
return schedulerJobLogsMapper.selectById(logId);
}
}
异步日志Mapper(继承Mybatis Plus)
package com.syiti.dev.task.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.syiti.dev.task.client.entity.SchedulerJobLogs;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface SchedulerJobLogsMapper extends BaseMapper {
}
接下来就是 Scheduler配置类
package com.syiti.dev.task.server.config;
import com.syiti.dev.task.server.listenter.JobLogsListener;
import com.syiti.dev.task.server.service.SchedulerJobLogsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.web.client.RestTemplate;
/**
* @author LinYoufeng
* @version 1.0.0
* @description Scheduler配置
* @date 2019/9/17 9:05
*/
@Configuration
@Slf4j
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer {
@Autowired
private JobLogsListener jobLogsListener;
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
// 延时5秒启动
schedulerFactoryBean.setStartupDelay(5);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
// 任务执行日志监听
schedulerFactoryBean.setGlobalJobListeners(jobLogsListener);
}
@Bean
public JobLogsListener jobLogsListener(SchedulerJobLogsService schedulerJobLogsService) {
return new JobLogsListener(schedulerJobLogsService);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Job监听器
package com.syiti.dev.task.server.listenter;
import com.alibaba.fastjson.JSONObject;
import com.syiti.dev.component.core.util.DateUtils;
import com.syiti.dev.component.core.util.StringUtils;
import com.syiti.dev.task.client.entity.SchedulerJobLogs;
import com.syiti.dev.task.server.service.SchedulerJobLogsService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import java.util.Date;
/**
* @author LinYoufeng
* @version 1.0.0
* @description 任务调度监听
* @date 2019/9/17 9:07
*/
@Slf4j
public class JobLogsListener implements JobListener {
private SchedulerJobLogsService schedulerJobLogsService;
public JobLogsListener(SchedulerJobLogsService schedulerJobLogsService) {
this.schedulerJobLogsService = schedulerJobLogsService;
}
@Override
public String getName() {
return "JobLogsListener";
}
/**
* 调度前执行
*
* @param job
*/
@Override
public void jobToBeExecuted(JobExecutionContext job) {
}
@Override
public void jobExecutionVetoed(JobExecutionContext job) {
}
/**
* 调度完成或异常时执行
*
* @param job
* @param e
*/
@Override
public void jobWasExecuted(JobExecutionContext job, JobExecutionException e) {
JobDetail detail = job.getJobDetail();
JobDataMap dataMap = detail.getJobDataMap();
String jobName = detail.getKey().getName();
String jobGroup = detail.getKey().getGroup();
String alarmMail = dataMap.getString("alarmMail");
String jobClass = detail.getJobClass().getName();
String description = detail.getDescription();
String exception = null;
String cronExpression = null;
Integer status = 1;
Trigger trigger = job.getTrigger();
String triggerClass = trigger.getClass().getName();
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
cronExpression = cronTrigger.getCronExpression();
}
if (e != null) {
status = 0;
exception = StringUtils.getExceptionToString(e);
if (StringUtils.isNotBlank(alarmMail)) {
String title = String.format("[%s]任务执行异常-%s", jobName, DateUtils.formatDateTime(new Date()));
try {
log.info("执行异常,通知管理员!");
} catch (Exception em) {
log.error("==> send alarmMail error:{}", em);
}
}
}
SchedulerJobLogs jobLog = new SchedulerJobLogs();
jobLog.setJobName(jobName);
jobLog.setJobGroup(jobGroup);
jobLog.setJobClass(jobClass);
jobLog.setJobDescription(description);
jobLog.setRunTime(job.getJobRunTime());
jobLog.setCreateTime(new Date());
jobLog.setCronExpression(cronExpression);
jobLog.setStartTime(job.getFireTime());
jobLog.setTriggerClass(triggerClass);
jobLog.setEndTime(new Date(job.getFireTime().getTime() + job.getJobRunTime()));
jobLog.setJobData(JSONObject.toJSONString(dataMap));
jobLog.setException(exception);
jobLog.setStatus(status);
schedulerJobLogsService.addLog(jobLog);
}
}
接下来就是最核心的Job (本来是想用OAuth2 的客户端登录模式写的,请求时总是说Token 错误了,就换了RestTemplate,后续在琢磨琢磨这个问题)
package com.syiti.dev.task.server.job;
import com.alibaba.fastjson.JSONObject;
import com.syiti.dev.component.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* @author LinYoufeng
* @version 1.0.0
* @description TODO
* @date 2019/9/17 9:37
*/
@Slf4j
public class HttpExecuteJob implements Job {
/**
* 由于微服务间有安全限制,这里使用公共客户端ID发起调度请求
* oauth2 请求模板类
* private OAuth2RestTemplate oAuth2RestTemplate;
*/
/**
* 负载均衡
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String serviceId = dataMap.getString("serviceId");
String method = dataMap.getString("method");
method = StringUtils.isBlank(method) ? "POST" : method;
String path = dataMap.getString("path");
String contentType = dataMap.getString("contentType");
contentType = StringUtils.isBlank(contentType) ? MediaType.APPLICATION_FORM_URLENCODED_VALUE : contentType;
String body = dataMap.getString("body");
ServiceInstance serviceInstance = loadBalancerClient.choose(serviceId);
// 获取服务实例
if (serviceInstance == null) {
throw new RuntimeException(String.format("%s服务暂不可用", serviceId));
}
String url = String.format("%s%s", serviceInstance.getUri(), path);
HttpHeaders headers = new HttpHeaders();
HttpMethod httpMethod = HttpMethod.resolve(method.toUpperCase());
HttpEntity requestEntity = null;
headers.setContentType(MediaType.parseMediaType(contentType));
if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
// json格式
requestEntity = new HttpEntity(body, headers);
} else {
// 表单形式
// 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
MultiValueMap params = new LinkedMultiValueMap();
if (StringUtils.isNotBlank(body)) {
Map data = JSONObject.parseObject(body, Map.class);
params.putAll(data);
requestEntity = new HttpEntity(params, headers);
}
}
log.info("==> url[{}] method[{}] data=[{}]", url, httpMethod, requestEntity);
ResponseEntity result = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
System.out.println(result.getBody());
}
}
以上代码就是远程任务调度中心的代码了。
调试:
添加定时任务:(成功)
查询任务列表:(成功)
定时任务自动执行:(成功)
删除定时任务(成功):