一小时搞定一个可视化定时任务在线管理系统

1,182 阅读7分钟

一小时搞定一个可视化定时任务在线管理系统(基于Spring + Vue)

前端时间看到的一篇博文中的一个很有意思的可视化项目管理系统,借这时间根据自己的理解敲了一下,下面分享给大家 如果想直接看源码的移步到源码地址:github.com/lenve/sched…

技术选型

SpringBoot Jpa MySql vue Spring Job Element UI

*成品展示:

zzz.png 采用SpringBoot + Jpa开发,开发起来更简易,下面来看详细

创建工程

先创建项目,用yml格式写配置文件 application.yml

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 123456

  jpa:
    database: mysql
    database-platform: mysql
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        # 指定数据库InnoDB存储引擎
        dialect: org.hibernate.dialect.MySQL57Dialect
#logging:
#  level: debug

基本功能实现

先写一个工具类,用来根据用户传入Bean名称去Spring容器中查找相应的Bean,这个工具类可以去实现ApplicationContextAware接口。下面说下实现这个接口的好处

  • 不用类似new ClassPathXmlApplicationContext()的方式,从已有的spring上下文取得已实例化的bean。

  • 当一个类实现了这个接口之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。

  • 加载Spring配置文件时,如果Spring配置文件中所定义的Bean类实现了ApplicationContextAware 接口,那么在加载Spring配置文件时,会自动调用ApplicationContextAware 接口中的setApplicationContext()方法获得ApplicationContext对象,

  • ApplicationContext对象是由spring注入的。前提必须在Spring配置文件中指定该类。

使用场景: 从ApplicationContextAware获取ApplicationContext上下文的情况,仅仅适用于当前运行的代码和已启动的Spring代码处于同一个Spring上下文,否则获取到的ApplicationContext是空的。

SpringContextUtils

@Component
public class SpringContextUtils implements ApplicationContextAware {

    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    /**
     * 根据Bean的名称去查找一个bean
     */
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    /**
     * 根据类型去spring容器查找bean
     * @param requiredType
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    /**
     * 判断一个Bean是否是单例的
     * @param name
     * @return
     */
    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    /**
     * 传入bean的名字,判断bean的类型
     * @param name
     * @return
     */
    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }
}

每一个定时任务都对应一个子线程,但这个子线程不能直接拿去用,下面对它封装 SchedulingRunnable

public class SchedulingRunnable implements Runnable{

    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);

    private String beanName;

    private String methodName;

    private String params;

    private Object targetBean;

    private Method method;

    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName,methodName,null);
    }

    public SchedulingRunnable(String beanName, String methodName, String params) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
        init();
    }

    // 初始化
    private void init() {
        try {
            targetBean = SpringContextUtils.getBean(beanName);
            if (StringUtils.hasText(params)){
                // 假设只有定时任务只有一个参数,并且参数类型是String
                method = targetBean.getClass().getDeclaredMethod(methodName,String.class);
            }else {
                method = targetBean.getClass().getDeclaredMethod(methodName);
            }
            // void makeAccessible(Method method) 将一个方法设置为可调用,主要针对private方法;
            ReflectionUtils.makeAccessible(method);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 定时任务,时间到了就执行run方法
     */
    @Override
    public void run() {
        logger.info("定时任务开始执行 -bean:{},方法:{},参数:{}",beanName,methodName,params);
        long startTime = System.currentTimeMillis();
        try {
            if (StringUtils.hasText(params)){
                // 如果方法有参数
                method.invoke(targetBean,params);
            }else {
                method.invoke(targetBean);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.info(String.format("定时任务执行异常 - bean: %s,方法: %s,参数: %s",beanName,methodName,params),e);
        }
        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
    }

    // 防止用户重复添加
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof SchedulingRunnable)) return false;
        SchedulingRunnable that = (SchedulingRunnable) o;
        return Objects.equals(beanName, that.beanName) && Objects.equals(methodName, that.methodName) && Objects.equals(params, that.params);
    }

    @Override
    public int hashCode() {
        return Objects.hash(beanName, methodName, params);
    }
}

