Kaynağa Gözat

定时任务管理相关接口

jichaobo 2 yıl önce
ebeveyn
işleme
21e8a47a5d
22 değiştirilmiş dosya ile 1670 ekleme ve 1 silme
  1. 17 0
      base-modules/service-system/service-system-biz/pom.xml
  2. 1 1
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/MybatisGeneratorUtils.java
  3. 138 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/controller/web/SysJobController.java
  4. 73 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/controller/web/SysJobLogController.java
  5. 91 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/domain/SysJob.java
  6. 81 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/domain/SysJobLog.java
  7. 22 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/mapper/SysJobLogMapper.java
  8. 18 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/mapper/SysJobMapper.java
  9. 63 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/SysJobLogService.java
  10. 113 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/SysJobService.java
  11. 57 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/config/ScheduleConfig.java
  12. 117 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/impl/SysJobLogServiceImpl.java
  13. 264 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/impl/SysJobServiceImpl.java
  14. 109 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/task/RyTask.java
  15. 96 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/AbstractQuartzJob.java
  16. 64 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/CronUtils.java
  17. 159 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/JobInvokeUtil.java
  18. 18 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/QuartzDisallowConcurrentExecution.java
  19. 16 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/QuartzJobExecution.java
  20. 111 0
      base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/ScheduleUtils.java
  21. 20 0
      base-modules/service-system/service-system-biz/src/main/resources/mapper/system/SysJobLogMapper.xml
  22. 22 0
      base-modules/service-system/service-system-biz/src/main/resources/mapper/system/SysJobMapper.xml

+ 17 - 0
base-modules/service-system/service-system-biz/pom.xml

@@ -39,6 +39,23 @@
             <version>1.1.0</version>
         </dependency>
 
+        <!-- Quartz -->
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.mchange</groupId>
+                    <artifactId>c3p0</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 1 - 1
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/MybatisGeneratorUtils.java

