diff --git a/magic-boot-ui/src/views/quartz/index.vue b/magic-boot-ui/src/views/quartz/index.vue new file mode 100644 index 0000000..a6cbff7 --- /dev/null +++ b/magic-boot-ui/src/views/quartz/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/magic-boot/pom.xml b/magic-boot/pom.xml index 5f1ded3..467f269 100644 --- a/magic-boot/pom.xml +++ b/magic-boot/pom.xml @@ -56,6 +56,15 @@ org.springframework.boot spring-boot-starter-quartz + + org.springframework.boot + spring-boot-starter-logging + + + org.projectlombok + lombok + true + diff --git a/magic-boot/src/main/java/org/ssssssss/magicboot/form/AddJobForm.java b/magic-boot/src/main/java/org/ssssssss/magicboot/form/AddJobForm.java new file mode 100644 index 0000000..263d5f6 --- /dev/null +++ b/magic-boot/src/main/java/org/ssssssss/magicboot/form/AddJobForm.java @@ -0,0 +1,54 @@ +package org.ssssssss.magicboot.form; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +/** + * 新增定时任务 + */ +@Getter +@Setter +public class AddJobForm { + + /** + * 任务名称 + */ + private String jName; + /** + * 任务组 + */ + private String jGroup; + /** + * 触发器名称 + */ + private String tName; + /** + * 触发器组 + */ + private String tGroup; + /** + * cron表达式 + */ + private String cron; + /** + * 执行MagicAPI中的接口,原始内容,不包含code以及message信息 + * Params: + * method – 请求方法 + */ + private String method; + /** + * 执行MagicAPI中的接口,原始内容,不包含code以及message信息 + * Params: + * path – 请求路径 + */ + private String path; + /** + * 执行MagicAPI中的接口,原始内容,不包含code以及message信息 + * Params: + * context – 变量信息 + */ + private Map context; +} diff --git a/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/MagicApiJob.java b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/MagicApiJob.java new file mode 100644 index 0000000..504a4da --- /dev/null +++ b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/MagicApiJob.java @@ -0,0 +1,62 @@ +package org.ssssssss.magicboot.quartz; + +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; +import org.ssssssss.magicapi.provider.MagicAPIService; + +import java.util.Date; +import java.util.HashMap; +import java.util.Optional; + +/** + * 基于magic-api接口的定时任务 + */ +@Slf4j +@Component +public class MagicApiJob extends QuartzJobBean { + + @Autowired + private MagicAPIService magicAPIService; + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + + /** + * 这是一个接口任务 + * 需要执行magic-api接口的代码 + * 首先你需要配置好magic-api接口,得到接口的请求方式、请求地址、请求参数 + * 然后这个job会调用在java层调用magic-api的java代码去执行 + * 这样一个完成的基于magic-api的定时任务接口就完成了 + */ + + // 获取之前保存的参数 + JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); + // 取出请求方式与路径 + String path = jobDataMap.getString("path"); + String method = jobDataMap.getString("method"); + Optional.ofNullable(path).orElseThrow(()->new RuntimeException("path 不能为空")); + Optional.ofNullable(method).orElseThrow(()->new RuntimeException("method 不能为空")); + + // 移除请求方式与路径,剩下的都是参数 + jobDataMap.remove("path"); + jobDataMap.remove("method"); + + /** + * + * 执行MagicAPI中的接口,原始内容,不包含code以及message信息 + * Params: + * method – 请求方法 + * path – 请求路径 + * context – 变量信息 + */ + log.info("开始执行magicApi定时任务"); + magicAPIService.execute(method,path,jobDataMap); + log.info("结束执行magicApi定时任务"); + } +} diff --git a/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksController.java b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksController.java new file mode 100644 index 0000000..5aaa33c --- /dev/null +++ b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksController.java @@ -0,0 +1,78 @@ +package org.ssssssss.magicboot.quartz; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.ssssssss.magicboot.form.AddJobForm; +import org.ssssssss.magicboot.vo.Result; + +/** + * 定时任务控制器 + */ +@Slf4j +@Controller +@RequestMapping("/job") +public class ScheduledTasksController { + + @Autowired + private ScheduledTasksService scheduledTasksService; + + /** + * 获取所有运行中的任务 + * @return + */ + @ResponseBody + @RequestMapping("/getAllJob") + public Result getAllJob() { + return scheduledTasksService.getAllJob(); + } + /** + * 新增一个定时任务 + * @param form + * @return + */ + @ResponseBody + @RequestMapping("/addJob") + public Result addJob(AddJobForm form) { + scheduledTasksService.addJob(form); + return Result.isSuccess("添加定时任务 成功"); + } + + /** + * 暂停定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + @ResponseBody + @RequestMapping("/pauseJob") + public Result pauseJob(String jName, String jGroup) { + scheduledTasksService.pauseJob(jName, jGroup); + return Result.isSuccess("暂停定时任务 成功"); + } + + /** + * 继续定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + @ResponseBody + @RequestMapping("/resumeJob") + public Result resumeJob(String jName, String jGroup) { + scheduledTasksService.resumeJob(jName, jGroup); + return Result.isSuccess("继续定时任务 成功"); + } + + /** + * 删除定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + @ResponseBody + @RequestMapping("/resumeJob") + public Result deleteJob(String jName, String jGroup) { + scheduledTasksService.deleteJob(jName, jGroup); + return Result.isSuccess("删除定时任务 成功"); + } +} diff --git a/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksService.java b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksService.java new file mode 100644 index 0000000..66431c7 --- /dev/null +++ b/magic-boot/src/main/java/org/ssssssss/magicboot/quartz/ScheduledTasksService.java @@ -0,0 +1,135 @@ +package org.ssssssss.magicboot.quartz; + +import org.quartz.*; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.ssssssss.magicboot.form.AddJobForm; +import org.ssssssss.magicboot.vo.Result; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + + +/** + * 定时任务业务控制层 + */ +@Service +public class ScheduledTasksService { + + @Autowired + private Scheduler scheduler; + + /** + * 获取所有运行中的任务 + * @return + */ + public Result getAllJob(){ + try { + List formList = new ArrayList<>(); + AddJobForm form = null; + for (TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.anyGroup())) { + ///通过triggerKey在scheduler中获取trigger对象 + CronTrigger trigger = (CronTrigger)scheduler.getTrigger(triggerKey); + JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey()); + + form = new AddJobForm(); + + form.setMethod(jobDetail.getJobDataMap().getString("method")); + form.setPath(jobDetail.getJobDataMap().getString("path")); + + jobDetail.getJobDataMap().remove("method"); + jobDetail.getJobDataMap().remove("path"); + + form.setContext(jobDetail.getJobDataMap()); + form.setJName(jobDetail.getKey().getName()); + form.setJGroup(jobDetail.getKey().getGroup()); + + form.setTName(trigger.getJobKey().getName()); + form.setTGroup(trigger.getJobKey().getGroup()); + + formList.add(form); + } + return Result.isSuccess(form); + + } catch (SchedulerException e) { + e.printStackTrace(); + return Result.isBad("获取所有任务失败:{}",e.getMessage()); + } + } + + /** + * 新增一个定时任务 + */ + public void addJob(AddJobForm form) { + Optional.ofNullable(form).orElseThrow(()->new RuntimeException("参数 不能为空")); + Optional.ofNullable(form.getPath()).orElseThrow(()->new RuntimeException("path 不能为空")); + Optional.ofNullable(form.getMethod()).orElseThrow(()->new RuntimeException("method 不能为空")); + Optional.ofNullable(form.getJName()).orElseThrow(()->new RuntimeException("任务名称 不能为空")); + Optional.ofNullable(form.getTName()).orElseThrow(()->new RuntimeException("触发器名称 不能为空")); + Optional.ofNullable(form.getCron()).orElseThrow(()->new RuntimeException("cron表达式 不能为空")); + + try { + // 携带参数供执行job时调用 + JobDataMap newJobDataMap = new JobDataMap(); + newJobDataMap.put("method",form.getMethod()); + newJobDataMap.put("path",form.getPath()); + newJobDataMap.putAll(form.getContext()); + + JobDetail jobDetail = JobBuilder.newJob(MagicApiJob.class) + .withIdentity(form.getJName(), form.getJGroup()) + .setJobData(newJobDataMap) + .build(); + CronTrigger trigger = TriggerBuilder.newTrigger() + .withIdentity(form.getTName(), form.getTGroup()) + .usingJobData(newJobDataMap) + .startNow() + .withSchedule(CronScheduleBuilder.cronSchedule(form.getCron())) + .build(); + scheduler.start(); + scheduler.scheduleJob(jobDetail, trigger); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 暂停定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + public void pauseJob(String jName, String jGroup) { + try { + scheduler.pauseJob(JobKey.jobKey(jName, jGroup)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 继续定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + public void resumeJob(String jName, String jGroup) { + try { + scheduler.resumeJob(JobKey.jobKey(jName, jGroup)); + } catch (SchedulerException e) { + e.printStackTrace(); + } + } + + /** + * 删除定时任务 + * @param jName 任务名 + * @param jGroup 任务组 + */ + public void deleteJob(String jName, String jGroup) { + try { + scheduler.deleteJob(JobKey.jobKey(jName, jGroup)); + } catch (SchedulerException e) { + e.printStackTrace(); + } + } +} diff --git a/magic-boot/src/main/java/org/ssssssss/magicboot/vo/Result.java b/magic-boot/src/main/java/org/ssssssss/magicboot/vo/Result.java new file mode 100644 index 0000000..76b9a67 --- /dev/null +++ b/magic-boot/src/main/java/org/ssssssss/magicboot/vo/Result.java @@ -0,0 +1,112 @@ +package org.ssssssss.magicboot.vo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Result { + // 错误码 + private String code; + // 提示信息 + private String msg; + // 具体的内容 + private T data; + // 是否成功 + private boolean success; + // 总数 + private Long total; + + public Result() { + } + + public Result(String code, String msg, T data, boolean success) { + this.code = code; + this.msg = msg; + this.data = data; + this.success = success; + } + + public static Result isSuccess(T data) { + Result objectResult = new Result(); + objectResult.setCode("200"); + objectResult.setSuccess(true); + objectResult.setMsg("成功"); + objectResult.setData(data); + return objectResult; + } + + public static Result isSuccess(T data, Long count) { + Result objectResult = new Result(); + objectResult.setCode("200"); + objectResult.setSuccess(true); + objectResult.setMsg("成功"); + objectResult.setData(data); + objectResult.setTotal(count); + return objectResult; + } + + public static Result isBad(T data) { + Result objectResult = new Result(); + objectResult.setCode("400"); + objectResult.setSuccess(false); + objectResult.setMsg("失败"); + objectResult.setData(data); + return objectResult; + } + + public static Result isBad(String msg) { + Result objectResult = new Result<>(); + objectResult.setCode("400"); + objectResult.setSuccess(false); + objectResult.setMsg(msg); + return objectResult; + } + + public static Result isBad(String msg, String code) { + Result objectResult = new Result(); + objectResult.setCode(code); + objectResult.setSuccess(false); + objectResult.setMsg(msg); + return objectResult; + } + + public static Result getResult(boolean isSuccess, T data) { + if (isSuccess) { + return Result.isSuccess(data); + } + return Result.isBad(data); + } + + public static Result getResult(boolean isSuccess, T data, String msg) { + if (isSuccess) { + return Result.isSuccess(data); + } + return Result.isBad(msg); + } + + public static Result getResult(boolean isSuccess, String msg) { + if (isSuccess) { + return Result.isSuccess(null); + } + return Result.isBad(msg); + } + + public static Result getResult(boolean isSuccess, String msg, String code) { + if (isSuccess) { + return Result.isSuccess(null); + } + return Result.isBad(msg, code); + } + + + @Override + public String toString() { + return "Result{" + + "code='" + code + '\'' + + ", msg='" + msg + '\'' + + ", data=" + data + + ", success=" + success + + '}'; + } +} diff --git a/magic-boot/src/main/resources/application-quartz.yml b/magic-boot/src/main/resources/application-quartz.yml new file mode 100644 index 0000000..68c7f2f --- /dev/null +++ b/magic-boot/src/main/resources/application-quartz.yml @@ -0,0 +1,6 @@ +spring: + quartz: + job-store-type: jdbc + jdbc: + initialize-schema: always + schema: classpath:schema/tables_mysql.sql diff --git a/magic-boot/src/main/resources/application.yml b/magic-boot/src/main/resources/application.yml index f069408..fd23c6d 100644 --- a/magic-boot/src/main/resources/application.yml +++ b/magic-boot/src/main/resources/application.yml @@ -7,6 +7,9 @@ server: spring: profiles: active: dev + include: + - online + - quartz servlet: multipart: max-file-size: 200MB @@ -15,9 +18,9 @@ spring: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: - url: jdbc:mysql://localhost:3307/magic-boot?useSSL=false&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai + url: jdbc:mysql://localhost:3306/magic-boot?useSSL=false&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai username: root - password: root + password: "********" upload: # oss,disk(磁盘) @@ -68,4 +71,4 @@ sa-token: # token风格 token-style: uuid # 是否输出操作日志 - is-log: false \ No newline at end of file + is-log: false diff --git a/magic-boot/src/main/resources/schema/tables_mysql.sql b/magic-boot/src/main/resources/schema/tables_mysql.sql new file mode 100644 index 0000000..fd986ab --- /dev/null +++ b/magic-boot/src/main/resources/schema/tables_mysql.sql @@ -0,0 +1,159 @@ +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + + +CREATE TABLE QRTZ_JOB_DETAILS +( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + JOB_CLASS_NAME VARCHAR(250) NOT NULL, + IS_DURABLE VARCHAR(1) NOT NULL, + IS_NONCONCURRENT VARCHAR(1) NOT NULL, + IS_UPDATE_DATA VARCHAR(1) NOT NULL, + REQUESTS_RECOVERY VARCHAR(1) NOT NULL, + JOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +); + +CREATE TABLE QRTZ_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + NEXT_FIRE_TIME BIGINT(13) NULL, + PREV_FIRE_TIME BIGINT(13) NULL, + PRIORITY INTEGER NULL, + TRIGGER_STATE VARCHAR(16) NOT NULL, + TRIGGER_TYPE VARCHAR(8) NOT NULL, + START_TIME BIGINT(13) NOT NULL, + END_TIME BIGINT(13) NULL, + CALENDAR_NAME VARCHAR(200) NULL, + MISFIRE_INSTR SMALLINT(2) NULL, + JOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + 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 VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + REPEAT_COUNT BIGINT(7) NOT NULL, + REPEAT_INTERVAL BIGINT(12) NOT NULL, + TIMES_TRIGGERED BIGINT(10) NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CRON_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + CRON_EXPRESSION VARCHAR(200) NOT NULL, + TIME_ZONE_ID VARCHAR(80), + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 VARCHAR(1) NULL, + BOOL_PROP_2 VARCHAR(1) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + BLOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CALENDARS +( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR(200) NOT NULL, + CALENDAR BLOB NOT NULL, + PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR(95) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + FIRED_TIME BIGINT(13) NOT NULL, + SCHED_TIME BIGINT(13) NOT NULL, + PRIORITY INTEGER NOT NULL, + STATE VARCHAR(16) NOT NULL, + JOB_NAME VARCHAR(200) NULL, + JOB_GROUP VARCHAR(200) NULL, + IS_NONCONCURRENT VARCHAR(1) NULL, + REQUESTS_RECOVERY VARCHAR(1) NULL, + PRIMARY KEY (SCHED_NAME,ENTRY_ID) +); + +CREATE TABLE QRTZ_SCHEDULER_STATE +( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + LAST_CHECKIN_TIME BIGINT(13) NOT NULL, + CHECKIN_INTERVAL BIGINT(13) NOT NULL, + PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) +); + +CREATE TABLE QRTZ_LOCKS +( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME,LOCK_NAME) +); + + +commit;