springboot中有一个beanThreadPoolTaskScheduler,可以很方便的对重复执行的任务进行调度管理;相比于通过java自带的周期性任务线程池ScheduleThreadPoolExecutor,此bean对象支持根据cron表达式创建周期性任务。ThreadPoolTaskScheduler其实底层使用也是java自带的线程池,所以下面直接使用该线程池

SchedulingConfig

@Configuration
public class SchedulingConfig {

    @Bean
    TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(4);
        // 取消任务时移除线程
        threadPoolTaskScheduler.setRemoveOnCancelPolicy(true);
        // 线程池任务调度器。设置线程名称前缀
        threadPoolTaskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return threadPoolTaskScheduler;
    }
}

处理定时任务执行完返回结果

ScheduledTask

public class ScheduledTask {

    // 定时任务返回结果
    volatile ScheduledFuture future;

    public void cancel(){
        ScheduledFuture<?> future = this.future;
        if (future != null){
            future.cancel(true);
        }
    }
}

下面配置最核心的配置类,作用包括:定时任务的添加、执行与取消

实现org.springframework.beans.factory.DisposableBean接口的bean允许在容器销毁该bean的时候获得一次回调。DisposableBean接口也只规定了一个方法:

void destroy() throws Exception; 通常,要避免使用DisposableBean标志接口而且不鼓励使用该接口,因为这样会将代码与Spring耦合在一起,有一个可选的方案是,在bean定义中指定一个普通的析构方法,然后在XML配置文件中通过指定destroy-method属性来完成。

CronTaskRegistrar

package com.qinghong.scheduling.config;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class CronTaskRegistrar implements DisposableBean {

    // 这个变量中保存着所有的定时任务
    private final Map<Runnable,ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);

    @Autowired
    TaskScheduler taskScheduler;

    public TaskScheduler getTaskScheduler(){
        return this.taskScheduler;
    }

    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }

    /**
     * 添加一个定时任务
     */
    public void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                // 说明要添加的定时任务已经存在
                // 先把已经存在的定时任务移除,然后再添加新的定时任务
                removeCronTask(task);
            }
            // 添加一个定时任务
            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }

    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());

        return scheduledTask;
    }


    public void removeCronTask(Runnable task) {
        // 1.从Map集合中移除
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        // 2.取消正在执行的定时任务
        if (scheduledTask != null)
            scheduledTask.cancel();
    }

    @Override
    public void destroy() {
        // 1、让所有定时任务停止执行
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }

        // 2、清空集合
        this.scheduledTasks.clear();
    }
}

下面创建一个系统任务实体类:

由于采用Jpa,只需要加上数据库表创建策略注解,数据库表就会自己生成

package com.qinghong.scheduling.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;
import java.util.Objects;

/**
 * 一个SysJob 对象就代表一个定时任务
 */
@Data
@Entity(name = "t_sys_job")
public class SysJob {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer jobId;

    private String beanName;

    private String methodName;

    private String methodParams;

    // Cron表达式
    private String cronExpression;

    // 定时任务的状态,0表示暂停 1表示运行
    private Integer jobStatus;