@@ -70,7 +70,7 @@ public class MybatisGeneratorUtils {
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombok
         //修改自己想要生成的表
-        strategy.setInclude("sys_user_person");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude("sys_job_log");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 138 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/controller/web/SysJobController.java

@@ -0,0 +1,138 @@
+package com.usky.system.controller.web;
+
+
+import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.exception.job.TaskException;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.system.domain.SysJob;
+import com.usky.system.service.SysJobService;
+import com.usky.system.service.util.CronUtils;
+import com.usky.system.service.util.ScheduleUtils;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.BeanExpressionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * <p>
+ * 定时任务调度表 前端控制器
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@RestController
+@RequestMapping("/sysJob")
+public class SysJobController {
+
+    @Autowired
+    private SysJobService jobService;
+
+    /**
+     * @param jobName      任务名称
+     * @param jobGroup     任务组名
+     * @param status       状态(0正常 1暂停)
+     * @param invokeTarget 调用目标字符串
+     * @param id           任务ID
+     * @param pageNum      当前页
+     * @param pageSize     每页条数
+     * @return
+     */
+    @GetMapping("/list")
+    public ApiResult<CommonPage<SysJob>> list(@RequestParam(value = "jobName", required = false) String jobName,
+                                              @RequestParam(value = "jobGroup", required = false) String jobGroup,
+                                              @RequestParam(value = "status", required = false) String status,
+                                              @RequestParam(value = "invokeTarget", required = false) String invokeTarget,
+                                              @RequestParam(value = "id", required = false, defaultValue = "0") Integer id,
+                                              @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                              @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        return ApiResult.success(jobService.selectJobList(jobName, jobGroup, status, invokeTarget, id, pageNum, pageSize));
+    }
+
+    /**
+     * 定时任务-新增
+     */
+    @Log(title = "定时任务-新增", businessType = BusinessType.INSERT)
+    @PostMapping("add")
+    public ApiResult<Void> add(@RequestBody SysJob job) throws SchedulerException, TaskException {
+        if (!CronUtils.isValid(job.getCronExpression())) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS})) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.HTTP, Constants.HTTPS})) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) {
+            throw new BeanExpressionException("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setCreateBy(SecurityUtils.getUsername());
+        jobService.insertJob(job);
+        return ApiResult.success();
+    }
+
+    /**
+     * 定时任务-修改
+     */
+    @Log(title = "定时任务-修改", businessType = BusinessType.UPDATE)
+    @PutMapping("edit")
+    public ApiResult<Void> edit(@RequestBody SysJob job) throws SchedulerException, TaskException {
+        if (!CronUtils.isValid(job.getCronExpression())) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+        } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS})) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.HTTP, Constants.HTTPS})) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+        } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) {
+            throw new BeanExpressionException("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+        job.setUpdateBy(SecurityUtils.getUsername());
+        jobService.updateJob(job);
+        return ApiResult.success();
+    }
+
+    /**
+     * 定时任务-状态修改
+     */
+    @Log(title = "定时任务-状态修改", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public ApiResult<Void> changeStatus(@RequestBody SysJob job) throws SchedulerException {
+        SysJob newJob = jobService.selectJobById(job.getJobId());
+        newJob.setStatus(job.getStatus());
+        jobService.changeStatus(newJob);
+        return ApiResult.success();
+    }
+
+    /**
+     * 定时任务-立即执行一次
+     */
+    @Log(title = "定时任务-立即执行一次", businessType = BusinessType.UPDATE)
+    @PutMapping("/run")
+    public ApiResult<Void> run(@RequestBody SysJob job) throws SchedulerException {
+        jobService.run(job);
+        return ApiResult.success();
+    }
+
+    /**
+     * 定时任务-删除
+     */
+    @Log(title = "定时任务-删除", businessType = BusinessType.DELETE)
+    @DeleteMapping("/remove")
+    public ApiResult<Void> remove(@RequestParam(value = "jobId") long jobId) throws SchedulerException, TaskException {
+        jobService.deleteJobByIds(jobId);
+        return ApiResult.success();
+    }
+
+}
+

+ 73 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/controller/web/SysJobLogController.java

@@ -0,0 +1,73 @@
+package com.usky.system.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.system.domain.SysJobLog;
+import com.usky.system.service.SysJobLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * <p>
+ * 定时任务调度日志表 前端控制器
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Controller
+@RequestMapping("/sysJobLog")
+public class SysJobLogController {
+    @Autowired
+    private SysJobLogService jobLogService;
+
+    /**
+     * 查询定时任务调度日志列表
+     *
+     * @return
+     */
+    @GetMapping("/list")
+    public ApiResult<CommonPage<SysJobLog>> list(@RequestParam(value = "jobName", required = false) String jobName,
+                                                 @RequestParam(value = "jobGroup", required = false) String jobGroup,
+                                                 @RequestParam(value = "status", required = false) String status,
+                                                 @RequestParam(value = "invokeTarget", required = false) String invokeTarget,
+                                                 @RequestParam(value = "id", required = false, defaultValue = "0") Integer id,
+                                                 @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                                 @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        return ApiResult.success(jobLogService.selectJobLogList(jobName, jobGroup, status, invokeTarget, id, pageNum, pageSize));
+    }
+
+    /**
+     * 根据调度编号获取详细信息
+     */
+    @GetMapping(value = "/{configId}")
+    public ApiResult<SysJobLog> getInfo(@PathVariable Long jobLogId) {
+        return ApiResult.success(jobLogService.selectJobLogById(jobLogId));
+    }
+
+    /**
+     * 删除定时任务调度日志
+     */
+    @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{jobLogIds}")
+    public ApiResult<Void> remove(@PathVariable Long[] jobLogIds) {
+        jobLogService.deleteJobLogByIds(jobLogIds);
+        return ApiResult.success();
+    }
+
+    /**
+     * 清空定时任务调度日志
+     */
+    @Log(title = "调度日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/clean")
+    public ApiResult<Void> clean() {
+        jobLogService.cleanJobLog();
+        return ApiResult.success();
+    }
+
+}
+

+ 91 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/domain/SysJob.java

@@ -0,0 +1,91 @@
+package com.usky.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 定时任务调度表
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class SysJob implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务ID
+     */
+    @TableId(value = "job_id", type = IdType.AUTO)
+    private Long jobId;
+
+    /**
+     * 任务名称
+     */
+    private String jobName;
+
+    /**
+     * 任务组名
+     */
+    private String jobGroup;
+
+    /**
+     * 调用目标字符串
+     */
+    private String invokeTarget;
+
+    /**
+     * cron执行表达式
+     */
+    private String cronExpression;
+
+    /**
+     * 计划执行错误策略(1立即执行 2执行一次 3放弃执行)
+     */
+    private String misfirePolicy;
+
+    /**
+     * 是否并发执行(0允许 1禁止)
+     */
+    private String concurrent;
+
+    /**
+     * 状态(0正常 1暂停)
+     */
+    private String status;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 备注信息
+     */
+    private String remark;
+
+
+}

+ 81 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/domain/SysJobLog.java

@@ -0,0 +1,81 @@
+package com.usky.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 定时任务调度日志表
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class SysJobLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务日志ID
+     */
+    @TableId(value = "job_log_id", type = IdType.AUTO)
+    private Long jobLogId;
+
+    /**
+     * 任务名称
+     */
+    private String jobName;
+
+    /**
+     * 任务组名
+     */
+    private String jobGroup;
+
+    /**
+     * 调用目标字符串
+     */
+    private String invokeTarget;
+
+    /**
+     * 日志信息
+     */
+    private String jobMessage;
+
+    /**
+     * 执行状态(0正常 1失败)
+     */
+    private String status;
+
+    /**
+     * 异常信息
+     */
+    private String exceptionInfo;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 开始时间
+     */
+    @TableField(exist = false)
+    private Date startTime;
+
+    /**
+     * 停止时间
+     */
+    @TableField(exist = false)
+    private Date stopTime;
+
+
+}

