Explorar o código

Merge remote-tracking branch 'origin/master'

zsl %!s(int64=2) %!d(string=hai) anos
pai
achega
7d75b52193
Modificáronse 44 ficheiros con 1992 adicións e 34 borrados
  1. 2 0
      tr-framework/src/main/java/cn/tr/core/utils/ServletUtils.java
  2. 1 1
      tr-modules/pom.xml
  3. 67 0
      tr-modules/tr-module-quartz/pom.xml
  4. 55 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/constant/Constants.java
  5. 50 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/constant/ScheduleConstants.java
  6. 18 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/core/mq/message/job/QuartzJobRefreshMessage.java
  7. 34 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/exception/TaskException.java
  8. 101 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/AbstractQuartzJob.java
  9. 20 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/QuartzDisallowConcurrentExecution.java
  10. 18 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/QuartzJobExecution.java
  11. 170 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/controller/SysJobController.java
  12. 58 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobDTO.java
  13. 20 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobQueryDTO.java
  14. 24 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobSmallDTO.java
  15. 57 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/po/SysJobPO.java
  16. 18 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/repository/SysJobRepository.java
  17. 99 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/service/ISysJobService.java
  18. 239 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/service/impl/SysJobServiceImpl.java
  19. 59 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/controller/SysJobLogController.java
  20. 52 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/dto/SysJobLogDTO.java
  21. 20 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/dto/SysJobLogQueryDTO.java
  22. 57 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/po/SysJobLogPO.java
  23. 18 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/repository/SysJobLogRepository.java
  24. 54 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/service/ISysJobLogService.java
  25. 99 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/service/impl/SysJobLogServiceImpl.java
  26. 27 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/mapper/SysJobLogMapper.java
  27. 28 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/mapper/SysJobMapper.java
  28. 26 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/strategy/QuartzStrategy.java
  29. 93 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/CronUtils.java
  30. 184 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/JobInvokeUtil.java
  31. 128 0
      tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/ScheduleUtils.java
  32. 15 9
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/monitor/controller/SysUserOnlineController.java
  33. 1 1
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/config/SaOAuth2TemplateImpl.java
  34. 5 1
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/controller/CurrentUserController.java
  35. 2 1
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/psw/operator/LoginOAuth2PswUserOperator.java
  36. 2 1
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/dto/OnlineUserOperationDTO.java
  37. 4 2
      tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/TrTenantAutoConfiguration.java
  38. 1 1
      tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/config/aop/TenantIgnoreAspect.java
  39. 29 11
      tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/config/web/TenantContextWebFilter.java
  40. 1 1
      tr-test/pom.xml
  41. 17 0
      tr-test/src/main/java/cn/tr/test/QuartzTest.java
  42. 9 3
      tr-test/src/main/java/cn/tr/test/WebApplication.java
  43. 5 1
      tr-test/src/main/resources/application-doc.yml
  44. 5 1
      tr-test/src/main/resources/application.yml

+ 2 - 0
tr-framework/src/main/java/cn/tr/core/utils/ServletUtils.java