     // 备注信息
    private String remark;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    /**
     * beanName methodName methodParams cronExpression全部都相同就认为是同一个任务
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof SysJob)) return false;
        SysJob sysJob = (SysJob) o;
        return Objects.equals(getBeanName(), sysJob.getBeanName()) && Objects.equals(getMethodName(), sysJob.getMethodName()) && Objects.equals(getMethodParams(), sysJob.getMethodParams()) && Objects.equals(getCronExpression(), sysJob.getCronExpression());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getBeanName(), getMethodName(), getMethodParams(), getCronExpression());
    }
}

接下来写数据访问层(dao)

SysJobRepository

package com.qinghong.scheduling.dao;

import com.qinghong.scheduling.model.SysJob;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface SysJobRepository extends JpaRepository<SysJob,Integer> {
    List<SysJob> findAllByJobStatus(Integer status);
}

业务层代码:

package com.qinghong.scheduling.service;

import com.qinghong.scheduling.config.CronTaskRegistrar;
import com.qinghong.scheduling.config.SchedulingRunnable;
import com.qinghong.scheduling.dao.SysJobRepository;
import com.qinghong.scheduling.model.SysJob;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SysJobService {

    @Autowired
    SysJobRepository sysJobRepository;

    @Autowired
    CronTaskRegistrar cronTaskRegistrar;

    public List<SysJob> getSysJobByStatus(int status) {
        return sysJobRepository.findAllByJobStatus(status);
    }

    public List<SysJob> getAllJobs() {
        return sysJobRepository.findAll();
    }

    public Boolean updateSysJob(SysJob sysJob) {
        SysJob job = sysJobRepository.saveAndFlush(sysJob);
        if (job != null){
            // 更新成功
            SchedulingRunnable runnable = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
            if (sysJob.getJobStatus() == 1){
                // 定时任务是开启状态
                cronTaskRegistrar.addCronTask(runnable,sysJob.getCronExpression());
            }else {
                // 定时任务是关闭状态,移除定时任务
                cronTaskRegistrar.removeCronTask(runnable);
            }
            return true;
        }else {
            // 定时任务
            return false;
        }
    }
}

由于项目启动要将运行态的定时任务跑起来,可以通过CommandLineRunner接口实现,下面先来说一说该接口使用。

在应用服务启动时,需要在所有Bean生成之后,加载一些数据和执行一些应用的初始化。例如:删除临时文件,清楚缓存信息,读取配置文件,数据库连接,这些工作类似开机自启动的概念,CommandLineRunnerApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)。

CommandLineRunner源码实现如下:

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @see ApplicationRunner
 */
@FunctionalInterface
public interface CommandLineRunner {

   /**
    * Callback used to run the bean.
    * @param args incoming main method arguments
    * @throws Exception on error
    */
   void run(String... args) throws Exception;

}

对该接口的注释可以看到如下的含义:

该接口用来指明:当一个bean包含在SpringApplication内,该bean就应当执行。可以在相同的应用上下文定义多个这样的bean。多个bean的先后执行顺序使用@Order注解确定。

下面是项目启动初始化执行的任务:

InitTask

package com.qinghong.scheduling.config;

import com.qinghong.scheduling.model.SysJob;
import com.qinghong.scheduling.service.SysJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

// 初始化定时任务,获取数据库
@Component
public class InitTask implements CommandLineRunner {

    @Autowired
    CronTaskRegistrar cronTaskRegistrar;

    @Autowired
    SysJobService sysJobService;

    @Override
    public void run(String... args) throws Exception {
        // 获取需要运行的定时任务
        List<SysJob> list = sysJobService.getSysJobByStatus(1);
        for (SysJob sysJob : list) {
            cronTaskRegistrar.addCronTask(new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams()),sysJob.getCronExpression());
        }
    }
}

下面写个类和方法进行测试以下:

package com.qinghong.scheduling.taskdemo;

import org.springframework.stereotype.Component;

// 出入spring容器
@Component("schedulingTaskDemo")
public class SchedulingTaskDemo {

    public void taskWithParams(String params){
        System.out.println("执行带参数的定时任务..."+params);
    }

    public void taskWithoutParams(){
        System.out.println("执行不带参数的定时任务");
    }
}

z.png

zz.png

可以看到该方法每秒执行了一次。也可以多添加几条数据测试测试;

可视化页面实现

封装返回前端的数据:

RespBean

package com.qinghong.scheduling.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RespBean {

    public static RespBean ok(String msg,Object obj) {
        return new RespBean(200, msg, obj);
    }


    public static RespBean ok(String msg) {
        return new RespBean(200, msg, null);
    }


    public static RespBean error(String msg,Object obj) {
        return new RespBean(500, msg, obj);
    }


    public static RespBean error(String msg) {
        return new RespBean(500, msg, null);
    }


    private Integer status;
    private String msg;
    private Object data;
}

前端用vueElement UI:

