Przeglądaj źródła

1. 完成 quartz 的封装

YunaiV 4 lat temu
rodzic
commit
014f91533c
33 zmienionych plików z 440 dodań i 552 usunięć
  1. 0 12
      bin/clean.bat
  2. 0 12
      bin/package.bat
  3. 0 14
      bin/run.bat
  4. 0 48
      ruoyi-common/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java
  5. 0 64
      ruoyi-common/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java
  6. 8 0
      ruoyi-ui/src/api/infra/job.js
  7. 28 0
      ruoyi-ui/src/api/infra/jobLog.js
  8. 0 35
      ruoyi-ui/src/api/monitor/jobLog.js
  9. 1 0
      ruoyi-ui/src/utils/dict.js
  10. 22 8
      ruoyi-ui/src/views/infra/job/index.vue
  11. 82 202
      ruoyi-ui/src/views/infra/job/log.vue
  12. 0 86
      ry.sh
  13. 32 0
      src/main/java/cn/iocoder/dashboard/framework/quartz/core/util/CronUtils.java
  14. 19 0
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobController.java
  15. 81 0
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobLogController.java
  16. 3 3
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/job/InfJobBaseVO.java
  17. 0 12
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/job/InfJobRespVO.java
  18. 7 7
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogBaseVO.java
  19. 5 1
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogExcelVO.java
  20. 6 2
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogExportReqVO.java
  21. 5 2
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogPageReqVO.java
  22. 1 1
      src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogRespVO.java
  23. 30 0
      src/main/java/cn/iocoder/dashboard/modules/infra/convert/job/InfJobLogConvert.java
  24. 0 31
      src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/job/InfJobDO.java
  25. 29 0
      src/main/java/cn/iocoder/dashboard/modules/infra/dal/mysql/job/InfJobLogMapper.java
  26. 40 0
      src/main/java/cn/iocoder/dashboard/modules/infra/service/job/InfJobLogService.java
  27. 25 0
      src/main/java/cn/iocoder/dashboard/modules/infra/service/job/impl/InfJobLogServiceImpl.java
  28. 3 2
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictDataMapper.java
  29. 3 2
      src/main/java/cn/iocoder/dashboard/modules/system/enums/dict/SysDictTypeEnum.java
  30. 0 3
      src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java
  31. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
  32. 1 1
      src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
  33. 6 1
      src/main/resources/application.yaml

+ 0 - 12
bin/clean.bat

@@ -1,12 +0,0 @@
-@echo off
-echo.
-echo [ÐÅÏ¢] ÇåÀíÉú³É·¾¶¡£
-echo.
-
-%~d0
-cd %~dp0
-
-cd ..
-call mvn clean
-
-pause

+ 0 - 12
bin/package.bat

@@ -1,12 +0,0 @@
-@echo off
-echo.
-echo [信息] 打包Web工程,生成war/jar包文件。
-echo.
-
-%~d0
-cd %~dp0
-
-cd ..
-call mvn clean package -Dmaven.test.skip=true
-
-pause

+ 0 - 14
bin/run.bat

@@ -1,14 +0,0 @@
-@echo off
-echo.
-echo [ÐÅÏ¢] ÔËÐÐWeb¹¤³Ì¡£
-echo.
-
-cd %~dp0
-cd ../ruoyi-admin/target
-
-set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
-
-java -jar %JAVA_OPTS% ruoyi-admin.jar
-
-cd bin
-pause

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

@@ -1,48 +0,0 @@
-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;
-    }
-}

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

@@ -1,64 +0,0 @@
-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));
-    }
-
-}

+ 8 - 0
ruoyi-ui/src/api/infra/job.js

@@ -73,3 +73,11 @@ export function runJob(jobId) {
     method: 'put'
   })
 }
+
+// 获得定时任务的下 n 次执行时间
+export function getJobNextTimes(jobId) {
+  return request({
+    url: '/infra/job/get_next_times?id=' + jobId,
+    method: 'get'
+  })
+}