+ 22 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/mapper/SysJobLogMapper.java

@@ -0,0 +1,22 @@
+package com.usky.system.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.system.domain.SysJobLog;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 定时任务调度日志表 Mapper 接口
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Repository
+public interface SysJobLogMapper extends CrudMapper<SysJobLog> {
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+
+}

+ 18 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/mapper/SysJobMapper.java

@@ -0,0 +1,18 @@
+package com.usky.system.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.system.domain.SysJob;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 定时任务调度表 Mapper 接口
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Repository
+public interface SysJobMapper extends CrudMapper<SysJob> {
+
+}

+ 63 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/SysJobLogService.java

@@ -0,0 +1,63 @@
+package com.usky.system.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.system.domain.SysJobLog;
+import com.usky.common.mybatis.core.CrudService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 定时任务调度日志表 服务类
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+public interface SysJobLogService extends CrudService<SysJobLog> {
+
+    /**
+     * 获取quartz调度器日志的计划任务
+     *
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    public CommonPage<SysJobLog> selectJobLogList(String jobName, String jobGroup, String status, String invokeTarget, Integer id, Integer pageNum, Integer pageSize);
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     *
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    public SysJobLog selectJobLogById(Long jobLogId);
+
+    /**
+     * 新增任务日志
+     *
+     * @param jobLog 调度日志信息
+     */
+    public void addJobLog(SysJobLog jobLog);
+
+    /**
+     * 批量删除调度日志信息
+     *
+     * @param logIds 需要删除的日志ID
+     * @return 结果
+     */
+    void deleteJobLogByIds(Long[] logIds);
+
+    /**
+     * 删除任务日志
+     *
+     * @param jobId 调度日志ID
+     * @return 结果
+     */
+    public int deleteJobLogById(Long jobId);
+
+    /**
+     * 清空任务日志
+     */
+    public void cleanJobLog();
+
+}

+ 113 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/SysJobService.java