@@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.servlet.ServletUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
@@ -45,6 +46,7 @@ public class ServletUtils {
      */
     @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
     public static void writeJSON(HttpServletResponse response, Object object) throws JsonProcessingException {
+        response.setStatus(HttpStatus.OK.value());
         String content = JsonUtils.toJsonString(object);
         ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
     }

+ 1 - 1
tr-modules/pom.xml

@@ -16,7 +16,7 @@
         <module>tr-module-system</module>
         <module>tr-module-gen</module>
         <module>tr-module-export</module>
-        <module>tr-module-job</module>
+        <module>tr-module-quartz</module>
     </modules>
 
 

+ 67 - 0
tr-modules/tr-module-quartz/pom.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>tr-modules</artifactId>
+        <groupId>cn.tr</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <version>${revision}</version>
+
+    <artifactId>tr-module-quartz</artifactId>
+
+    <description>任务调度模块</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-framework</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-mybatis</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-satoken</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-operatelog</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
+        </dependency>
+
+        <!-- 定时任务 -->
+        <!-- 引入 Quartz 依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 55 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/constant/Constants.java

@@ -0,0 +1,55 @@
+package cn.tr.module.quartz.constant;
+
+/**
+ * 通用常量信息
+ * 
+ * @author ruoyi
+ */
+public class Constants {
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = { "cn.tr" };
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache" };
+}

+ 50 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/constant/ScheduleConstants.java

@@ -0,0 +1,50 @@
+package cn.tr.module.quartz.constant;
+
+/**
+ * 任务调度通用常量
+ * 
+ * @author ruoyi
+ */
+public class ScheduleConstants
+{
+    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
+
+    /** 执行目标key */
+    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
+
+    /** 默认 */
+    public static final String MISFIRE_DEFAULT = "0";
+
+    /** 立即触发执行 */
+    public static final String MISFIRE_IGNORE_MISFIRES = "1";
+
+    /** 触发一次执行 */
+    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
+
+    /** 不触发立即执行 */
+    public static final String MISFIRE_DO_NOTHING = "3";
+
+    public enum Status
+    {
+        /**
+         * 正常
+         */
+        NORMAL("0"),
+        /**
+         * 暂停
+         */
+        PAUSE("1");
+
+        private String value;
+
+        private Status(String value)
+        {
+            this.value = value;
+        }
+
+        public String getValue()
+        {
+            return value;
+        }
+    }
+}

+ 18 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/core/mq/message/job/QuartzJobRefreshMessage.java

@@ -0,0 +1,18 @@
+package cn.tr.module.quartz.core.mq.message.job;
+
+import org.springframework.cloud.bus.event.RemoteApplicationEvent;
+
+/**
+ * @ClassName : QuartzJobRefreshMessage
+ * @Description : 任务调度刷新
+ * @Author : LF
+ * @Date: 2023年05月06日
+ */
+
+public class QuartzJobRefreshMessage extends RemoteApplicationEvent {
+    public QuartzJobRefreshMessage() {
+    }
+    public QuartzJobRefreshMessage(Object source, String originService, String destinationService) {
+        super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
+    }
+}

+ 34 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/exception/TaskException.java

@@ -0,0 +1,34 @@
+package cn.tr.module.quartz.exception;
+
+/**
+ * 计划策略异常
+ * 
+ * @author ruoyi
+ */
+public class TaskException extends Exception
+{
+    private static final long serialVersionUID = 1L;
+
+    private Code code;
+
+    public TaskException(String msg, Code code)
+    {
+        this(msg, code, null);
+    }
+
+    public TaskException(String msg, Code code, Exception nestedEx)
+    {
+        super(msg, nestedEx);
+        this.code = code;
+    }
+
+    public Code getCode()
+    {
+        return code;
+    }
+
+    public enum Code
+    {
+        TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
+    }
+}

+ 101 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/AbstractQuartzJob.java

@@ -0,0 +1,101 @@
+package cn.tr.module.quartz.execution;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.tr.core.utils.JsonUtils;
+import cn.tr.module.quartz.constant.Constants;
+import cn.tr.module.quartz.constant.ScheduleConstants;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+import cn.tr.module.quartz.strategy.QuartzStrategy;
+import lombok.extern.slf4j.Slf4j;
+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
+ */
+@Slf4j
+public abstract class AbstractQuartzJob implements Job {
+
+    /**
+     * 线程本地变量
+     */
+    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException
+    {
+        SysJobDTO job = JsonUtils.parseObject(JsonUtils.toJsonString(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)), SysJobDTO.class);
+        try
+        {
+            before(context, job);
+            if (job != null) {
+                doExecute(context, job);
+            }
+            after(context, job, null);
+        }
+        catch (Exception e) {
+            log.error("任务执行异常  - :", e);
+            after(context, job, e);
+        }
+    }
+
+    /**
+     * 执行前
+     *
+     * @param context 工作执行上下文对象
+     * @param SysJobDTO 系统计划任务
+     */
+    protected void before(JobExecutionContext context, SysJobDTO SysJobDTO) {
+        threadLocal.set(new Date());
+    }
+
+    /**
+     * 执行后
+     *
+     * @param context 工作执行上下文对象
+     * @param SysJobDTO 系统计划任务
+     */
+    protected void after(JobExecutionContext context, SysJobDTO SysJobDTO, Exception e) {
+        Date startTime = threadLocal.get();
+        threadLocal.remove();
+
+        final SysJobLogDTO jobLog = new SysJobLogDTO();
+        jobLog.setJobName(SysJobDTO.getJobName());
+        jobLog.setJobGroup(SysJobDTO.getJobGroup());
+        jobLog.setInvokeTarget(SysJobDTO.getInvokeTarget());
+        jobLog.setStartTime(startTime);
+        jobLog.setEndTime(new Date());
+        long runMs = jobLog.getEndTime().getTime() - jobLog.getStartTime().getTime();
+        jobLog.setJobMessage(jobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+        if (e != null)
+        {
+            jobLog.setStatus(Constants.FAIL);
+            String errorMsg = StrUtil.sub(ExceptionUtil.getMessage(e), 0, 2000);
+            jobLog.setExceptionInfo(errorMsg);
+        }
+        else {
+            jobLog.setStatus(Constants.SUCCESS);
+        }
+        // 写入数据库当中
+        QuartzStrategy.tr.saveJobLog(jobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param job 系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJobDTO job) throws Exception;
+}

+ 20 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/QuartzDisallowConcurrentExecution.java

@@ -0,0 +1,20 @@
+package cn.tr.module.quartz.execution;
+
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.utils.JobInvokeUtil;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+
+/**
+ * 定时任务处理(禁止并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJobDTO sysJob) throws Exception {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 18 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/execution/QuartzJobExecution.java

@@ -0,0 +1,18 @@
+package cn.tr.module.quartz.execution;
+
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.utils.JobInvokeUtil;
+import org.quartz.JobExecutionContext;
+
+/**
+ * 定时任务处理(允许并发执行)
+ * 
+ * @author ruoyi
+ *
+ */
+public class QuartzJobExecution extends AbstractQuartzJob {
+    @Override
+    protected void doExecute(JobExecutionContext context, SysJobDTO sysJob) throws Exception {
+        JobInvokeUtil.invokeMethod(sysJob);
+    }
+}

+ 170 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/controller/SysJobController.java

@@ -0,0 +1,170 @@
+package cn.tr.module.quartz.job.controller;
+
+import cn.hutool.core.util.StrUtil;
+import cn.tr.core.exception.ServiceException;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.module.quartz.constant.Constants;
+import cn.tr.module.quartz.exception.TaskException;
+import cn.tr.module.quartz.job.dto.SysJobSmallDTO;
+import cn.tr.module.quartz.utils.CronUtils;
+import cn.tr.module.quartz.utils.ScheduleUtils;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import cn.tr.core.pojo.CommonResult;
+import lombok.AllArgsConstructor;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.quartz.SchedulerException;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.job.service.ISysJobService;
+import cn.tr.module.quartz.job.dto.SysJobQueryDTO;
+import java.util.*;
+import cn.tr.plugin.mybatis.base.BaseController;
+import org.springframework.web.bind.annotation.*;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+import cn.tr.core.pojo.TableDataInfo;
+/**
+ * 定时任务调度表控制器
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ */
+@Api(tags = "定时任务调度表")
+@RestController
+@RequestMapping("/quartz/job")
+@AllArgsConstructor
+public class SysJobController extends BaseController{
+
+    private final ISysJobService sysJobService;
+
+    @ApiOperationSupport(author = "lf",order = 1)
+    @ApiOperation(value="根据条件查询定时任务调度表",notes = "权限: 无")
+    @PostMapping("/query/page")
+    public TableDataInfo<SysJobDTO> selectList(@RequestBody SysJobQueryDTO query) {
+        startPage();
+        return getDataTable(sysJobService.selectSysJobList(query));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 2)
+    @ApiOperation(value = "根据id查询定时任务调度表",notes = "权限: quartz:job:query")
+    @GetMapping("/detail/{id}")
+    @SaCheckPermission("quartz:job:query")
+    public CommonResult<SysJobDTO> findById(@PathVariable("id") String id){
+        return CommonResult.success(sysJobService.selectSysJobById(id));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 3)
+    @ApiOperation(value="添加定时任务调度表",notes = "权限: quartz:job:add")
+    @PostMapping("/add")
+    @OperateLog
+    @SaCheckPermission("quartz:job:add")
+    public CommonResult<Boolean> add(@RequestBody@Validated(Insert.class) SysJobDTO source) throws SchedulerException, TaskException {
+        validateSource(source);
+        return CommonResult.success(sysJobService.insertSysJob(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 4)
+    @ApiOperation(value="通过主键id编辑定时任务调度表",notes = "权限: quartz:job:edit")
+    @PostMapping("/edit")
+    @OperateLog
+    @SaCheckPermission("quartz:job:edit")
+    public CommonResult<Boolean> edit(@RequestBody@Validated(Update.class) SysJobDTO source) throws SchedulerException, TaskException {
+        validateSource(source);
+        return CommonResult.success(sysJobService.updateSysJobById(source));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 5)
+    @ApiOperation(value="删除定时任务调度表",notes = "权限: quartz:job:remove")
+    @PostMapping("/removeByIds")
+    @OperateLog
+    @SaCheckPermission("quartz:job:remove")
+    public CommonResult<Integer> delete(@RequestBody Collection<String> ids) throws SchedulerException {
+        return CommonResult.success(sysJobService.removeSysJobByIds(ids));
+    }
+
+    /**
+     * 任务调度立即执行一次
+     */
+    @ApiOperationSupport(author = "lf",order = 6)
+    @OperateLog
+    @ApiOperation(value="立即执行一次",notes = "权限: quartz:job:edit")
+    @PostMapping("/run/{jobId}")
+    @SaCheckPermission("quartz:job:edit")
+    public CommonResult<Boolean> run(@PathVariable("jobId") String jobId) throws SchedulerException {
+        return CommonResult.success(sysJobService.run(jobId));
+    }
+
+    /**
+     * 校验cron表达式是否有效
+     */
+    @ApiOperationSupport(author = "lf",order = 7)
+    @ApiOperation(value="校验cron表达式是否有效",notes = "权限: 无")
+    @PostMapping("/checkCronExpressionIsValid")
+    public CommonResult<Boolean> checkCronExpressionIsValid(@RequestBody String cronExpression) {
+        return CommonResult.success(sysJobService.checkCronExpressionIsValid(cronExpression));
+    }
+
+    /**
+     * 查询cron表达式近5次的执行时间
+     */
+    @ApiOperationSupport(author = "lf",order = 8)
+    @PostMapping("/queryCronExpression")
+    @ApiOperation(value="查询cron表达式近5次的执行时间",notes = "权限: 无")
+    public CommonResult<List<String>> queryCronExpression(@RequestBody String cronExpression)
+    {
+        if (sysJobService.checkCronExpressionIsValid(cronExpression)) {
+            List<String> dateList = CronUtils.getRecentTriggerTime(cronExpression);
+            return CommonResult.success(dateList);
+        }
+        else {
+            return CommonResult.error(TRExcCode.SYSTEM_ERROR_B0001,"表达式无效");
+        }
+    }
+
+
+    /**
+     * 任务调度状态修改
+     */
+    @ApiOperationSupport(author = "lf",order = 6)
+    @OperateLog
+    @ApiOperation(value="任务调度状态修改",notes = "权限: quartz:job:edit")
+    @PostMapping("/changeStatus")
+    @SaCheckPermission("quartz:job:edit")
+    public CommonResult<Boolean> changeStatus(@RequestBody@Validated SysJobSmallDTO source) throws SchedulerException {
+        return CommonResult.success(sysJobService.changeStatus(source.getId(),source.getStatus()));
+    }
+
+    private void validateSource(SysJobDTO source){
+        if (!CronUtils.isValid(source.getCronExpression()))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,Cron表达式不正确");
+        }
+        else if (StrUtil.containsIgnoreCase(source.getInvokeTarget(), Constants.LOOKUP_RMI))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+        }
+        else if (StrUtil.containsAnyIgnoreCase(source.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+        }
+        else if (StrUtil.containsAnyIgnoreCase(source.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+        }
+        else if (StrUtil.containsAnyIgnoreCase(source.getInvokeTarget(), Constants.JOB_ERROR_STR))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,目标字符串存在违规");
+        }
+        else if (!ScheduleUtils.whiteList(source.getInvokeTarget()))
+        {
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"新增任务'" + source.getJobName() + "'失败,目标字符串不在白名单内");
+        }
+    }
+}

+ 58 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobDTO.java

@@ -0,0 +1,58 @@
+package cn.tr.module.quartz.job.dto;
+
+import cn.tr.plugin.mybatis.pojo.BaseDTO;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.*;
+/**
+ * 定时任务调度表传输对象
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+@Data
+@ApiModel("定时任务调度表传输对象")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysJobDTO extends BaseDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "任务ID", position = 1)
+    @NotBlank  (message = "主键不能为空",groups = {Update.class})
+    private String jobId;
+
+    @ApiModelProperty(value = "任务名称", position = 2,required = true)
+    @NotBlank  (message = "任务名称不能为空",groups = {Update.class,Insert.class})
+    private String jobName;
+
+    @ApiModelProperty(value = "任务组名", position = 3)
+    @NotBlank  (message = "任务组名不能为空",groups = {Update.class,Insert.class})
+    private String jobGroup;
+
+    @ApiModelProperty(value = "调用目标字符串", position = 4)
+    private String invokeTarget;
+
+    @ApiModelProperty(value = "cron执行表达式", position = 5)
+    @NotBlank  (message = "cron执行表达式不能为空",groups = {Update.class,Insert.class})
+    private String cronExpression;
+
+    @ApiModelProperty(value = "计划执行错误策略(1立即执行 2执行一次 3放弃执行)", position = 6,required = true)
+    @NotNull  (message = "计划执行错误策略不能为空",groups = {Update.class,Insert.class})
+    private String misfirePolicy;
+
+    @ApiModelProperty(value = "是否并发执行(0允许 1禁止)", position = 7,required = true)
+    @NotNull  (message = "并发设置不能为空",groups = {Update.class,Insert.class})
+    private Boolean concurrent;
+
+    @ApiModelProperty(value = "状态(0正常 1暂停)", position = 8,required = true)
+    @NotNull  (message = "状态不能为空",groups = {Update.class,Insert.class})
+    private String status;
+
+    @ApiModelProperty(value = "备注信息", position = 13)
+    private String remark;
+
+}

+ 20 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobQueryDTO.java

@@ -0,0 +1,20 @@
+package cn.tr.module.quartz.job.dto;
+
+import lombok.ToString;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import cn.tr.plugin.mybatis.pojo.BetweenQuery;
+import java.util.*;
+/**
+ * 定时任务调度表查询参数
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+@Data
+@ApiModel("定时任务调度表查询参数")
+@ToString
+public class SysJobQueryDTO  {
+    private static final long serialVersionUID = 1L;
+}

+ 24 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/dto/SysJobSmallDTO.java

@@ -0,0 +1,24 @@
+package cn.tr.module.quartz.job.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @ClassName : SysJobSmallDTO
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年05月05日
+ */
+@ApiModel("修改任务状态")
+@Data
+public class SysJobSmallDTO {
+    @ApiModelProperty("任务id")
+    @NotNull(message = "任务id不能为空")
+    private String id;
+    @ApiModelProperty("任务状态")
+    @NotNull(message = "任务状态不能为空")
+    private String status;
+}

+ 57 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/po/SysJobPO.java

@@ -0,0 +1,57 @@
+package cn.tr.module.quartz.job.po;
+
+
+import cn.tr.plugin.mybatis.pojo.BasePO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import java.util.*;
+/**
+ * 定时任务调度表实体
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+@Data
+@TableName("sys_job")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysJobPO extends BasePO {
+
+    /** 任务ID */
+    @TableId
+    @ApiModelProperty(value = "任务ID", position = 1)
+    private String jobId;
+
+    /** 任务名称 */
+    @ApiModelProperty(value = "任务名称", position = 2)
+    private String jobName;
+
+    /** 任务组名 */
+    @ApiModelProperty(value = "任务组名", position = 3)
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @ApiModelProperty(value = "调用目标字符串", position = 4)
+    private String invokeTarget;
+
+    /** cron执行表达式 */
+    @ApiModelProperty(value = "cron执行表达式", position = 5)
+    private String cronExpression;
+
+    /** 计划执行错误策略(1立即执行 2执行一次 3放弃执行) */
+    @ApiModelProperty(value = "计划执行错误策略(1立即执行 2执行一次 3放弃执行)", position = 6)
+    private String misfirePolicy;
+
+    /** 是否并发执行(0允许 1禁止) */
+    @ApiModelProperty(value = "是否并发执行(0允许 1禁止)", position = 7)
+    private Boolean concurrent;
+
+    /** 状态(0正常 1暂停) */
+    @ApiModelProperty(value = "状态(0正常 1暂停)", position = 8)
+    private String status;
+
+}

+ 18 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/repository/SysJobRepository.java

@@ -0,0 +1,18 @@
+package cn.tr.module.quartz.job.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+import java.util.*;
+import cn.tr.module.quartz.job.po.SysJobPO;
+/**
+ * 定时任务调度表Mapper接口
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+@Repository
+@Mapper
+public interface SysJobRepository extends BaseMapper<SysJobPO> {
+}

+ 99 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/service/ISysJobService.java

@@ -0,0 +1,99 @@
+package cn.tr.module.quartz.job.service;
+
+import cn.tr.module.quartz.exception.TaskException;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.job.dto.SysJobQueryDTO;
+import org.quartz.SchedulerException;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 定时任务调度表Service接口
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+public interface ISysJobService{
+
+    void initLocal() throws TaskException, SchedulerException;
+
+    /**
+     * 根据条件查询定时任务调度表
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/05/05 10:55
+     */
+    List<SysJobDTO> selectSysJobList(SysJobQueryDTO query);
+
+    /**
+     * 根据id查询定时任务调度表
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/05/05 10:55
+     */
+    SysJobDTO selectSysJobById(String id);
+
+    /**
+     * 编辑定时任务调度表
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/05/05 10:55
+     */
+    boolean updateSysJobById(SysJobDTO source) throws SchedulerException, TaskException;
+
+    /**
+     * 新增定时任务调度表
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/05/05 10:55
+     */
+    boolean insertSysJob(SysJobDTO source) throws SchedulerException, TaskException;
+
+    /**
+     * 删除定时任务调度表详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/05/05 10:55
+     */
+    int removeSysJobByIds(Collection<String> ids) throws SchedulerException;
+
+    /**
+     * 立即运行任务
+     *
+     * @param jobId 调度信息id
+     * @return 结果
+     */
+    boolean run(String jobId) throws SchedulerException;
+
+    /**
+     * 校验cron表达式是否有效
+     * @param cronExpression cron表达式
+     * @return
+     */
+    boolean checkCronExpressionIsValid(String cronExpression);
+
+    /**
+     * 改变任务状态
+     * @param id 任务id
+     * @param status 状态
+     * @return
+     */
+    boolean changeStatus(String id, String status) throws SchedulerException;
+
+    /**
+     * 恢复任务
+     * @param jobId 任务id
+     * @return
+     * @throws SchedulerException
+     */
+    boolean resumeJob(String jobId) throws SchedulerException;
+
+    /**
+     * 暂停任务
+     * @param jobId 任务id
+     * @return
+     * @throws SchedulerException
+     */
+    boolean pauseJob(String jobId) throws SchedulerException;
+}

