一、前言
后端基于基于SpringBoot框架进行设计。在系统中可以设计定时任务、定时计划两大模块。
定时任务:定义的一些定时的任务。
任务的名称、任务描述、类名或者URL、状态(是否关联)、是否是系统计划、修改人、修改时间等。
定时计划:是真正执行的任务。
(1)每个定时计划可以设定一条或者多条定时任务(触发任务),并进行发布。
(2)需要设置定时计划的触发器(按天、周、月等)。
二、数据库表设计
相关的数据库表设计4个表,分别是定时任务表、定时计划表、定时任务定时计划关联表、定时计划执行记录表。
1、cfg_scheduletask(定时任务表)
字段 | 解释 |
---|---|
IID | 主键 |
ISYSTEM | 系统定义[0:系统级 (系统级不允许删除) 1:自定义级(可以进行删除操作) |
SNAME | 任务名称 |
SCLASSNAME | 存储任务的类名或者URL地址 |
SDESCRIPTION | 任务描述 |
ISTATUS | 状态[0:发布 1:未发布](定时计划中发布后,此状态为0) |
IMODIFIED | 修改人 |
DMODIFIED | 修改时间 |
IFLAG | 标志[0:启用 1:禁用](此任务是否启用,启用的话才可以被定时计划中选择为触发任务) |
2、cfg_scheduleplan(定时计划表)
除了主键、名称、描述、修改人、修改时间、状态[0:发布 1:未发布]、标志[0:启用 1:禁用]字段以外,还包括的关键字段有:
字段 | 解释 |
---|---|
TSTARTTIME | 触发开始时间 |
TENDTIME | 触发结束时间 |
ITRIGGERMODE | 触发方式[1:一次 2:按天 3:按周 4:按月] |
ITRIGGERMODE | 重复间隔 |
IDAYVALUE | 按天间隔 |
SDAYSOFWEEK | 按周间隔[3,5 每周三和周五] |
SMONTHS | 月重复 |
IMONTHTYPE | 月中日重复方式[1:月中的日 2:月末 3:月中的星期] |
SDAYSOFMONTH | 月中日重复 |
IMONTHWEEK | 月中周重复 |
IMONTHWEEKDAY | 月中周重复方式 |
3、cfg_scheduleplantask(定时计划与定时任务关联表)
字段 | 解释 |
---|---|
IID | 主键 |
IPLANID | 计划ID |
ITASKID | 任务ID |
4、cfg_scheduleplan_log(定时任务执行记录表)
记录每一条定时计划中的定时任务的执行情况。
字段 | 解释 |
---|---|
IID | 主键 |
IPLANID | 定时计划ID |
IPLANTASKID | 定时任务ID |
SCONTENT | 任务执行结果描述 |
ISUCCESS | 执行结果,0:成功,1:失败 |
TSTARTTIME | 任务执行开始时间 |
TENDTIME | 任务执行结束时间 |
三、界面设计
可以有两个界面。一个界面对定时任务进行设置。一个界面制定定时计划以及定时计划的发布、关闭、删除等操作。
四、代码设计逻辑
1、每个定时任务需要执行的方法直接写成接口的形式。这样既可以方便前端调用方法,而且不需要写一堆JOB,只需要写一个公共的JOB然后通过传参不同调用不同的接口URL就可以了。
2、用户只需要点击定时计划的发布按钮,直接调用公共方法初始化调度器、查询需要执行的任务。其中JOB传值为一个公共的JOB接口,在里面可以通过LOG日志记录定时任务的执行情况,同时调用一个公共方法:访问URL调用接口方法。
3、所以说就是把所有的JOB集结成一个调用接口的方法,并记录定时计划的执行情况。
五、SpringBoot整合Quartz实现以上功能小案例
5.1 代码设计
首先,项目结构:
1、编写定时任务接口代码,定义两个定时任务接口restA和restB。
package com.mytest.testQuartz.quartz.rest;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 13:27
*/
@RestController
@RequestMapping("/testApi")
public class restA {
@PostMapping("/task01")
public String testGet() {
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(createTime + " task01执行一次!");
return "task01======123456";
}
}
package com.mytest.testQuartz.quartz.rest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 13:27
*/
@RestController
@RequestMapping("/testApi")
public class restB {
@PostMapping("/task02")
public String testGet() {
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(createTime + " task01执行一次!");
return "task02======ABCDEF";
}
}
2、定义公共类quartzManager,定义启动定时计划的方法,传参可以是数据库中存的定时计划的id等,只要可以确定定时任务的具体信息。
package com.mytest.testQuartz.quartz;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 13:26
*/
public class quartzManager {
private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();
/**
* 启动定时任务的方法
* @param url
* @param cron
* @param id
*/
public static void test(String url,String cron, String id) {
//String thistype = "2";//默认按照秒进行,方便测试
Class name = null;// Job类名
// 1、创建调度器Scheduler
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
// 2、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
name = Class.forName("com.mytest.testQuartz.quartz.job.quartzRest");
JobDetail jobDetail1 = JobBuilder.newJob(name)
.withIdentity( id+"job1", id+"group1").build();
jobDetail1.getJobDataMap().put("url",url);
// 3、构建Trigger实例,每隔1s执行一次
CronTrigger cronTrigger1 = TriggerBuilder.newTrigger().withIdentity( id+"trigger1", id+"triggerGroup1")
.usingJobData("trigger1", "这是testJob1的trigger")
.startNow()//立即生效
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
//4、执行
scheduler.scheduleJob(jobDetail1, cronTrigger1);
System.err.println("--------" + " scheduler start ! " + cron + "------------");
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭某定时任务的方法
*/
}
3、定义公共JOB类quartzRest ,所有的定时任务都执行这个job,这个job会根据url地址去调用任务的接口方法。
package com.mytest.testQuartz.quartz.job;
import com.mytest.testQuartz.quartz.service.InterfaceService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.HashMap;
import java.util.Map;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 14:03
*/
public class quartzRest implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String url = context.getJobDetail().getJobDataMap().getString("url");
Map<String, Object> map = new HashMap<>();
//执行的JOB任务就是:调用自己的接口方法!
new InterfaceService().sendData(url, map);
}
}
4、定义公共类InterfaceService,实现调用URL接口的功能。即可以完成对任务接口的调用。
package com.mytest.testQuartz.quartz.service;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 14:21
*/
public class InterfaceService {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
RestTemplate restTemplate;
public String sendData(String url, Map<String, Object> mp) {
restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
JSONObject oparam = new JSONObject(mp);
//log.debug(url + " : " + oparam.toJSONString());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.setAll(mp);
String response = "";
try {
HttpEntity<String> httpEntity = new HttpEntity<>(oparam.toString(), headers);
ResponseEntity<String> response2 = restTemplate.postForEntity(url, httpEntity, String.class);
response = response2.getBody();
//log.debug(url+"的返回值为:=========="+response);
System.out.println(url+"的返回值为:=========="+response);
} catch (Exception e) {
response = "接口[" + url + "]调用异常!" + e.getMessage();
// log.error(response);
}
return response;
}
}
5.2 来测试吧
编写一个测试类myTest。我们测试的时候需要启动整个SpringBoot项目,然后再启动这个测试类来访问本地接口。我的项目端口号是8080。
1、测试类代码(其中定义的参数都可以是前端传的各种参数,真的做系统的话可以直接传定时计划id然后再查需要执行的任务以及详细信息)
package com.mytest.testQuartz.quartz;
/**
* @author zll
* @version 1.0
* @date 2020/7/28 14:04
*/
public class myTest {
private static String urlA = "http://127.0.0.1:8080/testApi/task01";
private static String urlB = "http://127.0.0.1:8080/testApi/task02";
private static String cronA = "*/10 * * * * ?";
private static String cronB = "*/5 * * * * ?";
public static void main(String args[]) {
quartzManager.test(urlA, cronA, "1");
quartzManager.test(urlB, cronB, "2");
}
}
2、启动整个项目。
3、启动测试类myTest 。
4、结果:成功访问到本地的两个接口,并定时执行。