单页面开发,直接引入各cdn即可

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>定时任务在线管理系统</title>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
    <el-table border strip size="mini" :data="jobs">
        <el-table-column prop="jobId" label="作业编号" width="80px"></el-table-column>
        <el-table-column prop="beanName" label="Bean名称"></el-table-column>
        <el-table-column prop="methodName" label="方法名称"></el-table-column>
        <el-table-column prop="methodParams" label="方法参数"></el-table-column>
        <el-table-column label="Cron表达式">
            <template slot-scope="scope">
                <el-tag size="mini">{{scope.row.cronExpression}}</el-tag>
            </template>
        </el-table-column>
        <el-table-column prop="jobStatus" label="作业状态">
            <template slot-scope="scope">
                <el-switch
                        @change="jobStatusChange(scope.row)"
                        size="mini"
                        v-model="scope.row.jobStatus"
                        active-color="#13ce66"
                        inactive-color="#ff4949"
                        active-text="开启"
                        :active-value="1"
                        :inactive-value="0"
                        inactive-text="禁用">
                </el-switch>
            </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注"></el-table-column>
        <el-table-column prop="createTime" label="创建时间"></el-table-column>
        <el-table-column prop="updateTime" label="更新时间"></el-table-column>
        <el-table-column label="操作" width="200px">
            <template slot-scope="scope">
                <el-button type="primary" size="mini" @click="showUpdateTaskView(scope.row)">作业编辑</el-button>
                <el-button type="danger" size="mini" @click="deleteTask(scope.row)">作业删除</el-button>
            </template>
        </el-table-column>
    </el-table>
    <el-button size="mini" icon="el-icon-plus" type="success" @click="showEditTaskView">添加作业</el-button>
    <el-dialog
            :title="dialogTitle"
            size="mini"
            :visible.sync="dialogVisible"
            width="30%">
        <div>
            <el-form ref="form" label-width="140px" size="mini">
                <el-form-item label="Bean名称" required>
                    <el-input v-model="job.beanName"></el-input>
                </el-form-item>
                <el-form-item label="方法名称" required>
                    <el-input v-model="job.methodName"></el-input>
                </el-form-item>
                <el-form-item label="方法参数">
                    <el-input v-model="job.methodParams"></el-input>
                </el-form-item>
                <el-form-item label="Cron表达式" required>
                    <el-input v-model="job.cronExpression"></el-input>
                </el-form-item>
                <el-form-item label="作业状态">
                    <el-radio v-model="job.jobStatus" :label="1">开启</el-radio>
                    <el-radio v-model="job.jobStatus" :label="0">禁用</el-radio>
                </el-form-item>
                <el-form-item label="备注信息">
                    <el-input v-model="job.remark"></el-input>
                </el-form-item>
            </el-form>
        </div>
        <span slot="footer" class="dialog-footer">
    <el-button size="mini" @click="dialogVisible = false">取 消</el-button>
    <el-button size="mini" type="primary" @click="commitData">确 定</el-button>
  </span>
    </el-dialog>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            jobs: [],
            dialogVisible: false,
            dialogTitle: '添加作业',
            job: {
                beanName: '',
                methodName: '',
                methodParams: '',
                cronExpression: '',
                jobStatus: 1,
                remark: ''
            }
        },
        mounted() {
          this.initJobs();
        },
        methods: {
            initJobs() {
                // 加载数据
                axios.get('/jobs/').then(resp=>{
                    if (resp.status == 200){
                        this.jobs = resp.data;
                    }
                })
            },
            deleteTask(data){
                console.log(this.job)
                console.log(data)
                this.$confirm('此操作将永久删除该作业, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    axios.delete('/jobs/?jobId='+data.jobId).then(resp=>{
                        console.log(resp)
                        // 更新成功
                        this.$notify({
                            title: resp.data.status == 200 ? '成功':'失败',
                            message: resp.data.msg,
                            type: resp.data.status == 200 ? 'success':'error'
                        })
                        // 刷新表格
                        this.initJobs();
                    })
                }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });
            },
            jobStatusChange(data){
                // 深复制
                console.log(data)
                console.log(this.job)
                Object.assign(this.job,data);
                this.updateJob();
            },
            updateJob() {
                axios.put('/jobs/',this.job).then(resp=>{
                    console.log(resp)
                    if (resp.status == 200){
                        // 更新成功
                        this.$notify({
                            title: resp.data.status == 200 ? '成功':'失败',
                            message: resp.data.msg,
                            type: resp.data.status == 200 ? 'success':'error'
                        })
                        // 刷新表格
                        this.initJobs();
                    }
                })
            },
            resetJob() {
                this.job = {
                    beanName: '',
                    methodName: '',
                    methodParams: '',
                    cronExpression: '',
                    jobStatus: 1,
                    remark: ''
                };
            },
            commitData() {
                if (!this.job.beanName || !this.job.cronExpression || !this.job.methodName) {
                    this.$message.error('带 * 表示必填字段!');
                    return;
                }
                if (this.job.jobId) {
                    //更新
                    this.updateJob();
                    return;
                }
                axios.post("/jobs/",this.job).then(resp=>{
                    if (resp.status == 200) {
                        this.$notify({
                            title: resp.data.status==200?'成功':'失败',
                            message: resp.data.msg,
                            type: resp.data.status == 200 ? 'success' : 'error'
                        });
                        this.dialogVisible = false;
                        this.initJobs();
                        this.resetJob();
                    }
                });
            },
            showEditTaskView() {
                this.dialogTitle = '添加作业';
                this.dialogVisible = true;
            },
            showUpdateTaskView(data) {
                this.dialogTitle = '修改作业';
                Object.assign(this.job, data);
                this.dialogVisible = true;
            }
        }
    })
</script>

</body>
</html>

创建控制层:

SysJobController

package com.qinghong.scheduling.Controller;

import com.qinghong.scheduling.model.RespBean;
import com.qinghong.scheduling.model.SysJob;
import com.qinghong.scheduling.service.SysJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/jobs")
public class SysJobController {

    @Autowired
    SysJobService sysJobService;

    /**
     * 获取所有任务
     * @return
     */
    @GetMapping("/")
    public List<SysJob> getAllJobs() {
        return sysJobService.getAllJobs();
    }

    // 更新任务状态
    @PutMapping("/")
    public RespBean updateSysJob(@RequestBody SysJob sysJob){
        Boolean result = sysJobService.updateSysJob(sysJob);
        if (result){
            return RespBean.ok("更新作业成功");
        }
        return RespBean.error("更新作业失败");
    }

    // 编辑作业

    /**
     * 添加作业
     * @param sysJob
     * @return
     */
    @PostMapping("/")
    public RespBean addJob(@RequestBody SysJob sysJob) {
        Boolean flag = sysJobService.addJob(sysJob);
        if (flag) {
            return RespBean.ok("作业添加成功");
        }
        return RespBean.error("作业重复,添加失败");
    }

    // 删除作业
    @DeleteMapping("/")
    public RespBean deleteSysJob(int jobId){
        Boolean flag = sysJobService.deleteJobsById(jobId);
        if (flag) {
            return RespBean.ok("删除成功");
        }
        return RespBean.error("删除失败");
    }
}

在业务层添上添加和删除功能:

/**
 * 添加作业
 * @param sysJob
 * @return
 */
public Boolean addJob(SysJob sysJob) {
    List<SysJob> all = sysJobRepository.findAll();
    for (SysJob job : all) {
        if (job.equals(sysJob)) {
            //作业重复,添加失败
            return false;
        }
    }
    //添加
    SysJob sj = sysJobRepository.save(sysJob);
    if (sj != null) {
        //添加成功,如果新加的job是开启状态,就顺便开启
        SchedulingRunnable schedulingRunnable = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
        if (sj.getJobStatus() == 1) {
            cronTaskRegistrar.addCronTask(schedulingRunnable, sysJob.getCronExpression());
        }
        //添加成功
        return true;
    }
    return false;
}


public Boolean deleteJobsById(Integer id) {
    SysJob sysJob = sysJobRepository.findById(id).get();
    SchedulingRunnable schedulingRunnable = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
    cronTaskRegistrar.removeCronTask(schedulingRunnable);
    sysJobRepository.delete(sysJob);
    return true;
}

启动项目,效果如下图

zzz.png