Ver Fonte

1. 完成 Job 的 CRUD 功能

YunaiV há 4 anos atrás
pai
commit
f60855faa0
27 ficheiros alterados com 1179 adições e 1367 exclusões
  1. 48 48
      ruoyi-common/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java
  2. 64 85
      ruoyi-common/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java
  3. 54 81
      ruoyi-common/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java
  4. 97 97
      ruoyi-common/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java
  5. 44 44
      ruoyi-common/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java
  6. 0 132
      ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java
  7. 0 218
      ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java
  8. 0 53
      ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java
  9. 12 17
      ruoyi-ui/src/api/infra/job.js
  10. 1 1
      ruoyi-ui/src/router/index.js
  11. 10 0
      ruoyi-ui/src/utils/constants.js
  12. 26 26
      ruoyi-ui/src/views/infra/druid/index.vue
  13. 308 305
      ruoyi-ui/src/views/infra/job/index.vue
  14. 0 0
      ruoyi-ui/src/views/infra/job/log.vue
  15. 209 209
      ruoyi-ui/src/views/infra/server/index.vue
  16. 66 20
      sql/ruoyi-vue-pro.sql
  17. 4 0
      src/main/java/cn/iocoder/dashboard/framework/mybatis/core/mapper/BaseMapperX.java
  18. 49 0
      src/main/java/cn/iocoder/dashboard/framework/quartz/core/scheduler/SchedulerManager.java
  19. 22 0
      src/main/java/cn/iocoder/dashboard/framework/quartz/core/util/CronUtils.java
  20. 32 5
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobController.java
  21. 4 6
      src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/job/InfJobDO.java
  22. 4 0
      src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/job/InfJobMapper.java
  23. 5 0
      src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java
  24. 19 3
      src/main/java/cn/iocoder/dashboard/modules/infra/service/job/InfJobService.java
  25. 95 15
      src/main/java/cn/iocoder/dashboard/modules/infra/service/job/impl/InfJobServiceImpl.java
  26. 4 0
      src/main/java/cn/iocoder/dashboard/util/collection/CollectionUtils.java
  27. 2 2
      src/main/resources/codegen/java/controller/controller.vm

+ 48 - 48
ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java → ruoyi-common/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java

@@ -1,48 +1,48 @@
-package com.ruoyi.quartz.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;
-
-/**
- * 定时任务配置
- *
- * @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.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");
-        factory.setQuartzProperties(prop);
-
-        factory.setSchedulerName("RuoyiScheduler");
-        // 延时启动
-        factory.setStartupDelay(1);
-        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
-        // 可选,QuartzScheduler
-        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
-        factory.setOverwriteExistingJobs(true);
-
-
-        return factory;
-    }
-}
+package com.ruoyi.quartz.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;
+
+/**
+ * 定时任务配置
+ *
+ * @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.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");
+        factory.setQuartzProperties(prop);
+
+        factory.setSchedulerName("RuoyiScheduler");
+        // 延时启动
+        factory.setStartupDelay(1);
+        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
+        // 可选,QuartzScheduler
+        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
+        factory.setOverwriteExistingJobs(true);
+
+
+        return factory;
+    }
+}

+ 64 - 85
ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java → ruoyi-common/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java

@@ -1,85 +1,64 @@
-package com.ruoyi.quartz.controller;
-
-import java.util.List;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.quartz.domain.SysJobLog;
-import com.ruoyi.quartz.service.ISysJobLogService;
-
-/**
- * 调度日志操作处理
- *
- * @author ruoyi
- */
-@RestController
-@RequestMapping("/monitor/jobLog")
-public class SysJobLogController extends BaseController {
-    @Autowired
-    private ISysJobLogService jobLogService;
-
-    /**
-     * 查询定时任务调度日志列表
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(SysJobLog sysJobLog) {
-        startPage();
-        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出定时任务调度日志列表
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:export')")
-    @Log(title = "任务调度日志", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(SysJobLog sysJobLog) {
-        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
-        ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
-        return util.exportExcel(list, "调度日志");
-    }
-
-    /**
-     * 根据调度编号获取详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:query')")
-    @GetMapping(value = "/{configId}")
-    public AjaxResult getInfo(@PathVariable Long jobLogId) {
-        return AjaxResult.success(jobLogService.selectJobLogById(jobLogId));
-    }
-
-
-    /**
-     * 删除定时任务调度日志
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
-    @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{jobLogIds}")
-    public AjaxResult remove(@PathVariable Long[] jobLogIds) {
-        return toAjax(jobLogService.deleteJobLogByIds(jobLogIds));
-    }
-
-    /**
-     * 清空定时任务调度日志
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
-    @Log(title = "调度日志", businessType = BusinessType.CLEAN)
-    @DeleteMapping("/clean")
-    public AjaxResult clean() {
-        jobLogService.cleanJobLog();
-        return AjaxResult.success();
-    }
-}
+package com.ruoyi.quartz.controller;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 调度日志操作处理
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/jobLog")
+public class SysJobLogController extends BaseController {
+    @Autowired
+    private ISysJobLogService jobLogService;
+
+    /**
+     * 查询定时任务调度日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysJobLog sysJobLog) {
+        startPage();
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定时任务调度日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:export')")
+    @Log(title = "任务调度日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(SysJobLog sysJobLog) {
+        List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
+        ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
+        return util.exportExcel(list, "调度日志");
+    }
+
+    /**
+     * 根据调度编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('monitor:job:query')")
+    @GetMapping(value = "/{configId}")
+    public AjaxResult getInfo(@PathVariable Long jobLogId) {
+        return AjaxResult.success(jobLogService.selectJobLogById(jobLogId));
+    }
+
+}

+ 54 - 81
ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java → ruoyi-common/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java

@@ -1,81 +1,54 @@
-package com.ruoyi.quartz.service.impl;
-
-import java.util.List;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import com.ruoyi.quartz.domain.SysJobLog;
-import com.ruoyi.quartz.mapper.SysJobLogMapper;
-import com.ruoyi.quartz.service.ISysJobLogService;
-
-/**
- * 定时任务调度日志信息 服务层
- *
- * @author ruoyi
- */
-@Service
-public class SysJobLogServiceImpl implements ISysJobLogService {
-    @Autowired
-    private SysJobLogMapper jobLogMapper;
-
-    /**
-     * 获取quartz调度器日志的计划任务
-     *
-     * @param jobLog 调度日志信息
-     * @return 调度任务日志集合
-     */
-    @Override
-    public List<SysJobLog> selectJobLogList(SysJobLog jobLog) {
-        return jobLogMapper.selectJobLogList(jobLog);
-    }
-
-    /**
-     * 通过调度任务日志ID查询调度信息
-     *
-     * @param jobLogId 调度任务日志ID
-     * @return 调度任务日志对象信息
-     */
-    @Override
-    public SysJobLog selectJobLogById(Long jobLogId) {
-        return jobLogMapper.selectJobLogById(jobLogId);
-    }
-
-    /**
-     * 新增任务日志
-     *
-     * @param jobLog 调度日志信息
-     */
-    @Override
-    public void addJobLog(SysJobLog jobLog) {
-        jobLogMapper.insertJobLog(jobLog);
-    }
-
-    /**
-     * 批量删除调度日志信息
-     *
-     * @param logIds 需要删除的数据ID
-     * @return 结果
-     */
-    @Override
-    public int deleteJobLogByIds(Long[] logIds) {
-        return jobLogMapper.deleteJobLogByIds(logIds);
-    }
-
-    /**
-     * 删除任务日志
-     *
-     * @param jobId 调度日志ID
-     */
-    @Override
-    public int deleteJobLogById(Long jobId) {
-        return jobLogMapper.deleteJobLogById(jobId);
-    }
-
-    /**
-     * 清空任务日志
-     */
-    @Override
-    public void cleanJobLog() {
-        jobLogMapper.cleanJobLog();
-    }
-}
+package com.ruoyi.quartz.service.impl;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.mapper.SysJobLogMapper;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 定时任务调度日志信息 服务层
+ *
+ * @author ruoyi
+ */
+@Service
+public class SysJobLogServiceImpl implements ISysJobLogService {
+
+    @Autowired
+    private SysJobLogMapper jobLogMapper;
+
+    /**
+     * 获取quartz调度器日志的计划任务
+     *
+     * @param jobLog 调度日志信息
+     * @return 调度任务日志集合
+     */
+    @Override
+    public List<SysJobLog> selectJobLogList(SysJobLog jobLog) {
+        return jobLogMapper.selectJobLogList(jobLog);
+    }
+
+    /**
+     * 通过调度任务日志ID查询调度信息
+     *
+     * @param jobLogId 调度任务日志ID
+     * @return 调度任务日志对象信息
+     */
+    @Override
+    public SysJobLog selectJobLogById(Long jobLogId) {
+        return jobLogMapper.selectJobLogById(jobLogId);
+    }
+
+    /**
+     * 新增任务日志
+     *
+     * @param jobLog 调度日志信息
+     */
+    @Override
+    public void addJobLog(SysJobLog jobLog) {
+        jobLogMapper.insertJobLog(jobLog);
+    }
+
+}

+ 97 - 97
ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java → ruoyi-common/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java

@@ -1,97 +1,97 @@
-package com.ruoyi.quartz.util;
-
-import java.util.Date;
-
-import org.quartz.Job;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.constant.ScheduleConstants;
-import com.ruoyi.common.utils.ExceptionUtil;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.common.utils.bean.BeanUtils;
-import com.ruoyi.common.utils.spring.SpringUtils;
-import com.ruoyi.quartz.domain.SysJob;
-import com.ruoyi.quartz.domain.SysJobLog;
-import com.ruoyi.quartz.service.ISysJobLogService;
-
-/**
- * 抽象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(Constants.FAIL);
-            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
-            sysJobLog.setExceptionInfo(errorMsg);
-        } else {
-            sysJobLog.setStatus(Constants.SUCCESS);
-        }
-
-        // 写入数据库当中
-        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
-    }
-
-    /**
-     * 执行方法,由子类重载
-     *
-     * @param context 工作执行上下文对象
-     * @param sysJob  系统计划任务
-     * @throws Exception 执行过程中的异常
-     */
-    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
-}
+package com.ruoyi.quartz.util;
+
+import java.util.Date;
+
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.utils.ExceptionUtil;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.ruoyi.quartz.domain.SysJob;
+import com.ruoyi.quartz.domain.SysJobLog;
+import com.ruoyi.quartz.service.ISysJobLogService;
+
+/**
+ * 抽象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(Constants.FAIL);
+            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
+            sysJobLog.setExceptionInfo(errorMsg);
+        } else {
+            sysJobLog.setStatus(Constants.SUCCESS);
+        }
+
+        // 写入数据库当中
+        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
+    }
+
+    /**
+     * 执行方法,由子类重载
+     *
+     * @param context 工作执行上下文对象
+     * @param sysJob  系统计划任务
+     * @throws Exception 执行过程中的异常
+     */
+    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}

