基于Spring Boot + Quartz + Nacos 开发任务调度中心

本文做一个记录,使用Spring Boot + Nacos + Quartz 开发一个任务调度中心,由于是公司开发,代码就不上传GitHub了。有时间再重新做一个。

工程结构:
一个client,一个serve
在这里插入图片描述在这里插入图片描述
工程创建:
创建一个Spring Boot 工程,在创建时Server 服务加入Quartz 依赖,Client 不需要添加(SpringBoot 已经封装好的)
在这里插入图片描述
这里贴下我的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<parent>
		<artifactId>syiti-task</artifactId>
		<groupId>com.syiti.dev</groupId>
		<version>1.0.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<description>任务调度服务接口</description>
	<artifactId>syiti-task-server</artifactId>

	<dependencies>
		<!-- Srping Boot Quartz -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-quartz</artifactId>
		</dependency>
		<!-- Spring Boot Test -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- 私服Jar 一些常用的依赖,如mybatis plus,Lombok,redis等等 可自行添加 -->
		<dependency>
			<groupId>com.syiti.dev</groupId>
			<artifactId>component-server-starter</artifactId>
		</dependency>
		<!-- 微服务 周边组件 消息总线 私服Jar-->
		<dependency>
			<groupId>com.syiti.dev</groupId>
			<artifactId>component-bus</artifactId>
			<version>1.0.0</version>
		</dependency>
		<!-- 引入Client -->
		<dependency>
			<groupId>com.syiti.dev</groupId>
			<artifactId>syiti-task-client</artifactId>
			<version>1.0.0</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

修改完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<IPage<SchedulerJobLogs>> getJobLogList(@RequestParam(required = false) Map map) {
		IPage<SchedulerJobLogs> result = schedulerJobLogsService.findListPage(new PageParams(map));
		return ResultBody.ok().data(result);
	}

	/**
	 * 获取任务列表
	 *
	 * @return
	 */
	@ApiOperation(value = "获取任务列表", notes = "获取任务列表")
	@GetMapping(value = "/job")
	public ResultBody<IPage<TaskInfo>> getJobList(@RequestParam(required = false) Map map) {
		List<TaskInfo> 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<String> getJobGroupNames() {
		try {
			return scheduler.getJobGroupNames();
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
		return Lists.newArrayList();
	}

	/**
	 * 获取任务列表
	 *
	 * @return
	 */
	public List<TaskInfo> getJobList() {
		List<TaskInfo> list = new ArrayList<>();
		try {
			for (String groupJob : getJobGroupNames()) {
				for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>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<Trigger> 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<Trigger> 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<SchedulerJobLogs> findListPage(PageParams pageParams) {
		SchedulerJobLogs query = pageParams.mapToObject(SchedulerJobLogs.class);
		QueryWrapper<SchedulerJobLogs> 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<SchedulerJobLogs> {
}

接下来就是 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<String, String> 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<String> result = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
		System.out.println(result.getBody());
	}
}

以上代码就是远程任务调度中心的代码了。
调试:
添加定时任务:(成功)
在这里插入图片描述
查询任务列表:(成功)
在这里插入图片描述
定时任务自动执行:(成功)
在这里插入图片描述
删除定时任务(成功):
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度,实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务的调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析QuartzSpring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架和框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。
使用Spring BootQuartz实现定时任务管理,可以让你更方便地管理和监控你的定时任务。下面是一个使用Spring BootQuartzSpring MVC实现定时任务管理的示例: 首先,在你的Spring Boot应用程序中添加QuartzSpring MVC的依赖项: ```xml <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 然后,在你的Spring Boot应用程序中创建一个Quartz的调度器,并添加一个Spring MVC的Controller来管理定时任务: ```java @Configuration public class QuartzConfig { @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setTriggers(myTaskTrigger().getObject()); return schedulerFactoryBean; } @Bean public JobDetailFactoryBean myTaskJob() { JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean(); jobDetailFactoryBean.setJobClass(MyTask.class); return jobDetailFactoryBean; } @Bean public CronTriggerFactoryBean myTaskTrigger() { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); cronTriggerFactoryBean.setJobDetail(myTaskJob().getObject()); cronTriggerFactoryBean.setCronExpression("0/5 * * * * ?"); // 每5秒执行一次 return cronTriggerFactoryBean; } } @RestController public class TaskController { @Autowired private Scheduler scheduler; @PostMapping("/tasks") public void addTask(@RequestBody TaskInfo taskInfo) throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob(taskInfo.getJobClass()) .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroup()) .build(); CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(taskInfo.getTriggerName(), taskInfo.getTriggerGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCronExpression())) .build(); scheduler.scheduleJob(jobDetail, trigger); } @DeleteMapping("/tasks/{jobName}/{jobGroup}") public void deleteTask(@PathVariable String jobName, @PathVariable String jobGroup) throws SchedulerException { JobKey jobKey = new JobKey(jobName, jobGroup); scheduler.deleteJob(jobKey); } } ``` 在上面的代码中,我们创建了一个Spring MVC的Controller来管理定时任务。我们使用了Scheduler类来添加和删除定时任务。在添加定时任务时,我们使用了TaskInfo类来封装定时任务的信息。在删除定时任务时,我们使用了jobName和jobGroup来识别定时任务。 最后,在你的定时任务类中实现Job接口,并在类上添加@DisallowConcurrentExecution注解,以确保每个任务执行时只有一个实例: ```java @Component @DisallowConcurrentExecution public class MyTask implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 定时任务执行的代码 } } ``` 以上就是使用Spring BootQuartzSpring MVC实现定时任务管理的示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值