@@ -0,0 +1,113 @@
+package com.usky.system.service;
+
+import com.ruoyi.common.core.exception.job.TaskException;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.system.domain.SysJob;
+import org.quartz.SchedulerException;
+
+/**
+ * <p>
+ * 定时任务调度表 服务类
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+public interface SysJobService extends CrudService<SysJob> {
+
+    /**
+     * 获取quartz调度器的计划任务
+     *
+     * @param jobName      任务名称
+     * @param jobGroup     任务组名
+     * @param status       状态(0正常 1暂停)
+     * @param invokeTarget 调用目标字符串
+     * @param id           任务ID
+     * @param pageNum      当前页
+     * @param pageSize     每页条数
+     * @return
+     */
+    CommonPage<SysJob> selectJobList(String jobName, String jobGroup, String status, String invokeTarget, Integer id, Integer pageNum, Integer pageSize);
+
+    /**
+     * 通过调度任务ID查询调度信息
+     *
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    SysJob selectJobById(Long jobId);
+
+    /**
+     * 暂停任务
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    int pauseJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 恢复任务
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    int resumeJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    int deleteJob(SysJob job) throws SchedulerException;
+
+    /**
+     * 批量删除调度信息
+     *
+     * @param jobId 需要删除的任务ID
+     * @return 结果
+     */
+    void deleteJobByIds(Long jobId) throws SchedulerException;
+
+    /**
+     * 任务调度状态修改
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    void changeStatus(SysJob job) throws SchedulerException;
+
+    /**
+     * 立即运行任务
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    void run(SysJob job) throws SchedulerException;
+
+    /**
+     * 新增任务
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    void insertJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 更新任务
+     *
+     * @param job 调度信息
+     * @return 结果
+     */
+    void updateJob(SysJob job) throws SchedulerException, TaskException;
+
+    /**
+     * 校验cron表达式是否有效
+     *
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    boolean checkCronExpressionIsValid(String cronExpression);
+
+}

+ 57 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/config/ScheduleConfig.java

@@ -0,0 +1,57 @@
+package com.usky.system.service.config;
+
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+
+import javax.sql.DataSource;
+import java.util.Properties;
+
+/**
+ * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效)
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class ScheduleConfig {
+    @Bean
+    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
+        SchedulerFactoryBean factory = new SchedulerFactoryBean();
+        factory.setDataSource(dataSource);
+
+        // quartz参数
+        Properties prop = new Properties();
+        prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
+        prop.put("org.quartz.scheduler.instanceId", "AUTO");
+        // 线程池配置
+        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
+        prop.put("org.quartz.threadPool.threadCount", "20");
+        prop.put("org.quartz.threadPool.threadPriority", "5");
+        // JobStore配置
+        prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
+        // 集群配置
+        prop.put("org.quartz.jobStore.isClustered", "true");
+        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
+        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
+        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
+
+        // sqlserver 启用
+        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
+        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
+        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
+        factory.setQuartzProperties(prop);
+
+        factory.setSchedulerName("RuoyiScheduler");
+        // 延时启动
+        factory.setStartupDelay(1);
+        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
+        // 可选,QuartzScheduler
+        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
+        factory.setOverwriteExistingJobs(true);
+        // 设置自动启动,默认为true
+        factory.setAutoStartup(true);
+
+        return factory;
+    }
+}

+ 117 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/impl/SysJobLogServiceImpl.java

@@ -0,0 +1,117 @@
+package com.usky.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.system.domain.SysJobLog;
+import com.usky.system.mapper.SysJobLogMapper;
+import com.usky.system.service.SysJobLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 定时任务调度日志表 服务实现类
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Service
+public class SysJobLogServiceImpl extends AbstractCrudService<SysJobLogMapper, SysJobLog> implements SysJobLogService {
+
+    @Autowired
+    private SysJobLogMapper jobLogMapper;
+
+    /**
+     * 获取quartz调度器日志的计划任务
+     *
+     * @param jobName
+     * @param jobGroup
+     * @param status
+     * @param invokeTarget
+     * @param id
+     * @param pageNum
+     * @param pageSize
+     * @return
+     */
+    public CommonPage<SysJobLog> selectJobLogList(String jobName, String jobGroup, String status, String invokeTarget, Integer id, Integer pageNum, Integer pageSize) {
+        IPage<SysJobLog> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<SysJobLog> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.like(StringUtils.isNotBlank(jobName), SysJobLog::getJobName, jobName)
+                .eq(StringUtils.isNotBlank(jobGroup), SysJobLog::getJobGroup, jobGroup)
+                .eq(StringUtils.isNotBlank(status), SysJobLog::getStatus, status)
+                .like(StringUtils.isNotBlank(invokeTarget), SysJobLog::getInvokeTarget, invokeTarget)
+                .eq(id != null && id != 0, SysJobLog::getJobLogId, id);
+        page = this.page(page, queryWrapper);
+        return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
+    }
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     *
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    @Override
+    public SysJobLog selectJobLogById(Long jobLogId) {
+        LambdaQueryWrapper<SysJobLog> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.like(SysJobLog::getJobLogId, jobLogId);
+        SysJobLog list = this.getOne(queryWrapper);
+        return list;
+    }
+
+    /**
+     * 新增任务日志
+     *
+     * @param jobLog 调度日志信息
+     */
+    @Override
+    public void addJobLog(SysJobLog jobLog) {
+        jobLog.setCreateTime(LocalDateTime.now());
+        this.save(jobLog);
+    }
+
+    /**
+     * 批量删除调度日志信息
+     *
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    public void deleteJobLogByIds(Long[] logIds) {
+        for (int i = 0; i < logIds.length; i++) {
+            this.removeById(logIds[i]);
+        }
+    }
+
+    /**
+     * 删除任务日志
+     *
+     * @param jobId 调度日志ID
+     */
+    @Override
+    public int deleteJobLogById(Long jobId) {
+        boolean b = this.removeById(jobId);
+        int row = 0;
+        if (b) {
+            row = 1;
+        }
+        return row;
+    }
+
+    /**
+     * 清空任务日志
+     */
+    @Override
+    public void cleanJobLog() {
+        jobLogMapper.cleanJobLog();
+    }
+
+}

+ 264 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/impl/SysJobServiceImpl.java