+ 239 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/job/service/impl/SysJobServiceImpl.java

@@ -0,0 +1,239 @@
+package cn.tr.module.quartz.job.service.impl;
+
+import cn.tr.core.exception.ServiceException;
+import cn.tr.core.exception.TRExcCode;
+import cn.tr.module.quartz.constant.ScheduleConstants;
+import cn.tr.module.quartz.exception.TaskException;
+import cn.tr.module.quartz.utils.CronUtils;
+import cn.tr.module.quartz.utils.ScheduleUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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 cn.hutool.core.collection.CollectionUtil;
+import org.springframework.transaction.annotation.Transactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import cn.tr.module.quartz.job.repository.SysJobRepository;
+import cn.tr.module.quartz.job.po.SysJobPO;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import cn.tr.module.quartz.job.dto.SysJobQueryDTO;
+import java.util.*;
+import cn.tr.module.quartz.job.service.ISysJobService;
+import cn.tr.module.quartz.mapper.SysJobMapper;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 定时任务调度表Service接口实现类
+ *
+ * @author lf
+ * @date  2023/05/05 10:55
+ **/
+@Slf4j
+@Service
+@AllArgsConstructor
+public class SysJobServiceImpl extends ServiceImpl<SysJobRepository,SysJobPO> implements ISysJobService {
+    private final Scheduler scheduler;
+
+    @PostConstruct
+    @Override
+    public void initLocal() throws TaskException, SchedulerException {
+        List<SysJobPO> jobs = this.list();
+        log.info("[initLocal]初始化任务数量{}个",CollectionUtil.size(jobs));
+        if(CollectionUtil.isEmpty(jobs)){
+            return;
+        }
+        List<SysJobDTO> jobDTOS = SysJobMapper.INSTANCE.convertDtoList(jobs);
+        for (SysJobDTO jobDTO : jobDTOS) {
+            ScheduleUtils.createScheduleJob(scheduler, jobDTO);
+        }
+    }
+    /**
+     * 根据条件查询定时任务调度表
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/05/05 10:55
+     */
+    @Override
+    public List<SysJobDTO> selectSysJobList(SysJobQueryDTO query){
+        return SysJobMapper.INSTANCE.convertDtoList(
+                baseMapper.selectList(new LambdaQueryWrapper<SysJobPO>()
+                )
+        );
+    };
+
+    /**
+     * 根据id查询定时任务调度表
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/05/05 10:55
+     */
+    @Override
+    public SysJobDTO selectSysJobById(String id){
+        return SysJobMapper.INSTANCE.convertDto(baseMapper.selectById(id));
+    };
+
+    /**
+     * 编辑定时任务调度表
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/05/05 10:55
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public boolean updateSysJobById(SysJobDTO source) throws SchedulerException, TaskException {
+        SysJobDTO properties = selectSysJobById(source.getJobId());
+        int rows = baseMapper.updateById(SysJobMapper.INSTANCE.convertPO(source));
+        if (rows > 0) {
+            updateSchedulerJob(source, properties.getJobGroup());
+        }
+        return rows!=0;
+    };
+
+    /**
+     * 新增定时任务调度表
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/05/05 10:55
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean insertSysJob(SysJobDTO source) throws SchedulerException, TaskException {
+        source.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        SysJobPO po = SysJobMapper.INSTANCE.convertPO(source);
+        int rows = baseMapper.insert(po);
+        if (rows > 0) {
+            source.setJobId(po.getJobId());
+            ScheduleUtils.createScheduleJob(scheduler, source);
+        }
+        return rows!=0;
+    };
+
+    /**
+     * 删除定时任务调度表详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/05/05 10:55
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeSysJobByIds(Collection<String> ids) throws SchedulerException {
+        List<SysJobPO> jobs = this.listByIds(ids);
+        if(CollectionUtil.isEmpty(jobs)){
+            return CollectionUtil.size(ids);
+        }
+        int result = baseMapper.deleteBatchIds(ids);
+        removeJobs(SysJobMapper.INSTANCE.convertDtoList(jobs));
+        return result;
+    }
+
+    @Override
+    public boolean run(String jobId) throws SchedulerException {
+        boolean result = false;
+        SysJobDTO tmpObj = selectSysJobById(jobId);
+        // 参数
+        JobDataMap dataMap = new JobDataMap();
+        dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj);
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup());
+        if (scheduler.checkExists(jobKey)) {
+            result = true;
+            scheduler.triggerJob(jobKey, dataMap);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean checkCronExpressionIsValid(String cronExpression) {
+        return CronUtils.isValid(cronExpression);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean changeStatus(String id, String status) throws SchedulerException {
+        if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
+            return resumeJob(id);
+        }
+        else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
+            return pauseJob(id);
+        }
+        return false;
+    }
+
+
+    /**
+     * 删除调度器中的任务
+     * @param jobs
+     * @throws SchedulerException
+     */
+    private void removeJobs(Collection<SysJobDTO> jobs) throws SchedulerException {
+        for (SysJobDTO job : jobs) {
+            String jobId = job.getJobId();
+            String jobGroup = job.getJobGroup();
+            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 更新任务
+     *
+     * @param job 任务对象
+     * @param jobGroup 任务组名
+     */
+    private void updateSchedulerJob(SysJobDTO job, String jobGroup) throws SchedulerException, TaskException {
+        String jobId = job.getJobId();
+        // 判断是否存在
+        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
+        if (scheduler.checkExists(jobKey)) {
+            // 防止创建时存在数据问题 先移除,然后在执行创建操作
+            scheduler.deleteJob(jobKey);
+        }
+        ScheduleUtils.createScheduleJob(scheduler, job);
+    }
+
+    /**
+     * 恢复任务
+     *
+     * @param jobId 调度信息id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean resumeJob(String jobId) throws SchedulerException {
+        SysJobPO job = this.baseMapper.selectById(jobId);
+        if(job==null){
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"所选任务不存在");
+        }
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
+        int rows = baseMapper.updateById(job);
+        if (rows > 0) {
+            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows!=0;
+    }
+
+    /**
+     * 暂停任务
+     *
+     * @param jobId 调度信息id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean pauseJob(String jobId) throws SchedulerException {
+        SysJobPO job = this.baseMapper.selectById(jobId);
+        if(job==null){
+            throw new ServiceException(TRExcCode.SYSTEM_ERROR_B0001,"所选任务不存在");
+        }
+        String jobGroup = job.getJobGroup();
+        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
+        int rows = baseMapper.updateById(job);
+        if (rows > 0) {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+        return rows!=0;
+    }
+}

+ 59 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/controller/SysJobLogController.java

@@ -0,0 +1,59 @@
+package cn.tr.module.quartz.jobLog.controller;
+
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.tr.core.pojo.CommonResult;
+import lombok.AllArgsConstructor;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+import cn.tr.module.quartz.jobLog.service.ISysJobLogService;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogQueryDTO;
+import java.util.*;
+import cn.tr.plugin.mybatis.base.BaseController;
+import org.springframework.web.bind.annotation.*;
+import cn.tr.plugin.operatelog.annotation.OperateLog;
+import cn.tr.core.pojo.TableDataInfo;
+/**
+ * 定时任务调度日志表控制器
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ */
+@Api(tags = "定时任务调度日志表")
+@RestController
+@RequestMapping("/quartz/jobLog")
+@AllArgsConstructor
+public class SysJobLogController extends BaseController{
+
+    private final ISysJobLogService sysJobLogService;
+
+    @ApiOperationSupport(author = "lf",order = 1)
+    @ApiOperation(value="根据条件查询定时任务调度日志表",notes = "权限: 无")
+    @PostMapping("/query/page")
+    public TableDataInfo<SysJobLogDTO> selectList(@RequestBody SysJobLogQueryDTO query) {
+        startPage();
+        return getDataTable(sysJobLogService.selectSysJobLogList(query));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 2)
+    @ApiOperation(value = "根据id查询定时任务调度日志表",notes = "权限: quartz:jobLog:query")
+    @GetMapping("/detail/{id}")
+    @SaCheckPermission("quartz:jobLog:query")
+    public CommonResult<SysJobLogDTO> findById(@PathVariable("id") String id){
+        return CommonResult.success(sysJobLogService.selectSysJobLogById(id));
+    }
+
+    @ApiOperationSupport(author = "lf",order = 5)
+    @ApiOperation(value="删除定时任务调度日志表",notes = "权限: quartz:jobLog:remove")
+    @PostMapping("/removeByIds")
+    @OperateLog
+    @SaCheckPermission("quartz:jobLog:remove")
+    public CommonResult<Integer> delete(@RequestBody Collection<String> ids) {
+        return CommonResult.success(sysJobLogService.removeSysJobLogByIds(ids));
+    }
+}

+ 52 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/dto/SysJobLogDTO.java

@@ -0,0 +1,52 @@
+package cn.tr.module.quartz.jobLog.dto;
+
+import cn.tr.plugin.mybatis.pojo.BaseDTO;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import cn.tr.core.validation.Insert;
+import cn.tr.core.validation.Update;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.*;
+import java.util.*;
+/**
+ * 定时任务调度日志表传输对象
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+@Data
+@ApiModel("定时任务调度日志表传输对象")
+@EqualsAndHashCode(callSuper = true)
+@ToString
+public class SysJobLogDTO extends BaseDTO  {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "任务日志ID", position = 1)
+     @NotBlank  (message = "主键不能为空",groups = {Update.class})
+    private String id;
+
+    @ApiModelProperty(value = "任务名称", position = 2)
+    private String jobName;
+
+    @ApiModelProperty(value = "任务组名", position = 3)
+    private String jobGroup;
+
+    @ApiModelProperty(value = "调用目标字符串", position = 4)
+    private String invokeTarget;
+
+    @ApiModelProperty(value = "日志信息", position = 5)
+    private String jobMessage;
+
+    @ApiModelProperty(value = "执行状态(0正常 1失败)", position = 6)
+    private String status;
+
+    @ApiModelProperty(value = "异常信息", position = 7)
+    private String exceptionInfo;
+
+    @ApiModelProperty(value = "执行开始时间", position = 8)
+    private Date startTime;
+
+    @ApiModelProperty(value = "执行结束时间", position = 9)
+    private Date endTime;
+}

+ 20 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/dto/SysJobLogQueryDTO.java

@@ -0,0 +1,20 @@
+package cn.tr.module.quartz.jobLog.dto;
+
+import lombok.ToString;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import cn.tr.plugin.mybatis.pojo.BetweenQuery;
+import java.util.*;
+/**
+ * 定时任务调度日志表查询参数
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+@Data
+@ApiModel("定时任务调度日志表查询参数")
+@ToString
+public class SysJobLogQueryDTO  {
+    private static final long serialVersionUID = 1L;
+}

+ 57 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/po/SysJobLogPO.java

@@ -0,0 +1,57 @@
+package cn.tr.module.quartz.jobLog.po;
+
+
+import cn.tr.plugin.mybatis.pojo.BasePO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import java.util.*;
+/**
+ * 定时任务调度日志表实体
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+@Data
+@TableName("sys_job_log")
+@ToString
+public class SysJobLogPO  {
+
+    /** 任务日志ID */
+    @TableId
+    @ApiModelProperty(value = "任务日志ID", position = 1)
+    private String id;
+
+    /** 任务名称 */
+    @ApiModelProperty(value = "任务名称", position = 2)
+    private String jobName;
+
+    /** 任务组名 */
+    @ApiModelProperty(value = "任务组名", position = 3)
+    private String jobGroup;
+
+    /** 调用目标字符串 */
+    @ApiModelProperty(value = "调用目标字符串", position = 4)
+    private String invokeTarget;
+
+    /** 日志信息 */
+    @ApiModelProperty(value = "日志信息", position = 5)
+    private String jobMessage;
+
+    /** 执行状态(0正常 1失败) */
+    @ApiModelProperty(value = "执行状态(0正常 1失败)", position = 6)
+    private Boolean status;
+
+    /** 异常信息 */
+    @ApiModelProperty(value = "异常信息", position = 7)
+    private String exceptionInfo;
+
+    @ApiModelProperty(value = "执行开始时间", position = 8)
+    private Date startTime;
+
+    @ApiModelProperty(value = "执行结束时间", position = 9)
+    private Date endTime;
+}

+ 18 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/repository/SysJobLogRepository.java

@@ -0,0 +1,18 @@
+package cn.tr.module.quartz.jobLog.repository;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+import java.util.*;
+import cn.tr.module.quartz.jobLog.po.SysJobLogPO;
+/**
+ * 定时任务调度日志表Mapper接口
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+@Repository
+@Mapper
+public interface SysJobLogRepository extends BaseMapper<SysJobLogPO> {
+}

+ 54 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/service/ISysJobLogService.java

@@ -0,0 +1,54 @@
+package cn.tr.module.quartz.jobLog.service;
+
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogQueryDTO;
+import java.util.*;
+
+/**
+ * 定时任务调度日志表Service接口
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+public interface ISysJobLogService{
+
+    /**
+     * 根据条件查询定时任务调度日志表
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/05/05 11:07
+     */
+    List<SysJobLogDTO> selectSysJobLogList(SysJobLogQueryDTO query);
+
+    /**
+     * 根据id查询定时任务调度日志表
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/05/05 11:07
+     */
+    SysJobLogDTO selectSysJobLogById(String id);
+
+    /**
+     * 编辑定时任务调度日志表
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/05/05 11:07
+     */
+    boolean updateSysJobLogById(SysJobLogDTO source);
+
+    /**
+     * 新增定时任务调度日志表
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/05/05 11:07
+     */
+    boolean insertSysJobLog(SysJobLogDTO source);
+
+    /**
+     * 删除定时任务调度日志表详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/05/05 11:07
+     */
+    int removeSysJobLogByIds(Collection<String> ids);
+}

+ 99 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/jobLog/service/impl/SysJobLogServiceImpl.java

@@ -0,0 +1,99 @@
+package cn.tr.module.quartz.jobLog.service.impl;
+
+import cn.tr.module.quartz.strategy.QuartzStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.*;
+import cn.tr.plugin.mybatis.pojo.BetweenQuery;
+import org.springframework.transaction.annotation.Transactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import cn.tr.module.quartz.jobLog.repository.SysJobLogRepository;
+import cn.tr.module.quartz.jobLog.po.SysJobLogPO;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogQueryDTO;
+import java.util.*;
+import cn.tr.module.quartz.jobLog.service.ISysJobLogService;
+import cn.tr.module.quartz.mapper.SysJobLogMapper;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 定时任务调度日志表Service接口实现类
+ *
+ * @author lf
+ * @date  2023/05/05 11:07
+ **/
+@Service
+public class SysJobLogServiceImpl implements ISysJobLogService {
+    @Autowired
+    private SysJobLogRepository baseRepository;
+
+    @PostConstruct
+    @Async
+    public void init(){
+        QuartzStrategy.tr.logConsumer=this::insertSysJobLog;
+    }
+
+    /**
+     * 根据条件查询定时任务调度日志表
+     * @param    query 查询参数
+     * @author   lf
+     * @date      2023/05/05 11:07
+     */
+    @Override
+    public List<SysJobLogDTO> selectSysJobLogList(SysJobLogQueryDTO query){
+        return SysJobLogMapper.INSTANCE.convertDtoList(
+                baseRepository.selectList(new LambdaQueryWrapper<SysJobLogPO>()
+                )
+        );
+    };
+
+    /**
+     * 根据id查询定时任务调度日志表
+     * @param    id 主键id
+     * @author   lf
+     * @date      2023/05/05 11:07
+     */
+    @Override
+    public SysJobLogDTO selectSysJobLogById(String id){
+        return SysJobLogMapper.INSTANCE.convertDto(baseRepository.selectById(id));
+    };
+
+    /**
+     * 编辑定时任务调度日志表
+     * @param   source 编辑实体类
+     * @author  lf
+     * @date     2023/05/05 11:07
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public boolean updateSysJobLogById(SysJobLogDTO source){
+        return baseRepository.updateById(SysJobLogMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 新增定时任务调度日志表
+     * @param   source 新增实体类
+     * @author lf
+     * @date  2023/05/05 11:07
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean insertSysJobLog(SysJobLogDTO source){
+        return baseRepository.insert(SysJobLogMapper.INSTANCE.convertPO(source))!=0;
+    };
+
+    /**
+     * 删除定时任务调度日志表详情
+     * @param  ids 删除主键集合
+     * @author lf
+     * @date    2023/05/05 11:07
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeSysJobLogByIds(Collection<String> ids){
+        return baseRepository.deleteBatchIds(ids);
+    };
+}

+ 27 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/mapper/SysJobLogMapper.java

@@ -0,0 +1,27 @@
+package cn.tr.module.quartz.mapper;
+
+import cn.tr.module.quartz.jobLog.po.SysJobLogPO;
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 定时任务调度日志表映射工具
+*
+* @author lf
+* @date  2023/05/05 11:07
+**/
+@Mapper
+public interface SysJobLogMapper {
+    SysJobLogMapper INSTANCE = Mappers.getMapper(SysJobLogMapper.class);
+
+    SysJobLogPO convertPO(SysJobLogDTO source);
+
+    SysJobLogDTO convertDto(SysJobLogPO source);
+
+    List<SysJobLogDTO> convertDtoList(List<SysJobLogPO> source);
+
+    List<SysJobLogPO> convertPOList(List<SysJobLogDTO> source);
+}

+ 28 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/mapper/SysJobMapper.java

@@ -0,0 +1,28 @@
+package cn.tr.module.quartz.mapper;
+
+import cn.tr.module.quartz.job.po.SysJobPO;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+* 定时任务调度表映射工具
+*
+* @author lf
+* @date  2023/05/05 10:55
+**/
+@Mapper
+public interface SysJobMapper {
+    SysJobMapper INSTANCE = Mappers.getMapper(SysJobMapper.class);
+
+    SysJobPO convertPO(SysJobDTO source);
+
+    SysJobDTO convertDto(SysJobPO source);
+
+    List<SysJobDTO> convertDtoList(List<SysJobPO> source);
+
+    List<SysJobPO> convertPOList(List<SysJobDTO> source);
+
+}

+ 26 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/strategy/QuartzStrategy.java

@@ -0,0 +1,26 @@
+package cn.tr.module.quartz.strategy;
+
+import cn.tr.module.quartz.jobLog.dto.SysJobLogDTO;
+
+import java.util.function.Consumer;
+
+/**
+ * @ClassName : QuartzStrategy
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年05月05日
+ */
+
+public class QuartzStrategy {
+    private QuartzStrategy(){
+
+    }
+
+    public Consumer<SysJobLogDTO> logConsumer=log->{};
+
+    public static QuartzStrategy tr=new QuartzStrategy();
+
+    public void saveJobLog(SysJobLogDTO jobLog){
+        logConsumer.accept(jobLog);
+    }
+}

+ 93 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/CronUtils.java

@@ -0,0 +1,93 @@
+package cn.tr.module.quartz.utils;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import org.quartz.CronExpression;
+import org.quartz.TriggerUtils;
+import org.quartz.impl.triggers.CronTriggerImpl;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 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());
+        }
+    }
+
+    /**
+     * 通过表达式获取近10次的执行时间
+     *
+     * @param cron 表达式
+     * @return 时间列表
+     */
+    public static List<String> getRecentTriggerTime(String cron) {
+        List<String> list = new ArrayList<String>();
+        try {
+            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
+            cronTriggerImpl.setCronExpression(cron);
+            List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 10);
+            for (Date date : dates)
+            {
+                list.add(DateUtil.format(date, DatePattern.NORM_DATETIME_PATTERN));
+            }
+        }
+        catch (ParseException e) {
+            return null;
+        }
+        return list;
+    }
+}