+ 28 - 0
ruoyi-ui/src/api/infra/jobLog.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request'
+
+// 获得定时任务
+export function getJobLog(id) {
+  return request({
+    url: '/infra/job-log/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得定时任务分页
+export function getJobLogPage(query) {
+  return request({
+    url: '/infra/job-log/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出定时任务 Excel
+export function exportJobLogExcel(query) {
+  return request({
+    url: '/infra/job-log/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 0 - 35
ruoyi-ui/src/api/monitor/jobLog.js

@@ -1,35 +0,0 @@
-import request from '@/utils/request'
-
-// 查询调度日志列表
-export function listJobLog(query) {
-  return request({
-    url: '/monitor/jobLog/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 删除调度日志
-export function delJobLog(jobLogId) {
-  return request({
-    url: '/monitor/jobLog/' + jobLogId,
-    method: 'delete'
-  })
-}
-
-// 清空调度日志
-export function cleanJobLog() {
-  return request({
-    url: '/monitor/jobLog/clean',
-    method: 'delete'
-  })
-}
-
-// 导出调度日志
-export function exportJobLog(query) {
-  return request({
-    url: '/monitor/jobLog/export',
-    method: 'get',
-    params: query
-  })
-}

+ 1 - 0
ruoyi-ui/src/utils/dict.js

@@ -18,6 +18,7 @@ export const DICT_TYPE = {
 
   INF_REDIS_TIMEOUT_TYPE: 'inf_redis_timeout_type',
   INF_JOB_STATUS: 'inf_job_status',
+  INF_JOB_LOG_STATUS: 'inf_job_log_status',
 
   TOOL_CODEGEN_TEMPLATE_TYPE: 'tool_codegen_template_type',
 }

+ 22 - 8
ruoyi-ui/src/views/infra/job/index.vue

@@ -30,7 +30,7 @@
       </el-col>
       <el-col :span="1.5">
         <el-button type="info" icon="el-icon-s-operation" size="mini" @click="handleJobLog"
-                   v-hasPermi="['infra:job:query']">日志</el-button>
+                   v-hasPermi="['infra:job:query']">执行日志</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
@@ -50,6 +50,8 @@
         <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" icon="el-icon-s-operation" @click="handleJobLog(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)"
@@ -109,13 +111,10 @@
             <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.retryCount }}</el-form-item>
             <el-form-item label="重试间隔:">{{ form.retryInterval + " 毫秒" }}</el-form-item>
             <el-form-item label="监控超时时间:">{{ form.monitorTimeout > 0 ? form.monitorTimeout + " 毫秒" : "未开启" }}</el-form-item>
+            <el-form-item label="后续执行时间:">{{ Array.from(nextTimes, x => parseTime(x)).join('; ')}}</el-form-item>
           </el-col>
         </el-row>
       </el-form>
@@ -128,7 +127,7 @@
 </template>
 
 <script>
-import { listJob, getJob, delJob, addJob, updateJob, exportJob, runJob, updateJobStatus } from "@/api/infra/job";
+import { listJob, getJob, delJob, addJob, updateJob, exportJob, runJob, updateJobStatus, getJobNextTimes } from "@/api/infra/job";
 import { InfJobStatusEnum } from "@/utils/constants";
 
 export default {
@@ -169,6 +168,7 @@ export default {
         retryCount: [{ required: true, message: "重试次数不能为空", trigger: "blur" }],
         retryInterval: [{ required: true, message: "重试间隔不能为空", trigger: "blur" }],
       },
+      nextTimes: [], // 后续执行时间
 
       // 枚举
       InfJobStatusEnum: InfJobStatusEnum
@@ -204,6 +204,7 @@ export default {
         retryInterval: undefined,
         monitorTimeout: undefined,
       };
+      this.nextTimes = [];
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -234,10 +235,23 @@ export default {
         this.form = response.data;
         this.openView = true;
       });
+      // 获取下一次执行时间
+      getJobNextTimes(row.id).then(response => {
+        this.nextTimes = response.data;
+      });
     },
     /** 任务日志列表查询 */
-    handleJobLog() {
-      this.$router.push("/job/log");
+    handleJobLog(row) {
+      if (row.id) {
+        this.$router.push({
+          path:"/job/log",
+          query:{
+            jobId: row.id
+          }
+        });
+      } else {
+        this.$router.push("/job/log");
+      }
     },
     /** 新增按钮操作 */
     handleAdd() {

+ 82 - 202
ruoyi-ui/src/views/infra/job/log.vue

@@ -1,158 +1,82 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="任务名称" prop="jobName">
-        <el-input
-          v-model="queryParams.jobName"
-          placeholder="请输入任务名称"
-          clearable
-          size="small"
-          style="width: 240px"
-          @keyup.enter.native="handleQuery"
-        />
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
+      <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 label="任务组名" prop="jobGroup">
-        <el-select
-          v-model="queryParams.jobGroup"
-          placeholder="请任务组名"
-          clearable
-          size="small"
-          style="width: 240px"
-        >
-          <el-option
-            v-for="dict in jobGroupOptions"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
-          />
-        </el-select>
+      <el-form-item label="开始执行时间" prop="beginTime">
+        <el-date-picker clearable size="small" v-model="queryParams.beginTime" type="date" value-format="yyyy-MM-dd" placeholder="选择开始执行时间" />
       </el-form-item>
-      <el-form-item label="执行状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择执行状态"
-          clearable
-          size="small"
-          style="width: 240px"
-        >
-          <el-option
-            v-for="dict in statusOptions"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
-          />
-        </el-select>
+      <el-form-item label="结束执行时间" prop="endTime">
+        <el-date-picker clearable size="small" v-model="queryParams.endTime" type="date" value-format="yyyy-MM-dd" placeholder="选择结束执行时间" />
       </el-form-item>
-      <el-form-item label="执行时间">
-        <el-date-picker
-          v-model="dateRange"
-          size="small"
-          style="width: 240px"
-          value-format="yyyy-MM-dd"
-          type="daterange"
-          range-separator="-"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-        ></el-date-picker>
+      <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_LOG_STATUS)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
       </el-form-item>
       <el-form-item>
-        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button type="primary" 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="danger"
-          icon="el-icon-delete"
-          size="mini"
-          :disabled="multiple"
-          @click="handleDelete"
-          v-hasPermi="['monitor:job:remove']"
-        >删除</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="danger"
-          icon="el-icon-delete"
-          size="mini"
-          @click="handleClean"
-          v-hasPermi="['monitor:job:remove']"
-        >清空</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-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['infra:job:export']">导出</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
-      <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
-      <el-table-column label="任务组名" align="center" prop="jobGroup" :formatter="jobGroupFormat" :show-overflow-tooltip="true" />
-      <el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
-      <el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
-      <el-table-column label="执行状态" align="center" prop="status" :formatter="statusFormat" />
-      <el-table-column label="执行时间" align="center" prop="createTime" width="180">
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="日志编号" align="center" prop="id" />
+      <el-table-column label="任务编号" align="center" prop="jobId" />
+      <el-table-column label="处理器的名字" align="center" prop="handlerName" />
+      <el-table-column label="处理器的参数" align="center" prop="handlerParam" />
+      <el-table-column label="第几次执行" align="center" prop="executeIndex" />
+      <el-table-column label="执行时间" align="center" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.beginTime) + ' ~ ' + parseTime(scope.row.endTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="执行时长" align="center" prop="duration">
+        <template slot-scope="scope">
+          <span>{{ scope.row.duration + ' 毫秒' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="任务状态" align="center" prop="status">
         <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.createTime) }}</span>
+          <span>{{ getDictDataLabel(DICT_TYPE.INF_JOB_LOG_STATUS, scope.row.status) }}</span>
         </template>
       </el-table-column>
       <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="handleView(scope.row)"
+                     v-hasPermi="['infra:job:query']">详细</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
 
     <!-- 调度日志详细 -->
     <el-dialog title="调度日志详细" :visible.sync="open" width="700px" append-to-body>
-      <el-form ref="form" :model="form" label-width="100px" size="mini">
+      <el-form ref="form" :model="form" label-width="120px" size="mini">
         <el-row>
           <el-col :span="12">
-            <el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item>
-            <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item>
-            <el-form-item label="执行时间:">{{ form.createTime }}</el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="执行状态:">
-              <div v-if="form.status == 0">正常</div>
-              <div v-else-if="form.status == 1">失败</div>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="异常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
+            <el-form-item label="日志编号:">{{ form.id }}</el-form-item>
+            <el-form-item label="任务编号:">{{ form.jobId }}</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="第几次执行:">{{ form.executeIndex }}</el-form-item>
+            <el-form-item label="执行时间:">{{ parseTime(form.beginTime) + ' ~ ' + parseTime(form.endTime) }}</el-form-item>
+            <el-form-item label="执行时长:">{{ parseTime(form.duration) + ' 毫秒' }}</el-form-item>
+            <el-form-item label="任务状态:">{{ getDictDataLabel(DICT_TYPE.INF_JOB_LOG_STATUS, form.status) }}</el-form-item>
+            <el-form-item label="执行结果:">{{ form.result }}</el-form-item>
           </el-col>
         </el-row>
       </el-form>
@@ -164,7 +88,7 @@
 </template>
 
 <script>
-import { listJobLog, delJobLog, exportJobLog, cleanJobLog } from "@/api/monitor/jobLog";
+import { getJobLogPage, exportJobLogExcel } from "@/api/infra/jobLog";
 
 export default {
   name: "JobLog",
@@ -172,125 +96,81 @@ export default {
     return {
       // 遮罩层
       loading: true,
-      // 选中数组
-      ids: [],
-      // 非多个禁用
-      multiple: true,
       // 显示搜索条件
       showSearch: true,
       // 总条数
       total: 0,
       // 调度日志表格数据
-      jobLogList: [],
+      list: [],
       // 是否显示弹出层
       open: false,
-      // 日期范围
-      dateRange: [],
       // 表单参数
       form: {},
-      // 执行状态字典
-      statusOptions: [],
-      // 任务组名字典
-      jobGroupOptions: [],
       // 查询参数
       queryParams: {
-        pageNum: 1,
+        pageNo: 1,
         pageSize: 10,
-        jobName: undefined,
-        jobGroup: undefined,
-        status: undefined
+        handlerName: null,
+        beginTime: null,
+        endTime: null,
+        status: null,
       }
     };
   },
   created() {
+    this.queryParams.jobId = this.$route.query && this.$route.query.jobId;
     this.getList();
-    this.getDicts("sys_job_status").then(response => {
-      this.statusOptions = response.data;
-    });
-    this.getDicts("sys_job_group").then(response => {
-      this.jobGroupOptions = response.data;
-    });
   },
   methods: {
     /** 查询调度日志列表 */
     getList() {
       this.loading = true;
-      listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
-          this.jobLogList = response.rows;
-          this.total = response.total;
+      getJobLogPage({
+        ...this.queryParams,
+        beginTime: this.queryParams.beginTime ? this.queryParams.beginTime + ' 00:00:00' : undefined,
+        endTime: this.queryParams.endTime ? this.queryParams.endTime + ' 23:59:59' : undefined,
+      }).then(response => {
+          this.list = response.data.list;
+          this.total = response.data.total;
           this.loading = false;
         }
       );
     },
-    // 执行状态字典翻译
-    statusFormat(row, column) {
-      return this.selectDictLabel(this.statusOptions, row.status);
-    },
-    // 任务组名字典翻译
-    jobGroupFormat(row, column) {
-      return this.selectDictLabel(this.jobGroupOptions, row.jobGroup);
-    },
     /** 搜索按钮操作 */
     handleQuery() {
-      this.queryParams.pageNum = 1;
+      this.queryParams.pageNo = 1;
       this.getList();
     },
     /** 重置按钮操作 */
     resetQuery() {
-      this.dateRange = [];
       this.resetForm("queryForm");
       this.handleQuery();
     },
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.jobLogId);
-      this.multiple = !selection.length;
-    },
     /** 详细按钮操作 */
     handleView(row) {
       this.open = true;
       this.form = row;
     },
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const jobLogIds = this.ids;
-      this.$confirm('是否确认删除调度日志编号为"' + jobLogIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delJobLog(jobLogIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        })
-    },
-    /** 清空按钮操作 */
-    handleClean() {
-      this.$confirm("是否确认清空所有调度日志数据项?", "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return cleanJobLog();
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("清空成功");
-        })
-    },
     /** 导出按钮操作 */
     handleExport() {
-      const queryParams = this.queryParams;
-      this.$confirm("是否确认导出所有调度日志数据项?", "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return exportJobLog(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-        })
+      // 处理查询参数
+      let params = {...this.queryParams,
+        beginTime: this.queryParams.beginTime ? this.queryParams.beginTime + ' 00:00:00' : undefined,
+        endTime: this.queryParams.endTime ? this.queryParams.endTime + ' 23:59:59' : undefined,
+      };
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      // 执行导出
+      this.$confirm('是否确认导出所有定时任务日志数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return exportJobLogExcel(params);
+      }).then(response => {
+        this.downloadExcel(response, '定时任务日志.xls');
+      })
     }
   }
 };
-</script>
+</script>

+ 0 - 86
ry.sh

@@ -1,86 +0,0 @@
-#!/bin/bash
-
-AppName=ruoyi-admin.jar
-
-#JVM参数
-JVM_OPTS="-Dname=$AppName  -Duser.timezone=Asia/Shanghai -Xms512M -Xmx512M -XX:PermSize=256M -XX:MaxPermSize=512M -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps  -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
-APP_HOME=`pwd`
-LOG_PATH=$APP_HOME/logs/$AppName.log
-
-if [ "$1" = "" ];
-then
-    echo -e "\033[0;31m 未输入操作名 \033[0m  \033[0;34m {start|stop|restart|status} \033[0m"
-    exit 1
-fi
-
-if [ "$AppName" = "" ];
-then
-    echo -e "\033[0;31m 未输入应用名 \033[0m"
-    exit 1
-fi
-
-function start()
-{
-    PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
-
-	if [ x"$PID" != x"" ]; then
-	    echo "$AppName is running..."
-	else
-		nohup java -jar  $JVM_OPTS target/$AppName > /dev/null 2>&1 &
-		echo "Start $AppName success..."
-	fi
-}
-
-function stop()
-{
-    echo "Stop $AppName"
-	
-	PID=""
-	query(){
-		PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
-	}
-
-	query
-	if [ x"$PID" != x"" ]; then
-		kill -TERM $PID
-		echo "$AppName (pid:$PID) exiting..."
-		while [ x"$PID" != x"" ]
-		do
-			sleep 1
-			query
-		done
-		echo "$AppName exited."
-	else
-		echo "$AppName already stopped."
-	fi
-}
-
-function restart()
-{
-    stop
-    sleep 2
-    start
-}
-
-function status()
-{
-    PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`
-    if [ $PID != 0 ];then
-        echo "$AppName is running..."
-    else
-        echo "$AppName is not running..."
-    fi
-}
-
-case $1 in
-    start)
-    start;;
-    stop)
-    stop;;
-    restart)
-    restart;;
-    status)
-    status;;
-    *)
-
-esac

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

@@ -2,6 +2,11 @@ package cn.iocoder.dashboard.framework.quartz.core.util;
 
 import org.quartz.CronExpression;
 
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
 /**
  * Quartz Cron 表达式的工具类
  *
@@ -19,4 +24,31 @@ public class CronUtils {
         return CronExpression.isValidExpression(cronExpression);
     }
 
+    /**
+     * 基于 CRON 表达式,获得下 n 个满足执行的时间
+     *
+     * @param cronExpression CRON 表达式
+     * @param n 数量
+     * @return 满足条件的执行时间
+     */
+    public static List<Date> getNextTimes(String cronExpression, int n) {
+        // 获得 CronExpression 对象
+        CronExpression cron;
+        try {
+            cron = new CronExpression(cronExpression);
+        } catch (ParseException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+        // 从当前开始计算,n 个满足条件的
+        Date now = new Date();
+        List<Date> nextTimes = new ArrayList<>(n);
+        for (int i = 0; i < n; i++) {
+            Date nextTime = cron.getNextValidTimeAfter(now);
+            nextTimes.add(nextTime);
+            // 切换现在,为下一个触发时间;
+            now = nextTime;
+        }
+        return nextTimes;
+    }
+
 }

+ 19 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobController.java

@@ -4,6 +4,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
+import cn.iocoder.dashboard.framework.quartz.core.util.CronUtils;
 import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.*;
 import cn.iocoder.dashboard.modules.infra.convert.job.InfJobConvert;
 import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
@@ -22,6 +23,8 @@ import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 
 import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
@@ -123,4 +126,20 @@ public class InfJobController {
         ExcelUtils.write(response, "定时任务.xls", "数据", InfJobExcelVO.class, datas);
     }
 
+    @GetMapping("/get_next_times")
+    @ApiOperation("获得定时任务的下 n 次执行时间")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class),
+            @ApiImplicitParam(name = "count", value = "数量", example = "5", dataTypeClass = Long.class)
+    })
+    @PreAuthorize("@ss.hasPermission('infra:job:query')")
+    public CommonResult<List<Date>> getJobNextTimes(@RequestParam("id") Long id,
+                                                    @RequestParam(value = "count", required = false, defaultValue = "5") Integer count) {
+        InfJobDO job = jobService.getJob(id);
+        if (job == null) {
+            return success(Collections.emptyList());
+        }
+        return success(CronUtils.getNextTimes(job.getCronExpression(), count));
+    }
+
 }

+ 81 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/InfJobLogController.java

@@ -0,0 +1,81 @@
+package cn.iocoder.dashboard.modules.infra.controller.job;
+
+import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExcelVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExportReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogPageReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogRespVO;
+import cn.iocoder.dashboard.modules.infra.convert.job.InfJobLogConvert;
+import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobLogDO;
+import cn.iocoder.dashboard.modules.infra.service.job.InfJobLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
+import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Api(tags = "定时任务日志")
+@RestController
+@RequestMapping("/infra/job-log")
+@Validated
+public class InfJobLogController {
+
+    @Resource
+    private InfJobLogService jobLogService;
+
+    @GetMapping("/get")
+    @ApiOperation("获得定时任务日志")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('infra:job:query')")
+    public CommonResult<InfJobLogRespVO> getJobLog(@RequestParam("id") Long id) {
+        InfJobLogDO jobLog = jobLogService.getJobLog(id);
+        return success(InfJobLogConvert.INSTANCE.convert(jobLog));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得定时任务日志列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('infra:job:query')")
+    public CommonResult<List<InfJobLogRespVO>> getJobLogList(@RequestParam("ids") Collection<Long> ids) {
+        List<InfJobLogDO> list = jobLogService.getJobLogList(ids);
+        return success(InfJobLogConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得定时任务日志分页")
+    @PreAuthorize("@ss.hasPermission('infra:job:query')")
+    public CommonResult<PageResult<InfJobLogRespVO>> getJobLogPage(@Valid InfJobLogPageReqVO pageVO) {
+        PageResult<InfJobLogDO> pageResult = jobLogService.getJobLogPage(pageVO);
+        return success(InfJobLogConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出定时任务日志 Excel")
+    @PreAuthorize("@ss.hasPermission('infra:job:export')")
+    @OperateLog(type = EXPORT)
+    public void exportJobLogExcel(@Valid InfJobLogExportReqVO exportReqVO,
+                                  HttpServletResponse response) throws IOException {
+        List<InfJobLogDO> list = jobLogService.getJobLogList(exportReqVO);
+        // 导出 Excel
+        List<InfJobLogExcelVO> datas = InfJobLogConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "定时任务.xls", "数据", InfJobLogExcelVO.class, datas);
+    }
+
+}

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/job/InfJobBaseVO.java

@@ -23,14 +23,14 @@ public class InfJobBaseVO {
     @NotNull(message = "CRON 表达式不能为空")
     private String cronExpression;
 
-    @ApiModelProperty(value = "重试次数", required = true)
+    @ApiModelProperty(value = "重试次数", required = true, example = "3")
     @NotNull(message = "重试次数不能为空")
     private Integer retryCount;
 
-    @ApiModelProperty(value = "重试间隔", required = true)
+    @ApiModelProperty(value = "重试间隔", required = true, example = "1000")
     @NotNull(message = "重试间隔不能为空")
     private Integer retryInterval;
-    
+
     @ApiModelProperty(value = "监控超时时间", example = "1000")
     private Integer monitorTimeout;
 

+ 0 - 12
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/job/InfJobRespVO.java

@@ -25,18 +25,6 @@ public class InfJobRespVO extends InfJobBaseVO {
     @NotNull(message = "处理器的名字不能为空")
     private String handlerName;
 
-    @ApiModelProperty(value = "最后一次执行的开始时间")
-    private Date executeBeginTime;
-
-    @ApiModelProperty(value = "最后一次执行的结束时间")
-    private Date executeEndTime;
-
-    @ApiModelProperty(value = "上一次触发时间")
-    private Date firePrevTime;
-
-    @ApiModelProperty(value = "下一次触发时间")
-    private Date fireNextTime;
-
     @ApiModelProperty(value = "创建时间", required = true)
     private Date createTime;
 

+ 7 - 7
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogBaseVO.java

@@ -16,18 +16,18 @@ import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOU
 @Data
 public class InfJobLogBaseVO {
 
-    @ApiModelProperty(value = "任务编号", required = true)
+    @ApiModelProperty(value = "任务编号", required = true, example = "1024")
     @NotNull(message = "任务编号不能为空")
     private Long jobId;
 
-    @ApiModelProperty(value = "处理器的名字", required = true)
+    @ApiModelProperty(value = "处理器的名字", required = true, example = "sysUserSessionTimeoutJob")
     @NotNull(message = "处理器的名字不能为空")
     private String handlerName;
 
-    @ApiModelProperty(value = "处理器的参数")
+    @ApiModelProperty(value = "处理器的参数", example = "yudao")
     private String handlerParam;
 
-    @ApiModelProperty(value = "第几次执行", required = true)
+    @ApiModelProperty(value = "第几次执行", required = true, example = "1")
     @NotNull(message = "第几次执行不能为空")
     private Integer executeIndex;
 
@@ -40,14 +40,14 @@ public class InfJobLogBaseVO {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private Date endTime;
 
-    @ApiModelProperty(value = "执行时长")
+    @ApiModelProperty(value = "执行时长", example = "123")
     private Integer duration;
 
-    @ApiModelProperty(value = "任务状态", required = true)
+    @ApiModelProperty(value = "任务状态", required = true, example = "1", notes = "参见 InfJobLogStatusEnum 枚举")
     @NotNull(message = "任务状态不能为空")
     private Integer status;
 
-    @ApiModelProperty(value = "结果数据")
+    @ApiModelProperty(value = "结果数据", example = "执行成功")
     private String result;
 
 }

+ 5 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogExcelVO.java

@@ -1,5 +1,8 @@
 package cn.iocoder.dashboard.modules.infra.controller.job.vo.log;
 
+import cn.iocoder.dashboard.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.dashboard.framework.excel.core.convert.DictConvert;
+import cn.iocoder.dashboard.modules.system.enums.dict.SysDictTypeEnum;
 import com.alibaba.excel.annotation.ExcelProperty;
 import lombok.Data;
 
@@ -37,7 +40,8 @@ public class InfJobLogExcelVO {
     @ExcelProperty("执行时长")
     private Integer duration;
 
-    @ExcelProperty("任务状态")
+    @ExcelProperty(value = "任务状态", converter = DictConvert.class)
+    @DictFormat(SysDictTypeEnum.INF_JOB_STATUS)
     private Integer status;
 
     @ExcelProperty("结果数据")

+ 6 - 2
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogExportReqVO.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
+import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -13,7 +14,10 @@ import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOU
 @Data
 public class InfJobLogExportReqVO {
 
-    @ApiModelProperty(value = "处理器的名字")
+    @ApiModelProperty(value = "任务编号", example = "10")
+    private Long jobId;
+
+    @ApiModelProperty(value = "处理器的名字", notes = "模糊匹配")
     private String handlerName;
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@@ -24,7 +28,7 @@ public class InfJobLogExportReqVO {
     @ApiModelProperty(value = "结束执行时间")
     private Date endTime;
 
-    @ApiModelProperty(value = "任务状态")
+    @ApiModelProperty(value = "任务状态", notes = "参见 InfJobLogStatusEnum 枚举")
     private Integer status;
 
 }

+ 5 - 2
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogPageReqVO.java

@@ -18,7 +18,10 @@ import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOU
 @ToString(callSuper = true)
 public class InfJobLogPageReqVO extends PageParam {
 
-    @ApiModelProperty(value = "处理器的名字")
+    @ApiModelProperty(value = "任务编号", example = "10")
+    private Long jobId;
+
+    @ApiModelProperty(value = "处理器的名字", notes = "模糊匹配")
     private String handlerName;
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@@ -29,7 +32,7 @@ public class InfJobLogPageReqVO extends PageParam {
     @ApiModelProperty(value = "结束执行时间")
     private Date endTime;
 
-    @ApiModelProperty(value = "任务状态")
+    @ApiModelProperty(value = "任务状态", notes = "参见 InfJobLogStatusEnum 枚举")
     private Integer status;
 
 }

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/controller/job/vo/log/InfJobLogRespVO.java

@@ -14,7 +14,7 @@ import java.util.Date;
 @ToString(callSuper = true)
 public class InfJobLogRespVO extends InfJobLogBaseVO {
 
-    @ApiModelProperty(value = "日志编号", required = true)
+    @ApiModelProperty(value = "日志编号", required = true, example = "1024")
     private Long id;
 
     @ApiModelProperty(value = "创建时间", required = true)

+ 30 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/convert/job/InfJobLogConvert.java

@@ -0,0 +1,30 @@
+package cn.iocoder.dashboard.modules.infra.convert.job;
+
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExcelVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogRespVO;
+import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobLogDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 定时任务日志 Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface InfJobLogConvert {
+
+    InfJobLogConvert INSTANCE = Mappers.getMapper(InfJobLogConvert.class);
+
+    InfJobLogRespVO convert(InfJobLogDO bean);
+
+    List<InfJobLogRespVO> convertList(List<InfJobLogDO> list);
+
+    PageResult<InfJobLogRespVO> convertPage(PageResult<InfJobLogDO> page);
+
+    List<InfJobLogExcelVO> convertList02(List<InfJobLogDO> list);
+
+}

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

@@ -6,8 +6,6 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
-import java.util.Date;
-
 /**
  * 定时任务 DO
  *
@@ -45,35 +43,10 @@ public class InfJobDO extends BaseDO {
      * 处理器的参数
      */
     private String handlerParam;
-
-    // ========== 时间相关字段 ==========
-
     /**
      * CRON 表达式
      */
     private String cronExpression;
-    /**
-     * 最后一次执行的开始时间
-     *
-     * 该字段在任务执行结束后,进行记录
-     */
-    private Date executeBeginTime;
-    /**
-     * 最后一次执行的结束时间
-     *
-     * 该字段在任务执行结束后,进行记录
-     */
-    private Date executeEndTime;
-    /**
-     * 上一次触发时间,来自 Quartz SCHE_TRIGGERS 表
-     */
-    private Date firePrevTime;
-    /**
-     * 下一次触发时间,来自 Quartz SCHE_TRIGGERS 表
-     *
-     * 在触发器状态从 `ACQUIRED` 变成 `BLOCKED` 时,就会更新 PREV_FIRE_TIME、NEXT_FIRE_TIME,然后定时任务才正式开始执行
-     */
-    private Date fireNextTime;
 
     // ========== 重试相关字段 ==========
     /**
@@ -96,8 +69,4 @@ public class InfJobDO extends BaseDO {
      */
     private Integer monitorTimeout;
 
-    // TODO misfirePolicy
-    // TODO concurrent
-    // TODO 失败重试
-
 }

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

@@ -1,9 +1,15 @@
 package cn.iocoder.dashboard.modules.infra.dal.mysql.job;
 
+import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExportReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogPageReqVO;
 import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobLogDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 /**
  * 任务日志 Mapper
  *
@@ -11,4 +17,27 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 public interface InfJobLogMapper extends BaseMapperX<InfJobLogDO> {
+
+    default PageResult<InfJobLogDO> selectPage(InfJobLogPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<InfJobLogDO>()
+                .eqIfPresent("job_id", reqVO.getJobId())
+                .likeIfPresent("handler_name", reqVO.getHandlerName())
+                .geIfPresent("begin_time", reqVO.getBeginTime())
+                .leIfPresent("end_time", reqVO.getEndTime())
+                .eqIfPresent("status", reqVO.getStatus())
+                .orderByDesc("id") // ID 倒序
+        );
+    }
+
+    default List<InfJobLogDO> selectList(InfJobLogExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<InfJobLogDO>()
+                .eqIfPresent("job_id", reqVO.getJobId())
+                .likeIfPresent("handler_name", reqVO.getHandlerName())
+                .geIfPresent("begin_time", reqVO.getBeginTime())
+                .leIfPresent("end_time", reqVO.getEndTime())
+                .eqIfPresent("status", reqVO.getStatus())
+                .orderByDesc("id") // ID 倒序
+        );
+    }
+
 }

+ 40 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/service/job/InfJobLogService.java

@@ -1,6 +1,13 @@
 package cn.iocoder.dashboard.modules.infra.service.job;
 
+import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.quartz.core.service.JobLogFrameworkService;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExportReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogPageReqVO;
+import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobLogDO;
+
+import java.util.Collection;
+import java.util.List;
 
 /**
  * Job 日志 Service 接口
@@ -8,4 +15,37 @@ import cn.iocoder.dashboard.framework.quartz.core.service.JobLogFrameworkService
  * @author 芋道源码
  */
 public interface InfJobLogService extends JobLogFrameworkService {
+
+    /**
+     * 获得定时任务
+     *
+     * @param id 编号
+     * @return 定时任务
+     */
+    InfJobLogDO getJobLog(Long id);
+
+    /**
+     * 获得定时任务列表
+     *
+     * @param ids 编号
+     * @return 定时任务列表
+     */
+    List<InfJobLogDO> getJobLogList(Collection<Long> ids);
+
+    /**
+     * 获得定时任务分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 定时任务分页
+     */
+    PageResult<InfJobLogDO> getJobLogPage(InfJobLogPageReqVO pageReqVO);
+
+    /**
+     * 获得定时任务列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 定时任务分页
+     */
+    List<InfJobLogDO> getJobLogList(InfJobLogExportReqVO exportReqVO);
+
 }

+ 25 - 0
src/main/java/cn/iocoder/dashboard/modules/infra/service/job/impl/InfJobLogServiceImpl.java

@@ -1,5 +1,8 @@
 package cn.iocoder.dashboard.modules.infra.service.job.impl;
 
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogExportReqVO;
+import cn.iocoder.dashboard.modules.infra.controller.job.vo.log.InfJobLogPageReqVO;
 import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobLogDO;
 import cn.iocoder.dashboard.modules.infra.dal.mysql.job.InfJobLogMapper;
 import cn.iocoder.dashboard.modules.infra.enums.job.InfJobLogStatusEnum;
@@ -10,7 +13,9 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 
 /**
  * Job 日志 Service 实现类
@@ -46,4 +51,24 @@ public class InfJobLogServiceImpl implements InfJobLogService {
         }
     }
 
+    @Override
+    public InfJobLogDO getJobLog(Long id) {
+        return jobLogMapper.selectById(id);
+    }
+
+    @Override
+    public List<InfJobLogDO> getJobLogList(Collection<Long> ids) {
+        return jobLogMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<InfJobLogDO> getJobLogPage(InfJobLogPageReqVO pageReqVO) {
+        return jobLogMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<InfJobLogDO> getJobLogList(InfJobLogExportReqVO exportReqVO) {
+        return jobLogMapper.selectList(exportReqVO);
+    }
+
 }

+ 3 - 2
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dict/SysDictDataMapper.java

@@ -15,8 +15,9 @@ import java.util.List;
 @Mapper
 public interface SysDictDataMapper extends BaseMapperX<SysDictDataDO> {
 
-    default SysDictDataDO selectByLabel(String label) {
-        return selectOne(new QueryWrapper<SysDictDataDO>().eq("label", label));
+    default SysDictDataDO selectByDictTypeAndLabel(String dictType, String label) {
+        return selectOne(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType)
+                .eq("label", label));
     }
 
     default int selectCountByDictType(String dictType) {

+ 3 - 2
src/main/java/cn/iocoder/dashboard/modules/system/enums/dict/SysDictTypeEnum.java

@@ -18,8 +18,9 @@ public enum SysDictTypeEnum {
     SYS_BOOLEAN_STRING("sys_boolean_string"), // Boolean 是否类型
 
     INF_REDIS_TIMEOUT_TYPE("inf_redis_timeout_type"),  // Redis 超时类型
-    INF_JOB_STATUS("inf_job_status") // 任务状态的枚举
-    ;
+    INF_JOB_STATUS("inf_job_status"), // 定时任务状态的枚举
+    INF_JOB_LOG_STATUS("inf_job_log_status") // 定时任务日志状态的枚举
+    ,;
 
 
     /**

+ 0 - 3
src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java

@@ -15,9 +15,6 @@ public class SysUserSessionTimeoutJob implements JobHandler {
 
     @Override
     public String execute(String param) throws Exception {
-        if (true) {
-            throw new RuntimeException("测试异常");
-        }
 //        System.out.println("执行了一次任务");
         log.info("[execute][执行任务:{}]", param);
         return null;

+ 3 - 3
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java

@@ -195,13 +195,13 @@ public class SysDictDataServiceImpl implements SysDictDataService {
         // 校验自己存在
         checkDictDataExists(id);
         // 校验字典数据的值的唯一性
-        checkDictDataValueUnique(id, label);
+        checkDictDataValueUnique(id, dictType, label);
         // 校验字典类型有效
         checkDictTypeValid(dictType);
     }
 
-    private void checkDictDataValueUnique(Long id, String label) {
-        SysDictDataDO dictData = dictDataMapper.selectByLabel(label);
+    private void checkDictDataValueUnique(Long id, String dictType, String label) {
+        SysDictDataDO dictData = dictDataMapper.selectByDictTypeAndLabel(dictType, label);
         if (dictData == null) {
             return;
         }

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java

@@ -51,7 +51,7 @@ public class ToolTestDemoController {
 
     @DeleteMapping("/delete")
     @ApiOperation("删除测试示例")
-    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
     @PreAuthorize("@ss.hasPermission('tool:test-demo:delete')")
     public CommonResult<Boolean> deleteTestDemo(@RequestParam("id") Long id) {
         testDemoService.deleteTestDemo(id);

+ 6 - 1
src/main/resources/application.yaml

@@ -50,11 +50,16 @@ spring:
     properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
       org:
         quartz:
+          # Scheduler 相关配置
+          scheduler:
+            instanceName: schedulerName
+            instanceId: AUTO # 自动生成 instance ID
           # JobStore 相关配置
           jobStore:
             class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
             isClustered: true # 是集群模式
-            clusterCheckinInterval: 1000
+            clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000,即 15 秒
+            misfireThreshold: 60000 # misfire 阀值,单位:毫秒。
           # 线程池相关配置
           threadPool:
             threadCount: 25 # 线程池大小。默认为 10 。