@@ -0,0 +1,264 @@
+package com.usky.system.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.constant.ScheduleConstants;
+import com.ruoyi.common.core.exception.job.TaskException;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.system.domain.SysJob;
+import com.usky.system.mapper.SysJobMapper;
+import com.usky.system.service.SysJobService;
+import com.usky.system.service.util.CronUtils;
+import com.usky.system.service.util.ScheduleUtils;
+import org.quartz.JobDataMap;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 定时任务调度表 服务实现类
+ * </p>
+ *
+ * @author JCB
+ * @since 2022-11-18
+ */
+@Service
+public class SysJobServiceImpl extends AbstractCrudService<SysJobMapper, SysJob> implements SysJobService {
+
+    @Autowired
+    private Scheduler scheduler;
+
+    /**
+     * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
+     */
+    @PostConstruct
+    public void init() throws SchedulerException, TaskException {
+        scheduler.clear();
+        LambdaQueryWrapper<SysJob> queryWrapper = Wrappers.lambdaQuery();
+        List<SysJob> jobList = this.list(queryWrapper);
+        for (SysJob job : jobList) {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+    }
+
+    /**
+     * 获取quartz调度器的计划任务列表
+     *
+     * @param jobName
+     * @param jobGroup
+     * @param status
+     * @param invokeTarget
+     * @param id
+     * @param pageNum
+     * @param pageSize
+     * @return
+     */
+    public CommonPage<SysJob> selectJobList(String jobName, String jobGroup, String status, String invokeTarget, Integer id, Integer pageNum, Integer pageSize) {
+        IPage<SysJob> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<SysJob> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.like(StringUtils.isNotBlank(jobName), SysJob::getJobName, jobName)
+                .eq(StringUtils.isNotBlank(jobGroup), SysJob::getJobGroup, jobGroup)
+                .eq(StringUtils.isNotBlank(status), SysJob::getStatus, status)
+                .like(StringUtils.isNotBlank(invokeTarget), SysJob::getInvokeTarget, invokeTarget)
+                .eq(id != null && id != 0, SysJob::getJobId, id);
+        page = this.page(page, queryWrapper);
+        return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
+    }
+
+    /**
+     * 通过调度任务ID查询调度信息
+     *
+     * @param jobId 调度任务ID
+     * @return 调度任务对象信息
+     */
+    @Override
+    public SysJob selectJobById(Long jobId) {
+        LambdaQueryWrapper<SysJob> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(SysJob::getJobId, jobId);
+        SysJob one = this.getOne(queryWrapper);
+        return one;
+    }
+
+    /**
+     * 暂停任务
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int pauseJob(SysJob job) throws SchedulerException {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        job.setUpdateTime(LocalDateTime.now());
+        boolean b = this.updateById(job);
+        int rows = 0;
+        if (b) {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+            rows = 1;
+        }
+        return rows;
+    }
+
+    /**
+     * 恢复任务
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int resumeJob(SysJob job) throws SchedulerException {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
+        job.setUpdateTime(LocalDateTime.now());
+        boolean b = this.updateById(job);
+        int rows = 0;
+        if (b) {
+            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+            rows = 1;
+        }
+        return rows;
+    }
+
+    /**
+     * 删除任务后,所对应的trigger也将被删除
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteJob(SysJob job) throws SchedulerException {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        boolean b = this.removeById(jobId);
+        int rows = 0;
+        if (b) {
+            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+            rows = 1;
+        }
+        return rows;
+    }
+
+    /**
+     * 批量删除调度信息
+     *
+     * @param jobId 需要删除的任务ID
+     * @return 结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteJobByIds(Long jobId) throws SchedulerException {
+//        for (Long jobId : jobIds) {
+            LambdaQueryWrapper<SysJob> queryWrapper = Wrappers.lambdaQuery();
+            queryWrapper.eq(SysJob::getJobId, jobId);
+            SysJob job = this.getOne(queryWrapper);
+            deleteJob(job);
+//        }
+    }
+
+    /**
+     * 任务调度状态修改
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void changeStatus(SysJob job) throws SchedulerException {
+        String status = job.getStatus();
+        if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
+            resumeJob(job);
+        } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
+            pauseJob(job);
+        }
+    }
+
+    /**
+     * 立即运行任务
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void run(SysJob job) throws SchedulerException {
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        SysJob properties = selectJobById(job.getJobId());
+        // 参数
+        JobDataMap dataMap = new JobDataMap();
+        dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
+        scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
+    }
+
+    /**
+     * 新增任务
+     *
+     * @param job 调度信息 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void insertJob(SysJob job) throws SchedulerException, TaskException {
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        job.setCreateTime(LocalDateTime.now());
+        boolean save = this.save(job);
+        if (save) {
+            ScheduleUtils.createScheduleJob(scheduler, job);
+        }
+    }
+
+    /**
+     * 更新任务的时间表达式
+     *
+     * @param job 调度信息
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateJob(SysJob job) throws SchedulerException, TaskException {
+        SysJob properties = selectJobById(job.getJobId());
+        job.setUpdateTime(LocalDateTime.now());
+        boolean b = this.updateById(job);
+        if (b) {
+            updateSchedulerJob(job, properties.getJobGroup());
+        }
+    }
+
+    /**
+     * 更新任务
+     *
+     * @param job      任务对象
+     * @param jobGroup 任务组名
+     */
+    public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException {
+        Long jobId = job.getJobId();
+        // 判断是否存在
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey)) {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(jobKey);
+        }
+        ScheduleUtils.createScheduleJob(scheduler, job);
+    }
+
+    /**
+     * 校验cron表达式是否有效
+     *
+     * @param cronExpression 表达式
+     * @return 结果
+     */
+    @Override
+    public boolean checkCronExpressionIsValid(String cronExpression) {
+        return CronUtils.isValid(cronExpression);
+    }
+}

+ 109 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/task/RyTask.java