+ 184 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/JobInvokeUtil.java

@@ -0,0 +1,184 @@
+package cn.tr.module.quartz.utils;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import org.apache.commons.lang3.StringUtils;
+
+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(SysJobDTO 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 = SpringUtil.getBean(beanName);
+            invokeMethod(bean, methodName, methodParams);
+        }
+        else
+        {
+            Object bean = Class.forName(beanName).getDeclaredConstructor().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 (CollectionUtil.isNotEmpty(methodParams))
+        {
+            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
+            method.invoke(bean, getMethodParamsValue(methodParams));
+        }
+        else
+        {
+            Method method = bean.getClass().getMethod(methodName);
+            method.invoke(bean);
+        }
+    }
+
+    /**
+     * 校验是否为为class包名
+     * 
+     * @param invokeTarget 名称
+     * @return true是 false否
+     */
+    public static boolean isValidClassName(String invokeTarget)
+    {
+        return StrUtil.count(invokeTarget, ".") > 1;
+    }
+
+    /**
+     * 获取bean名称
+     * 
+     * @param invokeTarget 目标字符串
+     * @return bean名称
+     */
+    public static String getBeanName(String invokeTarget) {
+        String beanName = StrUtil.subBefore(invokeTarget, "(",false);
+        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;
+    }
+}

+ 128 - 0
tr-modules/tr-module-quartz/src/main/java/cn/tr/module/quartz/utils/ScheduleUtils.java

@@ -0,0 +1,128 @@
+package cn.tr.module.quartz.utils;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.tr.module.quartz.constant.Constants;
+import cn.tr.module.quartz.constant.ScheduleConstants;
+import cn.tr.module.quartz.exception.TaskException;
+import cn.tr.module.quartz.execution.QuartzDisallowConcurrentExecution;
+import cn.tr.module.quartz.execution.QuartzJobExecution;
+import cn.tr.module.quartz.job.dto.SysJobDTO;
+import org.quartz.*;
+
+/**
+ * 定时任务工具类
+ * 
+ * @author ruoyi
+ *
+ */
+public class ScheduleUtils
+{
+    /**
+     * 得到quartz任务类
+     *
+     * @param job 执行计划
+     * @return 具体执行任务类
+     */
+    private static Class<? extends Job> getQuartzJobClass(SysJobDTO job) {
+        return Boolean.TRUE.equals(job.getConcurrent()) ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+    }
+
+    /**
+     * 构建任务触发对象
+     */
+    public static TriggerKey getTriggerKey(String jobId, String jobGroup) {
+        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 构建任务键对象
+     */
+    public static JobKey getJobKey(String jobId, String jobGroup) {
+        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+    }
+
+    /**
+     * 创建定时任务
+     */
+    public static void createScheduleJob(Scheduler scheduler, SysJobDTO job) throws SchedulerException, TaskException {
+        Class<? extends Job> jobClass = getQuartzJobClass(job);
+        // 构建job信息
+        String 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));
+        }
+
+        // 判断任务是否过期
+        if (ObjectUtil.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
+        {
+            // 执行调度任务
+            scheduler.scheduleJob(jobDetail, trigger);
+        }
+
+        // 暂停任务
+        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
+        {
+            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+        }
+    }
+
+    /**
+     * 设置定时任务策略
+     */
+    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJobDTO 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", TaskException.Code.CONFIG_ERROR);
+        }
+    }
+
+    /**
+     * 检查包名是否为白名单配置
+     * 
+     * @param invokeTarget 目标字符串
+     * @return 结果
+     */
+    public static boolean whiteList(String invokeTarget)
+    {
+        String packageName = StrUtil.subBefore(invokeTarget, "(",false);
+        int count = StrUtil.count(packageName, ".");
+        if (count > 1) {
+            return StrUtil.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
+        }
+        Object obj = SpringUtil.getBean(CollectionUtil.getFirst(StrUtil.split(invokeTarget, ".")));
+        String beanPackageName = obj.getClass().getPackage().getName();
+        return StrUtil.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
+                && !StrUtil.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
+    }
+}