+ 44 - 44
ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java → ruoyi-common/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java

@@ -1,44 +1,44 @@
-package com.ruoyi.quartz.util;
-
-import org.quartz.CronScheduleBuilder;
-import org.quartz.CronTrigger;
-import org.quartz.Job;
-import org.quartz.JobBuilder;
-import org.quartz.JobDetail;
-import org.quartz.JobKey;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.TriggerBuilder;
-import org.quartz.TriggerKey;
-import com.ruoyi.common.constant.ScheduleConstants;
-import com.ruoyi.common.exception.job.TaskException;
-import com.ruoyi.common.exception.job.TaskException.Code;
-import com.ruoyi.quartz.domain.SysJob;
-
-/**
- * 定时任务工具类
- *
- * @author ruoyi
- */
-public class ScheduleUtils {
-
-    /**
-     * 设置定时任务策略
-     */
-    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);
-        }
-    }
-}
+package com.ruoyi.quartz.util;
+
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import com.ruoyi.common.constant.ScheduleConstants;
+import com.ruoyi.common.exception.job.TaskException;
+import com.ruoyi.common.exception.job.TaskException.Code;
+import com.ruoyi.quartz.domain.SysJob;
+
+/**
+ * 定时任务工具类
+ *
+ * @author ruoyi
+ */
+public class ScheduleUtils {
+
+    /**
+     * 设置定时任务策略
+     */
+    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);
+        }
+    }
+}

+ 0 - 132
ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java