@@ -0,0 +1,109 @@
+package com.usky.system.service.task;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.usky.common.core.util.HttpUtils;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 定时任务调度测试
+ * 
+ * @author ruoyi
+ */
+@Component("ryTask")
+public class RyTask
+{
+    public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
+    {
+        System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
+    }
+
+    public void ryParams(String params)
+    {
+        System.out.println("执行有参方法:" + params);
+    }
+
+    public void ryNoParams()
+    {
+        System.out.println("执行无参方法");
+    }
+
+    public void ryNoParams1()
+    {
+        System.out.println("执行无参方法1");
+    }
+
+    public void enterpriseIndependenceCompany(){
+        System.out.println("企业自主管理-单位");
+//        String url = "http://qyxf.usky.cn/prod-api/service-fire/demGridMember/gridMemberList";
+        String url = "http://172.16.120.165:13200/prod-api/service-fire/demGridMember/gridMemberList";
+        Map<String, String> map = new HashMap<>();
+        map.put("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
+        map.put("Content-Type", "application/x-www-form-urlencoded");
+        try {
+            String res = HttpUtils.get(url, map);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void postInspectAnswerRequest(){
+        System.out.println("值班查岗");
+//        String url = "http://qyxf.usky.cn/prod-api/service-fire/mhPostInspect/postInspectAnswerRequest";
+        String url = "http://172.16.120.165:13200/prod-api/service-fire/mhPostInspect/postInspectAnswerRequest";
+        Map<String, String> map = new HashMap<>();
+        map.put("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
+        map.put("Content-Type", "application/x-www-form-urlencoded");
+        try {
+            String res = HttpUtils.get(url, map);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void enterpriseIndependenceWhole(){
+        System.out.println("企业自主管理-全部");
+//        String url = "http://qyxf.usky.cn/prod-api/service-fire/bscEnterpriseAutonomy/streetCompany";
+        String url = "http://172.16.120.165:13200/prod-api/service-fire/bscEnterpriseAutonomy/streetCompany";
+        Map<String, String> map = new HashMap<>();
+        map.put("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
+        map.put("Content-Type", "application/x-www-form-urlencoded");
+        try {
+            String res = HttpUtils.get(url, map);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void enterpriseIndependenceStreetTown(){
+        System.out.println("企业自主管理-街镇");
+//        String url = "http://qyxf.usky.cn/prod-api/service-fire/bscEnterpriseStreetTown/streetCompany";
+        String url = "http://172.16.120.165:13200/prod-api/service-fire/bscEnterpriseStreetTown/streetCompany";
+        Map<String, String> map = new HashMap<>();
+        map.put("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
+        map.put("Content-Type", "application/x-www-form-urlencoded");
+        try {
+            String res = HttpUtils.get(url, map);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void lawEnforcement(){
+        System.out.println("执法动态");
+//        String url = "http://qyxf.usky.cn/prod-api/service-fire/bscLawTrend/monthLaw";
+        String url = "http://172.16.120.165:13200/prod-api/service-fire/bscLawTrend/monthLaw";
+        Map<String, String> map = new HashMap<>();
+        map.put("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
+        map.put("Content-Type", "application/x-www-form-urlencoded");
+        try {
+            String res = HttpUtils.get(url, map);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 96 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/AbstractQuartzJob.java

@@ -0,0 +1,96 @@
+package com.usky.system.service.util;
+
+import com.ruoyi.common.core.constant.ScheduleConstants;
+import com.ruoyi.common.core.utils.ExceptionUtil;
+import com.ruoyi.common.core.utils.SpringUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.bean.BeanUtils;
+import com.usky.system.domain.SysJob;
+import com.usky.system.domain.SysJobLog;
+import com.usky.system.service.SysJobLogService;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+/**
+ * 抽象quartz调用
+ *
+ * @author ruoyi
+ */
+public abstract class AbstractQuartzJob implements Job {
+    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
+
+    /**
+     * 线程本地变量
+     */
+    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException {
+        SysJob sysJob = new SysJob();
+        BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
+        try {
+            before(context, sysJob);
+            if (sysJob != null) {
+                doExecute(context, sysJob);
+            }
+            after(context, sysJob, null);
+        } catch (Exception e) {
+            log.error("任务执行异常  - :", e);
+            after(context, sysJob, e);
+        }
+    }
+
+    /**
+     * 执行前
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob  系统计划任务
+     */
+    protected void before(JobExecutionContext context, SysJob sysJob) {
+        threadLocal.set(new Date());
+    }
+
+    /**
+     * 执行后
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob  系统计划任务
+     */
+    protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
+        Date startTime = threadLocal.get();
+        threadLocal.remove();
+
+        final SysJobLog sysJobLog = new SysJobLog();
+        sysJobLog.setJobName(sysJob.getJobName());
+        sysJobLog.setJobGroup(sysJob.getJobGroup());
+        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
+        sysJobLog.setStartTime(startTime);
+        sysJobLog.setStopTime(new Date());
+        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+        if (e != null) {
+            sysJobLog.setStatus("1");
+            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
+            sysJobLog.setExceptionInfo(errorMsg);
+        } else {
+            sysJobLog.setStatus("0");
+        }
+
+        // 写入数据库当中
+        SpringUtils.getBean(SysJobLogService.class).addJobLog(sysJobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob  系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}

+ 64 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/CronUtils.java

@@ -0,0 +1,64 @@
+package com.usky.system.service.util;
+
+import org.quartz.CronExpression;
+
+import java.text.ParseException;
+import java.util.Date;
+
+/**
+ * cron表达式工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class CronUtils
+{
+    /**
+     * 返回一个布尔值代表一个给定的Cron表达式的有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return boolean 表达式是否有效
+     */
+    public static boolean isValid(String cronExpression)
+    {
+        return CronExpression.isValidExpression(cronExpression);
+    }
+
+    /**
+     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
+     *
+     * @param cronExpression Cron表达式
+     * @return String 无效时返回表达式错误描述,如果有效返回null
+     */
+    public static String getInvalidMessage(String cronExpression)
+    {
+        try
+        {
+            new CronExpression(cronExpression);
+            return null;
+        }
+        catch (ParseException pe)
+        {
+            return pe.getMessage();
+        }
+    }
+
+    /**
+     * 返回下一个执行时间根据给定的Cron表达式
+     *
+     * @param cronExpression Cron表达式
+     * @return Date 下次Cron表达式执行时间
+     */
+    public static Date getNextExecution(String cronExpression)
+    {
+        try
+        {
+            CronExpression cron = new CronExpression(cronExpression);
+            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
+        }
+        catch (ParseException e)
+        {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+}

+ 159 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/JobInvokeUtil.java

@@ -0,0 +1,159 @@
+package com.usky.system.service.util;
+
+import com.ruoyi.common.core.utils.SpringUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.usky.system.domain.SysJob;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 任务执行工具
+ *
+ * @author ruoyi
+ */
+public class JobInvokeUtil {
+    /**
+     * 执行方法
+     *
+     * @param sysJob 系统任务
+     */
+    public static void invokeMethod(SysJob sysJob) throws Exception {
+        String invokeTarget = sysJob.getInvokeTarget();
+        String beanName = getBeanName(invokeTarget);
+        String methodName = getMethodName(invokeTarget);
+        List<Object[]> methodParams = getMethodParams(invokeTarget);
+
+        if (!isValidClassName(beanName)) {
+            Object bean = SpringUtils.getBean(beanName);
+            invokeMethod(bean, methodName, methodParams);
+        } else {
+            Object bean = Class.forName(beanName).newInstance();
+            invokeMethod(bean, methodName, methodParams);
+        }
+    }
+
+    /**
+     * 调用任务方法
+     *
+     * @param bean         目标对象
+     * @param methodName   方法名称
+     * @param methodParams 方法参数
+     */
+    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
+            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException {
+        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {
+            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
+            method.invoke(bean, getMethodParamsValue(methodParams));
+        } else {
+            Method method = bean.getClass().getDeclaredMethod(methodName);
+            method.invoke(bean);
+        }
+    }
+
+    /**
+     * 校验是否为为class包名
+     *
+     * @param invokeTarget 名称
+     * @return true是 false否
+     */
+    public static boolean isValidClassName(String invokeTarget) {
+        return StringUtils.countMatches(invokeTarget, ".") > 1;
+    }
+
+    /**
+     * 获取bean名称
+     *
+     * @param invokeTarget 目标字符串
+     * @return bean名称
+     */
+    public static String getBeanName(String invokeTarget) {
+        String beanName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringBeforeLast(beanName, ".");
+    }
+
+    /**
+     * 获取bean方法
+     *
+     * @param invokeTarget 目标字符串
+     * @return method方法
+     */
+    public static String getMethodName(String invokeTarget) {
+        String methodName = StringUtils.substringBefore(invokeTarget, "(");
+        return StringUtils.substringAfterLast(methodName, ".");
+    }
+
+    /**
+     * 获取method方法参数相关列表
+     *
+     * @param invokeTarget 目标字符串
+     * @return method方法相关参数列表
+     */
+    public static List<Object[]> getMethodParams(String invokeTarget) {
+        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
+        if (StringUtils.isEmpty(methodStr)) {
+            return null;
+        }
+        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
+        List<Object[]> classs = new LinkedList<>();
+        for (int i = 0; i < methodParams.length; i++) {
+            String str = StringUtils.trimToEmpty(methodParams[i]);
+            // String字符串类型,以'或"开头
+            if (StringUtils.startsWithAny(str, "'", "\"")) {
+                classs.add(new Object[]{StringUtils.substring(str, 1, str.length() - 1), String.class});
+            }
+            // boolean布尔类型,等于true或者false
+            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
+                classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
+            }
+            // long长整形,以L结尾
+            else if (StringUtils.endsWith(str, "L")) {
+                classs.add(new Object[]{Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class});
+            }
+            // double浮点类型,以D结尾
+            else if (StringUtils.endsWith(str, "D")) {
+                classs.add(new Object[]{Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class});
+            }
+            // 其他类型归类为整形
+            else {
+                classs.add(new Object[]{Integer.valueOf(str), Integer.class});
+            }
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数类型
+     *
+     * @param methodParams 参数相关列表
+     * @return 参数类型列表
+     */
+    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
+        Class<?>[] classs = new Class<?>[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams) {
+            classs[index] = (Class<?>) os[1];
+            index++;
+        }
+        return classs;
+    }
+
+    /**
+     * 获取参数值
+     *
+     * @param methodParams 参数相关列表
+     * @return 参数值列表
+     */
+    public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
+        Object[] classs = new Object[methodParams.size()];
+        int index = 0;
+        for (Object[] os : methodParams) {
+            classs[index] = (Object) os[0];
+            index++;
+        }
+        return classs;
+    }
+}

+ 18 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/QuartzDisallowConcurrentExecution.java

@@ -0,0 +1,18 @@
+package com.usky.system.service.util;
+
+import com.usky.system.domain.SysJob;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+
+/**
+ * 定时任务处理(禁止并发执行)
+ *
+ * @author ruoyi
+ */
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 16 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/QuartzJobExecution.java

@@ -0,0 +1,16 @@
+package com.usky.system.service.util;
+
+import com.usky.system.domain.SysJob;
+import org.quartz.JobExecutionContext;
+
+/**
+ * 定时任务处理(允许并发执行)
+ *
+ * @author ruoyi
+ */
+public class QuartzJobExecution extends AbstractQuartzJob {
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 111 - 0
base-modules/service-system/service-system-biz/src/main/java/com/usky/system/service/util/ScheduleUtils.java

@@ -0,0 +1,111 @@
+package com.usky.system.service.util;
+
+import com.ruoyi.common.core.constant.Constants;
+import com.ruoyi.common.core.constant.ScheduleConstants;
+import com.ruoyi.common.core.exception.job.TaskException;
+import com.ruoyi.common.core.exception.job.TaskException.Code;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.usky.system.domain.SysJob;
+import org.quartz.*;
+
+/**
+ * 定时任务工具类
+ *
+ * @author ruoyi
+ */
+public class ScheduleUtils {
+    /**
+     * 得到quartz任务类
+     *
+     * @param sysJob 执行计划
+     * @return 具体执行任务类
+     */
+    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
+        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
+        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+    }
+
+    /**
+     * 构建任务触发对象
+     */
+    public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
+        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 构建任务键对象
+     */
+    public static JobKey getJobKey(Long jobId, String jobGroup) {
+        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 创建定时任务
+     */
+    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {
+        Class<? extends Job> jobClass = getQuartzJobClass(job);
+        // 构建job信息
+        Long jobId = job.getJobId();
+        String jobGroup = job.getJobGroup();
+        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
+
+        // 表达式调度构建器
+        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
+        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
+
+        // 按新的cronExpression表达式构建一个新的trigger
+        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
+                .withSchedule(cronScheduleBuilder).build();
+
+        // 放入参数,运行时的方法可以获取
+        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
+
+        // 判断是否存在
+        if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(getJobKey(jobId, jobGroup));
+        }
+
+        scheduler.scheduleJob(jobDetail, trigger);
+
+        // 暂停任务
+        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 设置定时任务策略
+     */
+    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
+            throws TaskException {
+        switch (job.getMisfirePolicy()) {
+            case ScheduleConstants.MISFIRE_DEFAULT:
+                return cb;
+            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
+                return cb.withMisfireHandlingInstructionIgnoreMisfires();
+            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
+                return cb.withMisfireHandlingInstructionFireAndProceed();
+            case ScheduleConstants.MISFIRE_DO_NOTHING:
+                return cb.withMisfireHandlingInstructionDoNothing();
+            default:
+                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
+        }
+    }
+
+    /**
+     * 检查包名是否为白名单配置
+     *
+     * @param invokeTarget 目标字符串
+     * @return 结果
+     */
+    public static boolean whiteList(String invokeTarget) {
+        String packageName = StringUtils.substringBefore(invokeTarget, "(");
+        int count = StringUtils.countMatches(packageName, ".");
+        if (count > 1) {
+            return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
+        }
+        return true;
+    }
+}

+ 20 - 0
base-modules/service-system/service-system-biz/src/main/resources/mapper/system/SysJobLogMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.system.mapper.SysJobLogMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.system.domain.SysJobLog">
+        <id column="job_log_id" property="jobLogId"/>
+        <result column="job_name" property="jobName"/>
+        <result column="job_group" property="jobGroup"/>
+        <result column="invoke_target" property="invokeTarget"/>
+        <result column="job_message" property="jobMessage"/>
+        <result column="status" property="status"/>
+        <result column="exception_info" property="exceptionInfo"/>
+        <result column="create_time" property="createTime"/>
+    </resultMap>
+
+    <update id="cleanJobLog">
+        truncate table sys_job_log
+    </update>
+</mapper>

+ 22 - 0
base-modules/service-system/service-system-biz/src/main/resources/mapper/system/SysJobMapper.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.system.mapper.SysJobMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.system.domain.SysJob">
+        <id column="job_id" property="jobId" />
+        <result column="job_name" property="jobName" />
+        <result column="job_group" property="jobGroup" />
+        <result column="invoke_target" property="invokeTarget" />
+        <result column="cron_expression" property="cronExpression" />
+        <result column="misfire_policy" property="misfirePolicy" />
+        <result column="concurrent" property="concurrent" />
+        <result column="status" property="status" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="remark" property="remark" />
+    </resultMap>
+
+</mapper>