+ 15 - 9
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/monitor/controller/SysUserOnlineController.java

@@ -14,6 +14,7 @@ import cn.tr.core.strategy.PageStrategy;
 import cn.tr.module.sys.user.dto.OnlineUserOperationDTO;
 import cn.tr.module.sys.user.dto.OnlineUserQueryDTO;
 import cn.tr.module.sys.user.dto.OnlineUserSessionDTO;
+import cn.tr.module.sys.user.dto.OnlineUserTokenSessionDTO;
 import cn.tr.plugin.mybatis.base.BaseController;
 import cn.tr.plugin.security.constant.SecurityConstant;
 import cn.tr.plugin.security.utils.SaTokenUtils;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -69,17 +71,21 @@ public class SysUserOnlineController extends BaseController {
     @PostMapping("/forceLogout")
     public CommonResult forceLogout(@RequestBody @Validated OnlineUserOperationDTO source) {
         StpLogic stpLogic = SaTokenUtils.getStpUtil(source.getLoginType());
+        SaSession saSession = stpLogic.getSessionBySessionId(source.getSessionId());
         if (CollectionUtil.isNotEmpty(source.getTokenValues())) {
+            OnlineUserSessionDTO userSession = SaTokenUtils.getValue(saSession, SecurityConstant.SESSION_USER, OnlineUserSessionDTO.class);
+            List<OnlineUserTokenSessionDTO> tokenSessionList = userSession.getTokenSessionList();
             source.getTokenValues().forEach(stpLogic::logoutByTokenValue);
-        }
-        if(CollectionUtil.isNotEmpty(source.getSessionIds())){
-            source.getSessionIds().parallelStream().map(stpLogic::getSessionBySessionId)
-                    .peek(session -> {
-                        session.getTokenSignList().stream()
-                                .map(TokenSign::getValue)
-                                .forEach(stpLogic::logoutByTokenValue);
-                    })
-                    .forEach(SaSession::logout);
+            List<OnlineUserTokenSessionDTO> result = tokenSessionList.stream()
+                    .filter(tokenSession -> !source.getTokenValues().contains(tokenSession.getTokenValue()))
+                    .collect(Collectors.toList());
+            userSession.setTokenSessionList(result);
+            SaTokenUtils.set(saSession, SecurityConstant.SESSION_USER,userSession);
+        }else {
+            saSession.getTokenSignList().stream()
+                    .map(TokenSign::getValue)
+                    .forEach(stpLogic::logoutByTokenValue);
+            saSession.logout();
         }
         return CommonResult.success();
     }

+ 1 - 1
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/config/SaOAuth2TemplateImpl.java

@@ -26,7 +26,7 @@ public  class SaOAuth2TemplateImpl extends SaOAuth2Template implements CommandLi
 
     @Override
     public String randomAccessToken(String clientId, Object loginId, String scope) {
-        return  SaTokenUtils.getStpUtil().createLoginSession(loginId);
+        return  SaTokenUtils.getStpUtil().getTokenValue();
     }
 
     // 根据 id 获取 Client 信息

+ 5 - 1
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/controller/CurrentUserController.java

@@ -10,13 +10,13 @@ import cn.tr.module.sys.oauth2.service.CurrentUserService;
 import cn.tr.module.sys.user.dto.SysPortalDTO;
 import cn.tr.module.sys.user.vo.RouteItemVO;
 import cn.tr.plugin.security.context.LoginUserContextHolder;
+import cn.tr.plugin.security.utils.SaTokenUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
-
 import java.util.Collection;
 import java.util.List;
 
@@ -36,6 +36,7 @@ public class CurrentUserController {
     @ApiOperation("当前用户的登陆信息")
     @GetMapping("/loginInfo")
     public CommonResult accountInfo(){
+        SaTokenUtils.getStpUtil().checkLogin();
         String stpType = LoginUserContextHolder.getStpType();
         AbstractOAuth2PswUserOperator operator = pswUserOperatorManager.matchLoginType(stpType);
         if(operator==null){
@@ -47,18 +48,21 @@ public class CurrentUserController {
     @GetMapping("/getPermissionInfo")
     @ApiOperation( "获取登录用户的权限信息")
     public CommonResult<Collection<String>> getPermissionInfo() {
+        SaTokenUtils.getStpUtil().checkLogin();
         return CommonResult.success(currentUserService.currentUserPermission());
     }
 
     @GetMapping("/listMenus")
     @ApiOperation("获得登录用户的菜单列表")
     public CommonResult<List<RouteItemVO>> getMenus() {
+        SaTokenUtils.getStpUtil().checkLogin();
         return CommonResult.success(TreeUtil.buildTree(currentUserService.currentUserMenus()));
     }
 
     @GetMapping("/listPortal")
     @ApiOperation("获取登录用户的门户列表")
     public CommonResult<List<SysPortalDTO>> getPortals() {
+        SaTokenUtils.getStpUtil().checkLogin();
         return CommonResult.success(currentUserService.currentUserPortals());
     }
 }

+ 2 - 1
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/oauth2/psw/operator/LoginOAuth2PswUserOperator.java

@@ -82,6 +82,7 @@ public class LoginOAuth2PswUserOperator extends AbstractOAuth2PswUserOperator{
         }
         StpLogic stpUtil = SaTokenUtils.getStpUtil();
         stpUtil.login(user.getId());
+        String tokenValue = stpUtil.getTokenValue();
         Date loginTime = new Date();
         //更新最后登录信息
         String ip = ServletUtils.getClientIP();
@@ -98,7 +99,7 @@ public class LoginOAuth2PswUserOperator extends AbstractOAuth2PswUserOperator{
         UserAgent userAgent = Optional.ofNullable(UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"))).orElse(new UserAgent());
         String browser = ObjectUtil.isEmpty(userAgent.getBrowser()) ? "未知" : userAgent.getBrowser().getName();
         String os = ObjectUtil.isEmpty(userAgent.getOs()) ? "未知" : userAgent.getOs().getName();
-        String tokenValue = stpUtil.getTokenValue();
+
         UserLoginInfoBO loginInfo = UserLoginInfoBO.builder()
                 .userId(user.getId())
                 .username(username)

+ 2 - 1
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/user/dto/OnlineUserOperationDTO.java

@@ -25,5 +25,6 @@ public class OnlineUserOperationDTO implements Serializable {
     private List<String> tokenValues;
 
     @ApiModelProperty(value = "会话id集合,与tokenValues不能同时为空")
-    private List<String>  sessionIds;
+    @NotNull(message = "会话id不能为空")
+    private String  sessionId;
 }

+ 4 - 2
tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/TrTenantAutoConfiguration.java

@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerIntercept
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
@@ -72,9 +73,10 @@ public class TrTenantAutoConfiguration implements BeanPostProcessor {
 
     // ========== WEB ==========
     @Bean
-    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {
+    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter(TenantProperties tenantProperties,
+                                                                                 ServerProperties serverProperties) {
         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();
-        registrationBean.setFilter(new TenantContextWebFilter());
+        registrationBean.setFilter(new TenantContextWebFilter(tenantProperties,serverProperties.getServlet().getContextPath()));
         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);
         return registrationBean;
     }

+ 1 - 1
tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/config/aop/TenantIgnoreAspect.java

@@ -27,7 +27,7 @@ public class TenantIgnoreAspect {
             // 执行逻辑
             return joinPoint.proceed();
         } finally {
-            TenantContextHolder.setIgnore(oldIgnore);
+            TenantContextHolder.setIgnore(Boolean.TRUE.equals(oldIgnore));
         }
     }
 

+ 29 - 11
tr-plugins/tr-spring-boot-starter-plugin-biz-tenant/src/main/java/cn/tr/plugin/biz/tenant/config/web/TenantContextWebFilter.java

@@ -1,7 +1,11 @@
 package cn.tr.plugin.biz.tenant.config.web;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.tr.core.strategy.LoginUserStrategy;
 import cn.tr.plugin.biz.tenant.context.TenantContextHolder;
+import cn.tr.plugin.biz.tenant.properties.TenantProperties;
+import lombok.AllArgsConstructor;
+import org.springframework.util.AntPathMatcher;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
@@ -16,24 +20,38 @@ import java.io.IOException;
  *
  * @author 芋道源码
  */
+@AllArgsConstructor
 public class TenantContextWebFilter extends OncePerRequestFilter {
-
+    private final TenantProperties tenantProperties;
+    private final String contextPath;
+    private final AntPathMatcher pathMatcher=new AntPathMatcher();
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
-        // 设置
-        try {
-            String tenantId = LoginUserStrategy.tr.getTenantId();
-            if (tenantId != null) {
-                TenantContextHolder.setTenantId(tenantId);
-                TenantContextHolder.setIgnore(false);
-            }
-        }catch (Exception e){
-            //用户未登录
+        if (isIgnoreUrl(request)) {
             TenantContextHolder.setIgnore(true);
+            chain.doFilter(request, response);
+        } else {
+            // 设置
+            String tenantId = LoginUserStrategy.tr.getTenantId();
+            TenantContextHolder.setTenantId(tenantId);
+            TenantContextHolder.setIgnore(false);
+            chain.doFilter(request, response);
         }
+    }
 
-        chain.doFilter(request, response);
+    private boolean isIgnoreUrl(HttpServletRequest request) {
+        // 快速匹配,保证性能
+        if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) {
+            return true;
+        }
+        // 逐个 Ant 路径匹配
+        for (String url : tenantProperties.getIgnoreUrls()) {
+            if (pathMatcher.match(contextPath+url, request.getRequestURI())) {
+                return true;
+            }
+        }
+        return false;
     }
 
 }

+ 1 - 1
tr-test/pom.xml

@@ -86,7 +86,7 @@
 
         <dependency>
             <groupId>cn.tr</groupId>
-            <artifactId>tr-module-job</artifactId>
+            <artifactId>tr-module-quartz</artifactId>
             <version>0.0.9</version>
         </dependency>
 

+ 17 - 0
tr-test/src/main/java/cn/tr/test/QuartzTest.java

@@ -0,0 +1,17 @@
+package cn.tr.test;
+
+/**
+ * @ClassName : QuartzTest
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年05月05日
+ */
+public class QuartzTest {
+    public void noArgs(){
+        System.out.println("无参方法被调用了");
+    }
+
+    public void haveArgs(String arg){
+        System.out.println("有参方法被调用了>>>"+arg);
+    }
+}

+ 9 - 3
tr-test/src/main/java/cn/tr/test/WebApplication.java

@@ -1,14 +1,14 @@
 package cn.tr.test;
 
 import cn.dev33.satoken.strategy.SaStrategy;
-import cn.tr.module.sys.core.mq.consumer.sms.SmsChannelRefreshConsumer;
-import cn.tr.module.sys.core.mq.consumer.sms.SmsTemplateRefreshConsumer;
 import cn.tr.module.sys.core.mq.message.sms.SmsChannelRefreshMessage;
 import cn.tr.module.sys.core.mq.message.sms.SmsTemplateRefreshMessage;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableAsync;
 
 /**
  * @ClassName : WebApplication
@@ -17,11 +17,17 @@ import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
  * @Date: 2023年02月22日
  */
 @SpringBootApplication(scanBasePackages = "cn.tr.module.*")
-@MapperScan({"cn.tr.module.sys.*.repository","cn.tr.module.gen.modular.*.mapper"})
+@MapperScan({"cn.tr.module.*.*.repository","cn.tr.module.*.modular.*.mapper"})
 @RemoteApplicationEventScan(basePackageClasses = {SmsChannelRefreshMessage.class, SmsTemplateRefreshMessage.class})
+@EnableAsync
 public class WebApplication {
     public static void main(String[] args) {
         SaStrategy.me.checkElementAnnotation=c->{};
         SpringApplication.run(WebApplication.class);
     }
+
+    @Bean
+    public QuartzTest quartzTest(){
+        return new QuartzTest();
+    }
 }

+ 5 - 1
tr-test/src/main/resources/application-doc.yml

@@ -17,10 +17,14 @@ knife4j:
       gen:
         group-name: 代码生成器
         api-rule: package
-
         api-rule-resources:
           - cn.tr.module.gen.modular.basic
           - cn.tr.module.gen.modular.config
+      quartz:
+        group-name: 任务调度
+        api-rule: package
+        api-rule-resources:
+          - cn.tr.module.quartz
   setting:
     enable-footer: false
     enable-footer-custom: true

+ 5 - 1
tr-test/src/main/resources/application.yml

@@ -67,7 +67,11 @@ tr:
       - sys_sms_log
 #      编号策略
       - sys_numbering_strategy
-
+#      任务调度模块
+      - sys_job
+      - sys_job_log
+    ignore-urls:
+      - /oauth2/psw/token
 sa-token:
   is-read-header: true
   # token名称 (同时也是cookie名称)