@@ -1,132 +0,0 @@
-package com.ruoyi.quartz.controller;
-
-import java.util.List;
-
-import org.quartz.SchedulerException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import com.ruoyi.common.annotation.Log;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.page.TableDataInfo;
-import com.ruoyi.common.enums.BusinessType;
-import com.ruoyi.common.exception.job.TaskException;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.quartz.domain.SysJob;
-import com.ruoyi.quartz.service.ISysJobService;
-import com.ruoyi.quartz.util.CronUtils;
-
-/**
- * 调度任务信息操作处理
- *
- * @author ruoyi
- */
-@RestController
-@RequestMapping("/monitor/job")
-public class SysJobController extends BaseController {
-    @Autowired
-    private ISysJobService jobService;
-
-    /**
-     * 查询定时任务列表
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(SysJob sysJob) {
-        startPage();
-        List<SysJob> list = jobService.selectJobList(sysJob);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出定时任务列表
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:export')")
-    @Log(title = "定时任务", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(SysJob sysJob) {
-        List<SysJob> list = jobService.selectJobList(sysJob);
-        ExcelUtil<SysJob> util = new ExcelUtil<SysJob>(SysJob.class);
-        return util.exportExcel(list, "定时任务");
-    }
-
-    /**
-     * 获取定时任务详细信息
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:query')")
-    @GetMapping(value = "/{jobId}")
-    public AjaxResult getInfo(@PathVariable("jobId") Long jobId) {
-        return AjaxResult.success(jobService.selectJobById(jobId));
-    }
-
-    /**
-     * 新增定时任务
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:add')")
-    @Log(title = "定时任务", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody SysJob sysJob) throws SchedulerException, TaskException {
-        if (!CronUtils.isValid(sysJob.getCronExpression())) {
-            return AjaxResult.error("cron表达式不正确");
-        }
-        sysJob.setCreateBy(SecurityUtils.getUsername());
-        return toAjax(jobService.insertJob(sysJob));
-    }
-
-    /**
-     * 修改定时任务
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:edit')")
-    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody SysJob sysJob) throws SchedulerException, TaskException {
-        if (!CronUtils.isValid(sysJob.getCronExpression())) {
-            return AjaxResult.error("cron表达式不正确");
-        }
-        sysJob.setUpdateBy(SecurityUtils.getUsername());
-        return toAjax(jobService.updateJob(sysJob));
-    }
-
-    /**
-     * 定时任务状态修改
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')")
-    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
-    @PutMapping("/changeStatus")
-    public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException {
-        SysJob newJob = jobService.selectJobById(job.getJobId());
-        newJob.setStatus(job.getStatus());
-        return toAjax(jobService.changeStatus(newJob));
-    }
-
-    /**
-     * 定时任务立即执行一次
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')")
-    @Log(title = "定时任务", businessType = BusinessType.UPDATE)
-    @PutMapping("/run")
-    public AjaxResult run(@RequestBody SysJob job) throws SchedulerException {
-        jobService.run(job);
-        return AjaxResult.success();
-    }
-
-    /**
-     * 删除定时任务
-     */
-    @PreAuthorize("@ss.hasPermi('monitor:job:remove')")
-    @Log(title = "定时任务", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{jobIds}")
-    public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException {
-        jobService.deleteJobByIds(jobIds);
-        return AjaxResult.success();
-    }
-}

+ 0 - 218
ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java

@@ -1,218 +0,0 @@
-package com.ruoyi.quartz.service.impl;
-
-import java.util.List;
-import javax.annotation.PostConstruct;
-
-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 com.ruoyi.common.constant.ScheduleConstants;
-import com.ruoyi.common.exception.job.TaskException;
-import com.ruoyi.quartz.domain.SysJob;
-import com.ruoyi.quartz.mapper.SysJobMapper;
-import com.ruoyi.quartz.service.ISysJobService;
-import com.ruoyi.quartz.util.CronUtils;
-import com.ruoyi.quartz.util.ScheduleUtils;
-
-/**
- * 定时任务调度信息 服务层
- *
- * @author ruoyi
- */
-@Service
-public class SysJobServiceImpl implements ISysJobService {
-    @Autowired
-    private Scheduler scheduler;
-
-    @Autowired
-    private SysJobMapper jobMapper;
-
-    /**
-     * 获取quartz调度器的计划任务列表
-     *
-     * @param job 调度信息
-     * @return
-     */
-    @Override
-    public List<SysJob> selectJobList(SysJob job) {
-        return jobMapper.selectJobList(job);
-    }
-
-    /**
-     * 通过调度任务ID查询调度信息
-     *
-     * @param jobId 调度任务ID
-     * @return 调度任务对象信息
-     */
-    @Override
-    public SysJob selectJobById(Long jobId) {
-        return jobMapper.selectJobById(jobId);
-    }
-
-    /**
-     * 暂停任务
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    public int pauseJob(SysJob job) throws SchedulerException {
-        Long jobId = job.getJobId();
-        String jobGroup = job.getJobGroup();
-        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
-        int rows = jobMapper.updateJob(job);
-        if (rows > 0) {
-            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
-        }
-        return rows;
-    }
-
-    /**
-     * 恢复任务
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    public int resumeJob(SysJob job) throws SchedulerException {
-        Long jobId = job.getJobId();
-        String jobGroup = job.getJobGroup();
-        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
-        int rows = jobMapper.updateJob(job);
-        if (rows > 0) {
-            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
-        }
-        return rows;
-    }
-
-    /**
-     * 删除任务后,所对应的trigger也将被删除
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    public int deleteJob(SysJob job) throws SchedulerException {
-        Long jobId = job.getJobId();
-        String jobGroup = job.getJobGroup();
-        int rows = jobMapper.deleteJobById(jobId);
-        if (rows > 0) {
-            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
-        }
-        return rows;
-    }
-
-    /**
-     * 批量删除调度信息
-     *
-     * @param jobIds 需要删除的任务ID
-     * @return 结果
-     */
-    @Override
-    @Transactional
-    public void deleteJobByIds(Long[] jobIds) throws SchedulerException {
-        for (Long jobId : jobIds) {
-            SysJob job = jobMapper.selectJobById(jobId);
-            deleteJob(job);
-        }
-    }
-
-    /**
-     * 任务调度状态修改
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    public int changeStatus(SysJob job) throws SchedulerException {
-        int rows = 0;
-        String status = job.getStatus();
-        if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
-            rows = resumeJob(job);
-        } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
-            rows = pauseJob(job);
-        }
-        return rows;
-    }
-
-    /**
-     * 立即运行任务
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    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
-    public int insertJob(SysJob job) throws SchedulerException, TaskException {
-        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
-        int rows = jobMapper.insertJob(job);
-        if (rows > 0) {
-            ScheduleUtils.createScheduleJob(scheduler, job);
-        }
-        return rows;
-    }
-
-    /**
-     * 更新任务的时间表达式
-     *
-     * @param job 调度信息
-     */
-    @Override
-    @Transactional
-    public int updateJob(SysJob job) throws SchedulerException, TaskException {
-        SysJob properties = selectJobById(job.getJobId());
-        int rows = jobMapper.updateJob(job);
-        if (rows > 0) {
-            updateSchedulerJob(job, properties.getJobGroup());
-        }
-        return rows;
-    }
-
-    /**
-     * 更新任务
-     *
-     * @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);
-    }
-}

+ 0 - 53
ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java

@@ -1,53 +0,0 @@
-package com.ruoyi.quartz.util;
-
-import java.text.ParseException;
-import java.util.Date;
-
-import org.quartz.CronExpression;
-
-/**
- * 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());
-        }
-    }
-}

+ 12 - 17
ruoyi-ui/src/api/monitor/job.js → ruoyi-ui/src/api/infra/job.js

@@ -46,35 +46,30 @@ export function delJob(jobId) {
 // 导出定时任务调度
 export function exportJob(query) {
   return request({
-    url: '/infra/job/export',
+    url: '/infra/job/export-excel',
     method: 'get',
-    params: query
+    params: query,
+    responseType: 'blob'
   })
 }
 
 // 任务状态修改
-export function changeJobStatus(jobId, status) {
-  const data = {
-    jobId,
-    status
-  }
+export function updateJobStatus(jobId, status) {
   return request({
-    url: '/monitor/job/changeStatus',
+    url: '/infra/job/update-status',
     method: 'put',
-    data: data
+    headers:{
+      'Content-type': 'application/x-www-form-urlencoded'
+    },
+    data: 'id=' + jobId + "&status=" + status,
   })
 }
 
 
 // 定时任务立即执行一次
-export function runJob(jobId, jobGroup) {
-  const data = {
-    jobId,
-    jobGroup
-  }
+export function runJob(jobId) {
   return request({
-    url: '/monitor/job/run',
-    method: 'put',
-    data: data
+    url: '/infra/job/trigger?id=' + jobId,
+    method: 'put'
   })
 }

+ 1 - 1
ruoyi-ui/src/router/index.js

@@ -100,7 +100,7 @@ export const constantRoutes = [
     children: [
       {
         path: 'log',
-        component: (resolve) => require(['@/views/monitor/job/log'], resolve),
+        component: (resolve) => require(['@/views/infra/job/log'], resolve),
         name: 'JobLog',
         meta: { title: '调度日志' }
       }

+ 10 - 0
ruoyi-ui/src/utils/constants.js

@@ -48,3 +48,13 @@ export const ToolCodegenTemplateTypeEnum = {
   TREE: 2, // 树形 CRUD
   SUB: 3, // 主子表 CRUD
 }
+
+/**
+ * 任务状态的枚举
+ */
+export const InfJobStatusEnum = {
+  INIT: 0, // 初始化中
+  NORMAL: 1, // 开启运行
+  EXCEPTION: 2, // 异常运行
+  STOP: 3, // 暂停运行
+}

+ 26 - 26
ruoyi-ui/src/views/monitor/druid/index.vue → ruoyi-ui/src/views/infra/druid/index.vue

@@ -1,26 +1,26 @@
-<template>
-  <div v-loading="loading" :style="'height:'+ height">
-    <iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
-  </div>
-</template>
-<script>
-export default {
-  name: "Druid",
-  data() {
-    return {
-      src: process.env.VUE_APP_BASE_API + "/druid/index.html",
-      height: document.documentElement.clientHeight - 94.5 + "px;",
-      loading: true
-    };
-  },
-  mounted: function() {
-    setTimeout(() => {
-      this.loading = false;
-    }, 230);
-    const that = this;
-    window.onresize = function temp() {
-      that.height = document.documentElement.clientHeight - 94.5 + "px;";
-    };
-  }
-};
-</script>
+<template>
+  <div v-loading="loading" :style="'height:'+ height">
+    <iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
+  </div>
+</template>
+<script>
+export default {
+  name: "Druid",
+  data() {
+    return {
+      src: process.env.VUE_APP_BASE_API + "/druid/index.html",
+      height: document.documentElement.clientHeight - 94.5 + "px;",
+      loading: true
+    };
+  },
+  mounted: function() {
+    setTimeout(() => {
+      this.loading = false;
+    }, 230);
+    const that = this;
+    window.onresize = function temp() {
+      that.height = document.documentElement.clientHeight - 94.5 + "px;";
+    };
+  }
+};
+</script>

+ 308 - 305
ruoyi-ui/src/views/monitor/job/index.vue → ruoyi-ui/src/views/infra/job/index.vue

@@ -1,305 +1,308 @@
-<template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-      <el-form-item label="任务名称" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable size="small" @keyup.enter.native="handleQuery"/>
-      </el-form-item>
-      <el-form-item label="任务状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable size="small">
-          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.INF_JOB_STATUS)"
-                     :key="dict.value" :label="dict.label" :value="dict.value"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="处理器的名字" prop="handlerName">
-        <el-input v-model="queryParams.handlerName" placeholder="请输入处理器的名字" clearable size="small" @keyup.enter.native="handleQuery"/>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
-                   v-hasPermi="['monitor:job:add']">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
-                   v-hasPermi="['monitor:job:export']">导出</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="info" icon="el-icon-s-operation" size="mini" @click="handleJobLog"
-                   v-hasPermi="['monitor:job:query']">日志</el-button>
-      </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="jobList">
-      <el-table-column label="任务编号" align="center" prop="id" />
-      <el-table-column label="任务名称" align="center" prop="name" />
-      <el-table-column label="任务状态" align="center" prop="status">
-        <template slot-scope="scope">
-          <span>{{ getDictDataLabel(DICT_TYPE.INF_JOB_STATUS, scope.row.status) }}</span>
-        </template>
-      </el-table-column>>
-      <el-table-column label="处理器的名字" align="center" prop="handlerName" />
-      <el-table-column label="处理器的参数" align="center" prop="handlerParam" />
-      <el-table-column label="CRON 表达式" align="center" prop="cronExpression" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"
-                     v-hasPermi="['monitor:job:query']">详细</el-button>
-          <el-button size="mini" type="text" icon="el-icon-view" @click="handleUpdate(scope.row)"
-                     v-hasPermi="['monitor:job:query']">修改</el-button>
-          <el-button
-              size="mini"
-              type="text"
-              icon="el-icon-caret-right"
-              @click="handleRun(scope.row)"
-              v-hasPermi="['monitor:job:changeStatus']"
-          >执行一次</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页组件 -->
-    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
-                @pagination="getList"/>
-
-    <!-- 添加或修改定时任务对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-        <el-form-item label="任务名称" prop="name">
-          <el-input v-model="form.name" placeholder="请输入任务名称" />
-        </el-form-item>
-        <el-form-item label="处理器的名字" prop="handlerName">
-          <el-input v-model="form.handlerName" placeholder="请输入处理器的名字" v-bind:readonly="form.id !== undefined" />
-        </el-form-item>
-        <el-form-item label="处理器的参数" prop="handlerParam">
-          <el-input v-model="form.handlerParam" placeholder="请输入处理器的参数" />
-        </el-form-item>
-        <el-form-item label="CRON 表达式" prop="cronExpression">
-          <el-input v-model="form.cronExpression" placeholder="请输入CRON 表达式" />
-        </el-form-item>
-        <el-form-item label="监控超时时间" prop="monitorTimeout">
-          <el-input v-model="form.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" />
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
-
-    <!-- 任务日志详细 -->
-    <el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body>
-      <el-form ref="form" :model="form" label-width="200px" size="mini">
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="任务编号:">{{ form.id }}</el-form-item>
-            <el-form-item label="任务名称:">{{ form.name }}</el-form-item>
-            <el-form-item label="任务名称:">{{ getDictDataLabel(DICT_TYPE.INF_JOB_STATUS, form.status) }}</el-form-item>
-            <el-form-item label="处理器的名字:">{{ form.handlerName }}</el-form-item>
-            <el-form-item label="处理器的参数:">{{ form.handlerParam }}</el-form-item>
-            <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
-            <el-form-item label="最后一次执行的开始时间:">{{ parseTime(form.executeBeginTime) }}</el-form-item>
-            <el-form-item label="最后一次执行的开始时间:">{{ parseTime(form.executeEndTime) }}</el-form-item>
-            <el-form-item label="上一次触发时间:">{{ parseTime(form.firePrevTime) }}</el-form-item>
-            <el-form-item label="下一次触发时间:">{{ parseTime(form.fireNextTime) }}</el-form-item>
-            <el-form-item label="监控超时时间:">{{ form.monitorTimeout > 0 ? form.monitorTimeout + " 毫秒" : "未开启" }}</el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="openView = false">关 闭</el-button>
-      </div>
-    </el-dialog>
-
-  </div>
-</template>
-
-<script>
-import { listJob, getJob, delJob, addJob, updateJob, exportJob, runJob, changeJobStatus } from "@/api/monitor/job";
-
-export default {
-  name: "Job",
-  data() {
-    return {
-      // 遮罩层
-      loading: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 定时任务表格数据
-      jobList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
-      // 是否显示详细弹出层
-      openView: false,
-      // 状态字典
-      statusOptions: [],
-      // 查询参数
-      queryParams: {
-        pageNo: 1,
-        pageSize: 10,
-        name: undefined,
-        status: undefined,
-        handlerName: undefined
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        name: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
-        handlerName: [{ required: true, message: "处理器的名字不能为空", trigger: "blur" }],
-        cronExpression: [{ required: true, message: "CRON 表达式不能为空", trigger: "blur" }],
-      }
-    };
-  },
-  created() {
-    this.getList();
-  },
-  methods: {
-    /** 查询定时任务列表 */
-    getList() {
-      this.loading = true;
-      listJob(this.queryParams).then(response => {
-        this.jobList = response.data.list;
-        this.total = response.data.total;
-        this.loading = false;
-      });
-    },
-    /** 取消按钮 */
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    /** 表单重置 */
-    reset() {
-      this.form = {
-        id: undefined,
-        name: undefined,
-        handlerName: undefined,
-        handlerParam: undefined,
-        cronExpression: undefined,
-        monitorTimeout: undefined,
-      };
-      this.resetForm("form");
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNo = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.handleQuery();
-    },
-    // 任务状态修改
-    handleStatusChange(row) {
-      let text = row.status === "0" ? "启用" : "停用";
-      this.$confirm('确认要"' + text + '""' + row.name + '"任务吗?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return changeJobStatus(row.id, row.status);
-        }).then(() => {
-          this.msgSuccess(text + "成功");
-        }).catch(function() {
-          row.status = row.status === "0" ? "1" : "0";
-        });
-    },
-    /* 立即执行一次 */
-    handleRun(row) {
-      this.$confirm('确认要立即执行一次"' + row.name + '"任务吗?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return runJob(row.id, row.jobGroup);
-        }).then(() => {
-          this.msgSuccess("执行成功");
-        })
-    },
-    /** 任务详细信息 */
-    handleView(row) {
-      getJob(row.id).then(response => {
-        this.form = response.data;
-        this.openView = true;
-      });
-    },
-    /** 任务日志列表查询 */
-    handleJobLog() {
-      this.$router.push("/job/log");
-    },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = "添加任务";
-    },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const id = row.id;
-      getJob(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = "修改任务";
-      });
-    },
-    /** 提交按钮 */
-    submitForm: function() {
-      this.$refs["form"].validate(valid => {
-        if (valid) {
-          if (this.form.id !== undefined) {
-            updateJob(this.form).then(response => {
-              this.msgSuccess("修改成功");
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addJob(this.form).then(response => {
-              this.msgSuccess("新增成功");
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
-    },
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const ids = row.id || this.ids;
-      this.$confirm('是否确认删除定时任务编号为"' + ids + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delJob(ids);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        })
-    },
-    /** 导出按钮操作 */
-    handleExport() {
-      const queryParams = this.queryParams;
-      this.$confirm("是否确认导出所有定时任务数据项?", "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportJob(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        })
-    }
-  }
-};
-</script>
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="任务名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="任务状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.INF_JOB_STATUS)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="处理器的名字" prop="handlerName">
+        <el-input v-model="queryParams.handlerName" placeholder="请输入处理器的名字" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['infra:job:create']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['infra:job:export']">导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" icon="el-icon-s-operation" size="mini" @click="handleJobLog"
+                   v-hasPermi="['monitor:job:query']">日志</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="jobList">
+      <el-table-column label="任务编号" align="center" prop="id" />
+      <el-table-column label="任务名称" align="center" prop="name" />
+      <el-table-column label="任务状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <span>{{ getDictDataLabel(DICT_TYPE.INF_JOB_STATUS, scope.row.status) }}</span>
+        </template>
+      </el-table-column>>
+      <el-table-column label="处理器的名字" align="center" prop="handlerName" />
+      <el-table-column label="处理器的参数" align="center" prop="handlerParam" />
+      <el-table-column label="CRON 表达式" align="center" prop="cronExpression" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"
+                     v-hasPermi="['infra:job:query']">详细</el-button>
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['infra:job:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-check" @click="handleChangeStatus(scope.row, true)"
+                     v-hasPermi="['infra:job:update']">开启</el-button>
+          <el-button size="mini" type="text" icon="el-icon-close" @click="handleChangeStatus(scope.row, false)"
+                     v-hasPermi="['infra:job:update']">暂停</el-button>
+          <el-button size="mini" type="text" icon="el-icon-caret-right" @click="handleRun(scope.row)"
+                     v-hasPermi="['infra:job:trigger']">执行一次</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['infra:job:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 添加或修改定时任务对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="任务名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入任务名称" />
+        </el-form-item>
+        <el-form-item label="处理器的名字" prop="handlerName">
+          <el-input v-model="form.handlerName" placeholder="请输入处理器的名字" v-bind:readonly="form.id !== undefined" />
+        </el-form-item>
+        <el-form-item label="处理器的参数" prop="handlerParam">
+          <el-input v-model="form.handlerParam" placeholder="请输入处理器的参数" />
+        </el-form-item>
+        <el-form-item label="CRON 表达式" prop="cronExpression">
+          <el-input v-model="form.cronExpression" placeholder="请输入CRON 表达式" />
+        </el-form-item>
+        <el-form-item label="监控超时时间" prop="monitorTimeout">
+          <el-input v-model="form.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 任务日志详细 -->
+    <el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body>
+      <el-form ref="form" :model="form" label-width="200px" size="mini">
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="任务编号:">{{ form.id }}</el-form-item>
+            <el-form-item label="任务名称:">{{ form.name }}</el-form-item>
+            <el-form-item label="任务名称:">{{ getDictDataLabel(DICT_TYPE.INF_JOB_STATUS, form.status) }}</el-form-item>
+            <el-form-item label="处理器的名字:">{{ form.handlerName }}</el-form-item>
+            <el-form-item label="处理器的参数:">{{ form.handlerParam }}</el-form-item>
+            <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
+            <el-form-item label="最后一次执行的开始时间:">{{ parseTime(form.executeBeginTime) }}</el-form-item>
+            <el-form-item label="最后一次执行的开始时间:">{{ parseTime(form.executeEndTime) }}</el-form-item>
+            <el-form-item label="上一次触发时间:">{{ parseTime(form.firePrevTime) }}</el-form-item>
+            <el-form-item label="下一次触发时间:">{{ parseTime(form.fireNextTime) }}</el-form-item>
+            <el-form-item label="监控超时时间:">{{ form.monitorTimeout > 0 ? form.monitorTimeout + " 毫秒" : "未开启" }}</el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="openView = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import { listJob, getJob, delJob, addJob, updateJob, exportJob, runJob, updateJobStatus } from "@/api/infra/job";
+import { InfJobStatusEnum } from "@/utils/constants";
+
+export default {
+  name: "Job",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 定时任务表格数据
+      jobList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否显示详细弹出层
+      openView: false,
+      // 状态字典
+      statusOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: undefined,
+        status: undefined,
+        handlerName: undefined
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
+        handlerName: [{ required: true, message: "处理器的名字不能为空", trigger: "blur" }],
+        cronExpression: [{ required: true, message: "CRON 表达式不能为空", trigger: "blur" }],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询定时任务列表 */
+    getList() {
+      this.loading = true;
+      listJob(this.queryParams).then(response => {
+        this.jobList = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        handlerName: undefined,
+        handlerParam: undefined,
+        cronExpression: undefined,
+        monitorTimeout: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 立即执行一次 **/
+    handleRun(row) {
+      this.$confirm('确认要立即执行一次"' + row.name + '"任务吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return runJob(row.id);
+        }).then(() => {
+          this.msgSuccess("执行成功");
+        })
+    },
+    /** 任务详细信息 */
+    handleView(row) {
+      getJob(row.id).then(response => {
+        this.form = response.data;
+        this.openView = true;
+      });
+    },
+    /** 任务日志列表查询 */
+    handleJobLog() {
+      this.$router.push("/job/log");
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加任务";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getJob(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改任务";
+      });
+    },
+    /** 提交按钮 */
+    submitForm: function() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id !== undefined) {
+            updateJob(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addJob(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id;
+      this.$confirm('是否确认删除定时任务编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delJob(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        })
+    },
+    /** 更新状态操作 */
+    handleChangeStatus(row, open) {
+      const id = row.id;
+      let status = open ? InfJobStatusEnum.NORMAL : InfJobStatusEnum.STOP;
+      let statusStr = open ? '开启' : '关闭';
+      this.$confirm('是否确认' + statusStr + '定时任务编号为"' + id + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return updateJobStatus(id, status);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess(statusStr + "成功");
+      })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm("是否确认导出所有定时任务数据项?", "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return exportJob(queryParams);
+        }).then(response => {
+          this.downloadExcel(response, '定时任务.xls');
+        })
+    }
+  }
+};
+</script>

+ 0 - 0
ruoyi-ui/src/views/monitor/job/log.vue → ruoyi-ui/src/views/infra/job/log.vue


+ 209 - 209
ruoyi-ui/src/views/monitor/server/index.vue → ruoyi-ui/src/views/infra/server/index.vue

@@ -1,210 +1,210 @@
-<template>
-  <div class="app-container">
-    <el-row>
-      <el-col :span="12" class="card-box">
-        <el-card>
-          <div slot="header"><span>CPU</span></div>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table cellspacing="0" style="width: 100%;">
-              <thead>
-                <tr>
-                  <th class="is-leaf"><div class="cell">属性</div></th>
-                  <th class="is-leaf"><div class="cell">值</div></th>
-                </tr>
-              </thead>
-              <tbody>
-                <tr>
-                  <td><div class="cell">核心数</div></td>
-                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">用户使用率</div></td>
-                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">系统使用率</div></td>
-                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">当前空闲率</div></td>
-                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :span="12" class="card-box">
-        <el-card>
-          <div slot="header"><span>内存</span></div>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table cellspacing="0" style="width: 100%;">
-              <thead>
-                <tr>
-                  <th class="is-leaf"><div class="cell">属性</div></th>
-                  <th class="is-leaf"><div class="cell">内存</div></th>
-                  <th class="is-leaf"><div class="cell">JVM</div></th>
-                </tr>
-              </thead>
-              <tbody>
-                <tr>
-                  <td><div class="cell">总内存</div></td>
-                  <td><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">已用内存</div></td>
-                  <td><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">剩余内存</div></td>
-                  <td><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">使用率</div></td>
-                  <td><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
-                  <td><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :span="24" class="card-box">
-        <el-card>
-          <div slot="header">
-            <span>服务器信息</span>
-          </div>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table cellspacing="0" style="width: 100%;">
-              <tbody>
-                <tr>
-                  <td><div class="cell">服务器名称</div></td>
-                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
-                  <td><div class="cell">操作系统</div></td>
-                  <td><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">服务器IP</div></td>
-                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
-                  <td><div class="cell">系统架构</div></td>
-                  <td><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :span="24" class="card-box">
-        <el-card>
-          <div slot="header">
-            <span>Java虚拟机信息</span>
-          </div>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table cellspacing="0" style="width: 100%;">
-              <tbody>
-                <tr>
-                  <td><div class="cell">Java名称</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
-                  <td><div class="cell">Java版本</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
-                </tr>
-                <tr>
-                  <td><div class="cell">启动时间</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
-                  <td><div class="cell">运行时长</div></td>
-                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
-                </tr>
-                <tr>
-                  <td colspan="1"><div class="cell">安装路径</div></td>
-                  <td colspan="3"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
-                </tr>
-                <tr>
-                  <td colspan="1"><div class="cell">项目路径</div></td>
-                  <td colspan="3"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :span="24" class="card-box">
-        <el-card>
-          <div slot="header">
-            <span>磁盘状态</span>
-          </div>
-          <div class="el-table el-table--enable-row-hover el-table--medium">
-            <table cellspacing="0" style="width: 100%;">
-              <thead>
-                <tr>
-                  <th class="is-leaf"><div class="cell">盘符路径</div></th>
-                  <th class="is-leaf"><div class="cell">文件系统</div></th>
-                  <th class="is-leaf"><div class="cell">盘符类型</div></th>
-                  <th class="is-leaf"><div class="cell">总大小</div></th>
-                  <th class="is-leaf"><div class="cell">可用大小</div></th>
-                  <th class="is-leaf"><div class="cell">已用大小</div></th>
-                  <th class="is-leaf"><div class="cell">已用百分比</div></th>
-                </tr>
-              </thead>
-              <tbody v-if="server.sysFiles">
-                <tr v-for="sysFile in server.sysFiles">
-                  <td><div class="cell">{{ sysFile.dirName }}</div></td>
-                  <td><div class="cell">{{ sysFile.sysTypeName }}</div></td>
-                  <td><div class="cell">{{ sysFile.typeName }}</div></td>
-                  <td><div class="cell">{{ sysFile.total }}</div></td>
-                  <td><div class="cell">{{ sysFile.free }}</div></td>
-                  <td><div class="cell">{{ sysFile.used }}</div></td>
-                  <td><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import { getServer } from "@/api/monitor/server";
-
-export default {
-  name: "Server",
-  data() {
-    return {
-      // 加载层信息
-      loading: [],
-      // 服务器信息
-      server: []
-    };
-  },
-  created() {
-    this.getList();
-    this.openLoading();
-  },
-  methods: {
-    /** 查询服务器信息 */
-    getList() {
-      getServer().then(response => {
-        this.server = response.data;
-        this.loading.close();
-      });
-    },
-    // 打开加载层
-    openLoading() {
-      this.loading = this.$loading({
-        lock: true,
-        text: "拼命读取中",
-        spinner: "el-icon-loading",
-        background: "rgba(0, 0, 0, 0.7)"
-      });
-    }
-  }
-};
+<template>
+  <div class="app-container">
+    <el-row>
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <div slot="header"><span>CPU</span></div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="is-leaf"><div class="cell">属性</div></th>
+                  <th class="is-leaf"><div class="cell">值</div></th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td><div class="cell">核心数</div></td>
+                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">用户使用率</div></td>
+                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">系统使用率</div></td>
+                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">当前空闲率</div></td>
+                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <div slot="header"><span>内存</span></div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="is-leaf"><div class="cell">属性</div></th>
+                  <th class="is-leaf"><div class="cell">内存</div></th>
+                  <th class="is-leaf"><div class="cell">JVM</div></th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td><div class="cell">总内存</div></td>
+                  <td><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">已用内存</div></td>
+                  <td><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">剩余内存</div></td>
+                  <td><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">使用率</div></td>
+                  <td><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
+                  <td><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <div slot="header">
+            <span>服务器信息</span>
+          </div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <tbody>
+                <tr>
+                  <td><div class="cell">服务器名称</div></td>
+                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
+                  <td><div class="cell">操作系统</div></td>
+                  <td><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">服务器IP</div></td>
+                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
+                  <td><div class="cell">系统架构</div></td>
+                  <td><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <div slot="header">
+            <span>Java虚拟机信息</span>
+          </div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <tbody>
+                <tr>
+                  <td><div class="cell">Java名称</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
+                  <td><div class="cell">Java版本</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
+                </tr>
+                <tr>
+                  <td><div class="cell">启动时间</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
+                  <td><div class="cell">运行时长</div></td>
+                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
+                </tr>
+                <tr>
+                  <td colspan="1"><div class="cell">安装路径</div></td>
+                  <td colspan="3"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
+                </tr>
+                <tr>
+                  <td colspan="1"><div class="cell">项目路径</div></td>
+                  <td colspan="3"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <div slot="header">
+            <span>磁盘状态</span>
+          </div>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="is-leaf"><div class="cell">盘符路径</div></th>
+                  <th class="is-leaf"><div class="cell">文件系统</div></th>
+                  <th class="is-leaf"><div class="cell">盘符类型</div></th>
+                  <th class="is-leaf"><div class="cell">总大小</div></th>
+                  <th class="is-leaf"><div class="cell">可用大小</div></th>
+                  <th class="is-leaf"><div class="cell">已用大小</div></th>
+                  <th class="is-leaf"><div class="cell">已用百分比</div></th>
+                </tr>
+              </thead>
+              <tbody v-if="server.sysFiles">
+                <tr v-for="sysFile in server.sysFiles">
+                  <td><div class="cell">{{ sysFile.dirName }}</div></td>
+                  <td><div class="cell">{{ sysFile.sysTypeName }}</div></td>
+                  <td><div class="cell">{{ sysFile.typeName }}</div></td>
+                  <td><div class="cell">{{ sysFile.total }}</div></td>
+                  <td><div class="cell">{{ sysFile.free }}</div></td>
+                  <td><div class="cell">{{ sysFile.used }}</div></td>
+                  <td><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { getServer } from "@/api/monitor/server";
+
+export default {
+  name: "Server",
+  data() {
+    return {
+      // 加载层信息
+      loading: [],
+      // 服务器信息
+      server: []
+    };
+  },
+  created() {
+    this.getList();
+    this.openLoading();
+  },
+  methods: {
+    /** 查询服务器信息 */
+    getList() {
+      getServer().then(response => {
+        this.server = response.data;
+        this.loading.close();
+      });
+    },
+    // 打开加载层
+    openLoading() {
+      this.loading = this.$loading({
+        lock: true,
+        text: "拼命读取中",
+        spinner: "el-icon-loading",
+        background: "rgba(0, 0, 0, 0.7)"
+      });
+    }
+  }
+};
 </script>

Diff do ficheiro suprimidas por serem muito extensas
+ 66 - 20
sql/ruoyi-vue-pro.sql


+ 4 - 0
src/main/java/cn/iocoder/dashboard/framework/mybatis/core/mapper/BaseMapperX.java

@@ -28,4 +28,8 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
         return selectList(new QueryWrapper<>());
     }
 
+    default T selectOne(String field, Object value) {
+        return selectOne(new QueryWrapper<T>().eq(field, value));
+    }
+
 }

+ 49 - 0
src/main/java/cn/iocoder/dashboard/framework/quartz/core/scheduler/SchedulerManager.java

@@ -7,6 +7,12 @@ import org.quartz.*;
 /**
  * {@link org.quartz.Scheduler} 的管理器,负责创建任务
  *
+ * 考虑到实现的简洁性,我们使用 jobHandlerName 作为唯一标识,即:
+ * 1. Job 的 {@link JobDetail#getKey()}
+ * 2. Trigger 的 {@link Trigger#getKey()}
+ *
+ * 另外,jobHandlerName 对应到 Spring Bean 的名字,直接调用
+ *
  * @author 芋道源码
  */
 public class SchedulerManager {
@@ -17,6 +23,15 @@ public class SchedulerManager {
         this.scheduler = scheduler;
     }
 
+    /**
+     * 添加 Job 到 Quartz 中
+     *
+     * @param jobId 任务编号
+     * @param jobHandlerName 任务处理器的名字
+     * @param jobHandlerParam 任务处理器的参数
+     * @param cronExpression CRON 表达式
+     * @throws SchedulerException 添加异常
+     */
     public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression)
             throws SchedulerException {
         // 创建 JobDetail 对象
@@ -30,6 +45,14 @@ public class SchedulerManager {
         scheduler.scheduleJob(jobDetail, trigger);
     }
 
+    /**
+     * 更新 Job 到 Quartz
+     *
+     * @param jobHandlerName 任务处理器的名字
+     * @param jobHandlerParam 任务处理器的参数
+     * @param cronExpression CRON 表达式
+     * @throws SchedulerException 更新异常
+     */
     public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression)
             throws SchedulerException {
         // 创建新 Trigger 对象
@@ -38,19 +61,45 @@ public class SchedulerManager {
         scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);
     }
 
+    /**
+     * 删除 Quartz 中的 Job
+     *
+     * @param jobHandlerName 任务处理器的名字
+     * @throws SchedulerException 删除异常
+     */
     public void deleteJob(String jobHandlerName) throws SchedulerException {
         scheduler.deleteJob(new JobKey(jobHandlerName));
     }
 
+    /**
+     * 暂停 Quartz 中的 Job
+     *
+     * @param jobHandlerName 任务处理器的名字
+     * @throws SchedulerException 暂停异常
+     */
     public void pauseJob(String jobHandlerName) throws SchedulerException {
         scheduler.pauseJob(new JobKey(jobHandlerName));
     }
 
+    /**
+     * 启动 Quartz 中的 Job
+     *
+     * @param jobHandlerName 任务处理器的名字
+     * @throws SchedulerException 启动异常
+     */
     public void resumeJob(String jobHandlerName) throws SchedulerException {
         scheduler.resumeJob(new JobKey(jobHandlerName));
         scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
     }
 
+    /**
+     * 立即触发一次 Quartz 中的 Job
+     *
+     * @param jobId 任务编号
+     * @param jobHandlerName 任务处理器的名字
+     * @param jobHandlerParam 任务处理器的参数
+     * @throws SchedulerException 触发异常
+     */
     public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)
             throws SchedulerException {
         JobDataMap data = new JobDataMap();

+ 22 - 0
src/main/java/cn/iocoder/dashboard/framework/quartz/core/util/CronUtils.java

@@ -0,0 +1,22 @@
+package cn.iocoder.dashboard.framework.quartz.core.util;
+
+import org.quartz.CronExpression;
+
+/**
+ * Quartz Cron 表达式的工具类
+ *
+ * @author 芋道源码
+ */
+public class CronUtils {
+
+    /**
+     * 校验 CRON 表达式是否有效
+     *
+     * @param cronExpression CRON 表达式
+     * @return 是否有效
+     */
+    public static boolean isValid(String cronExpression) {
+        return CronExpression.isValidExpression(cronExpression);
+    }
+
+}

+ 32 - 5
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobController.java

@@ -10,7 +10,9 @@ import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
 import cn.iocoder.dashboard.modules.infra.service.job.InfJobService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
+import org.quartz.SchedulerException;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -37,30 +39,55 @@ public class InfJobController {
     @PostMapping("/create")
     @ApiOperation("创建定时任务")
     @PreAuthorize("@ss.hasPermission('infra:job:create')")
-    public CommonResult<Long> createJob(@Valid @RequestBody InfJobCreateReqVO createReqVO) {
+    public CommonResult<Long> createJob(@Valid @RequestBody InfJobCreateReqVO createReqVO)
+            throws SchedulerException {
         return success(jobService.createJob(createReqVO));
     }
 
     @PutMapping("/update")
     @ApiOperation("更新定时任务")
     @PreAuthorize("@ss.hasPermission('infra:job:update')")
-    public CommonResult<Boolean> updateJob(@Valid @RequestBody InfJobUpdateReqVO updateReqVO) {
+    public CommonResult<Boolean> updateJob(@Valid @RequestBody InfJobUpdateReqVO updateReqVO)
+            throws SchedulerException {
         jobService.updateJob(updateReqVO);
         return success(true);
     }
 
+    @PutMapping("/update-status")
+    @ApiOperation("更新定时任务的状态")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class),
+            @ApiImplicitParam(name = "status", value = "状态", required = true, example = "1", dataTypeClass = Integer.class),
+    })
+    @PreAuthorize("@ss.hasPermission('infra:job:update')")
+    public CommonResult<Boolean> updateJobStatus(@RequestParam(value = "id") Long id, @RequestParam("status") Integer status)
+            throws SchedulerException {
+        jobService.updateJobStatus(id, status);
+        return success(true);
+    }
+
 	@DeleteMapping("/delete")
     @ApiOperation("删除定时任务")
-    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
 	@PreAuthorize("@ss.hasPermission('infra:job:delete')")
-    public CommonResult<Boolean> deleteJob(@RequestParam("id") Long id) {
+    public CommonResult<Boolean> deleteJob(@RequestParam("id") Long id)
+            throws SchedulerException {
         jobService.deleteJob(id);
         return success(true);
     }
 
+    @PutMapping("/trigger")
+    @ApiOperation("触发定时任务")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('infra:job:trigger')")
+    public CommonResult<Boolean> triggerJob(@RequestParam("id") Long id) throws SchedulerException {
+        jobService.triggerJob(id);
+        return success(true);
+    }
+
     @GetMapping("/get")
     @ApiOperation("获得定时任务")
-    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
     @PreAuthorize("@ss.hasPermission('infra:job:query')")
     public CommonResult<InfJobRespVO> getJob(@RequestParam("id") Long id) {
         InfJobDO job = jobService.getJob(id);

+ 4 - 6
src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/job/InfJobDO.java

@@ -2,13 +2,9 @@ package cn.iocoder.dashboard.modules.infra.dal.dataobject.job;
 
 import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum;
-import com.baomidou.mybatisplus.annotation.FieldStrategy;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
+import lombok.*;
 
 import java.util.Date;
 
@@ -21,6 +17,9 @@ import java.util.Date;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class InfJobDO extends BaseDO {
 
     /**
@@ -45,7 +44,6 @@ public class InfJobDO extends BaseDO {
     /**
      * 处理器的参数
      */
-    @TableField(updateStrategy = FieldStrategy.IGNORED)
     private String handlerParam;
 
     // ========== 时间相关字段 ==========

+ 4 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/job/InfJobMapper.java

@@ -18,6 +18,10 @@ import java.util.List;
 @Mapper
 public interface InfJobMapper extends BaseMapperX<InfJobDO> {
 
+    default InfJobDO selectByHandlerName(String handlerName) {
+        return selectOne("handler_name", handlerName);
+    }
+
     default PageResult<InfJobDO> selectPage(InfJobPageReqVO reqVO) {
         return selectPage(reqVO, new QueryWrapperX<InfJobDO>()
                 .likeIfPresent("name", reqVO.getName())

+ 5 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java

@@ -17,5 +17,10 @@ public interface InfErrorCodeConstants {
 
     // ========== 定时任务 1001001000 ==========
     ErrorCode JOB_NOT_EXISTS = new ErrorCode(1001001000, "定时任务不存在");
+    ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1001001001, "定时任务的处理器已经存在");
+    ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1001001002, "只允许修改为开启或者关闭状态");
+    ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1001001003, "定时任务已经处于该状态,无需修改");
+    ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1001001004, "只有开启状态的任务,才可以修改");
+    ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1001001005, "CRON 表达式不正确");
 
 }

+ 19 - 3
src/main/java/cn/iocoder/dashboard/modules/infra/service/job/InfJobService.java

@@ -6,6 +6,7 @@ import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobExportReqV
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO;
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobUpdateReqVO;
 import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
+import org.quartz.SchedulerException;
 
 import javax.validation.Valid;
 import java.util.Collection;
@@ -24,21 +25,36 @@ public interface InfJobService {
      * @param createReqVO 创建信息
      * @return 编号
      */
-    Long createJob(@Valid InfJobCreateReqVO createReqVO);
+    Long createJob(@Valid InfJobCreateReqVO createReqVO) throws SchedulerException;
 
     /**
      * 更新定时任务
      *
      * @param updateReqVO 更新信息
      */
-    void updateJob(@Valid InfJobUpdateReqVO updateReqVO);
+    void updateJob(@Valid InfJobUpdateReqVO updateReqVO) throws SchedulerException;
+
+    /**
+     * 更新定时任务的状态
+     *
+     * @param id 任务编号
+     * @param status 状态
+     */
+    void updateJobStatus(Long id, Integer status) throws SchedulerException;
+
+    /**
+     * 触发定时任务
+     *
+     * @param id 任务编号
+     */
+    void triggerJob(Long id) throws SchedulerException;
 
     /**
      * 删除定时任务
      *
      * @param id 编号
      */
-    void deleteJob(Long id);
+    void deleteJob(Long id) throws SchedulerException;
 
     /**
      * 获得定时任务

+ 95 - 15
src/main/java/cn/iocoder/dashboard/modules/infra/service/job/impl/InfJobServiceImpl.java

@@ -1,7 +1,8 @@
 package cn.iocoder.dashboard.modules.infra.service.job.impl;
 
-import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.quartz.core.scheduler.SchedulerManager;
+import cn.iocoder.dashboard.framework.quartz.core.util.CronUtils;
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobCreateReqVO;
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobExportReqVO;
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO;
@@ -9,15 +10,20 @@ import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobUpdateReqV
 import cn.iocoder.dashboard.modules.infra.convert.job.InfJobConvert;
 import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
 import cn.iocoder.dashboard.modules.infra.dal.mysql.job.InfJobMapper;
+import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum;
 import cn.iocoder.dashboard.modules.infra.service.job.InfJobService;
+import org.quartz.SchedulerException;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
 
-import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.JOB_NOT_EXISTS;
+import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
+import static cn.iocoder.dashboard.util.collection.CollectionUtils.containsAny;
 
 /**
  * 定时任务 Service 实现类
@@ -31,41 +37,109 @@ public class InfJobServiceImpl implements InfJobService {
     @Resource
     private InfJobMapper jobMapper;
 
+    @Resource
+    private SchedulerManager schedulerManager;
+
     @Override
-    public Long createJob(InfJobCreateReqVO createReqVO) {
+    @Transactional
+    public Long createJob(InfJobCreateReqVO createReqVO) throws SchedulerException {
+        validateCronExpression(createReqVO.getCronExpression());
+        // 校验唯一性
+        if (jobMapper.selectByHandlerName(createReqVO.getHandlerName()) != null) {
+            throw exception(JOB_HANDLER_EXISTS);
+        }
         // 插入
         InfJobDO job = InfJobConvert.INSTANCE.convert(createReqVO);
-        if (job.getMonitorTimeout() == null) {
-            job.setMonitorTimeout(0);
-        }
+        job.setStatus(InfJobStatusEnum.INIT.getStatus());
+        fillJobMonitorTimeoutEmpty(job);
         jobMapper.insert(job);
+
+        // 添加 Job 到 Quartz 中
+        schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression());
+        // 更新
+        InfJobDO updateObj = InfJobDO.builder().id(job.getId()).status(InfJobStatusEnum.NORMAL.getStatus()).build();
+        jobMapper.updateById(updateObj);
+
         // 返回
         return job.getId();
     }
 
     @Override
-    public void updateJob(InfJobUpdateReqVO updateReqVO) {
+    @Transactional
+    public void updateJob(InfJobUpdateReqVO updateReqVO) throws SchedulerException {
+        validateCronExpression(updateReqVO.getCronExpression());
         // 校验存在
-        this.validateJobExists(updateReqVO.getId());
+        InfJobDO job = this.validateJobExists(updateReqVO.getId());
+        // 只有开启状态,才可以修改.原因是,如果出暂停状态,修改 Quartz Job 时,会导致任务又开始执行
+        if (!job.getStatus().equals(InfJobStatusEnum.NORMAL.getStatus())) {
+            throw exception(JOB_UPDATE_ONLY_NORMAL_STATUS);
+        }
         // 更新
         InfJobDO updateObj = InfJobConvert.INSTANCE.convert(updateReqVO);
-        if (updateObj.getMonitorTimeout() == null) {
-            updateObj.setMonitorTimeout(0);
+        fillJobMonitorTimeoutEmpty(updateObj);
+        jobMapper.updateById(updateObj);
+
+        // 更新 Job 到 Quartz 中
+        schedulerManager.updateJob(job.getHandlerName(), updateReqVO.getHandlerParam(), updateReqVO.getCronExpression());
+    }
+
+    @Override
+    @Transactional
+    public void updateJobStatus(Long id, Integer status) throws SchedulerException {
+        // 校验 status
+        if (!containsAny(status, InfJobStatusEnum.NORMAL.getStatus(), InfJobStatusEnum.STOP.getStatus())) {
+            throw exception(JOB_CHANGE_STATUS_INVALID);
+        }
+        // 校验存在
+        InfJobDO job = this.validateJobExists(id);
+        // 校验是否已经为当前状态
+        if (job.getStatus().equals(status)) {
+            throw exception(JOB_CHANGE_STATUS_EQUALS);
         }
+        // 更新 Job 状态
+        InfJobDO updateObj = InfJobDO.builder().id(id).status(status).build();
         jobMapper.updateById(updateObj);
+
+        // 更新状态 Job 到 Quartz 中
+        if (InfJobStatusEnum.NORMAL.getStatus().equals(status)) { // 开启
+            schedulerManager.resumeJob(job.getHandlerName());
+        } else { // 暂停
+            schedulerManager.pauseJob(job.getHandlerName());
+        }
     }
 
     @Override
-    public void deleteJob(Long id) {
+    public void triggerJob(Long id) throws SchedulerException {
         // 校验存在
-        this.validateJobExists(id);
+        InfJobDO job = this.validateJobExists(id);
+
+        // 触发 Quartz 中的 Job
+        schedulerManager.triggerJob(job.getId(), job.getHandlerName(), job.getHandlerParam());
+    }
+
+    @Override
+    @Transactional
+    public void deleteJob(Long id) throws SchedulerException {
+        // 校验存在
+        InfJobDO job = this.validateJobExists(id);
         // 更新
         jobMapper.deleteById(id);
+
+        // 删除 Job 到 Quartz 中
+        schedulerManager.deleteJob(job.getHandlerName());
     }
 
-    private void validateJobExists(Long id) {
-        if (jobMapper.selectById(id) == null) {
-            throw ServiceExceptionUtil.exception(JOB_NOT_EXISTS);
+    private InfJobDO validateJobExists(Long id) {
+        InfJobDO job = jobMapper.selectById(id);
+        if (job == null) {
+            throw exception(JOB_NOT_EXISTS);
+        }
+        return job;
+    }
+
+    private void validateCronExpression(String cronExpression) {
+        if (CronUtils.isValid(cronExpression)) {
+            throw exception(JOB_CRON_EXPRESSION_VALID);
         }
     }
 
@@ -89,4 +163,10 @@ public class InfJobServiceImpl implements InfJobService {
 		return jobMapper.selectList(exportReqVO);
     }
 
+    private static void fillJobMonitorTimeoutEmpty(InfJobDO job) {
+        if (job.getMonitorTimeout() == null) {
+            job.setMonitorTimeout(0);
+        }
+    }
+
 }

+ 4 - 0
src/main/java/cn/iocoder/dashboard/util/collection/CollectionUtils.java

@@ -15,6 +15,10 @@ import java.util.stream.Collectors;
  */
 public class CollectionUtils {
 
+    public static boolean containsAny(Object source, Object... targets) {
+        return Arrays.asList(targets).contains(source);
+    }
+
     public static boolean isAnyEmpty(Collection<?>... collections) {
         return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
     }

+ 2 - 2
src/main/resources/codegen/java/controller/controller.vm

@@ -63,7 +63,7 @@ public class ${table.className}Controller {
 
     @GetMapping("/get")
     @ApiOperation("获得${table.classComment}")
-    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = ${primaryColumn.javaType}.class)
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = ${primaryColumn.javaType}.class)
     @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
     public CommonResult<${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
         ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
@@ -72,7 +72,7 @@ public class ${table.className}Controller {
 
     @GetMapping("/list")
     @ApiOperation("获得${table.classComment}列表")
-    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, dataTypeClass = List.class)
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
     @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
     public CommonResult<List<${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) {
         List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff