1. 完成 Job 的 CRUD 功能

pull/2/head
YunaiV 2021-02-14 23:01:21 +08:00
parent bc1504d991
commit f60855faa0
27 changed files with 1277 additions and 1374 deletions

View File

@ -1,48 +1,48 @@
package com.ruoyi.quartz.config; package com.ruoyi.quartz.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.Properties; import java.util.Properties;
/** /**
* *
* *
* @author ruoyi * @author ruoyi
*/ */
@Configuration @Configuration
public class ScheduleConfig { public class ScheduleConfig {
@Bean @Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean(); SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource); factory.setDataSource(dataSource);
// quartz参数 // quartz参数
Properties prop = new Properties(); Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO"); prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 集群配置 // 集群配置
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
// sqlserver 启用 // sqlserver 启用
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
prop.put("org.quartz.jobStore.misfireThreshold", "12000"); prop.put("org.quartz.jobStore.misfireThreshold", "12000");
factory.setQuartzProperties(prop); factory.setQuartzProperties(prop);
factory.setSchedulerName("RuoyiScheduler"); factory.setSchedulerName("RuoyiScheduler");
// 延时启动 // 延时启动
factory.setStartupDelay(1); factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey"); factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选QuartzScheduler // 可选QuartzScheduler
// 启动时更新己存在的Job这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 // 启动时更新己存在的Job这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true); factory.setOverwriteExistingJobs(true);
return factory; return factory;
} }
} }

View File

@ -1,85 +1,64 @@
package com.ruoyi.quartz.controller; package com.ruoyi.quartz.controller;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.domain.SysJobLog;
import com.ruoyi.quartz.service.ISysJobLogService; import com.ruoyi.quartz.service.ISysJobLogService;
/** /**
* *
* *
* @author ruoyi * @author ruoyi
*/ */
@RestController @RestController
@RequestMapping("/monitor/jobLog") @RequestMapping("/monitor/jobLog")
public class SysJobLogController extends BaseController { public class SysJobLogController extends BaseController {
@Autowired @Autowired
private ISysJobLogService jobLogService; private ISysJobLogService jobLogService;
/** /**
* *
*/ */
@PreAuthorize("@ss.hasPermi('monitor:job:list')") @PreAuthorize("@ss.hasPermi('monitor:job:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(SysJobLog sysJobLog) { public TableDataInfo list(SysJobLog sysJobLog) {
startPage(); startPage();
List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog); List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
return getDataTable(list); return getDataTable(list);
} }
/** /**
* *
*/ */
@PreAuthorize("@ss.hasPermi('monitor:job:export')") @PreAuthorize("@ss.hasPermi('monitor:job:export')")
@Log(title = "任务调度日志", businessType = BusinessType.EXPORT) @Log(title = "任务调度日志", businessType = BusinessType.EXPORT)
@GetMapping("/export") @GetMapping("/export")
public AjaxResult export(SysJobLog sysJobLog) { public AjaxResult export(SysJobLog sysJobLog) {
List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog); List<SysJobLog> list = jobLogService.selectJobLogList(sysJobLog);
ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class); ExcelUtil<SysJobLog> util = new ExcelUtil<SysJobLog>(SysJobLog.class);
return util.exportExcel(list, "调度日志"); return util.exportExcel(list, "调度日志");
} }
/** /**
* *
*/ */
@PreAuthorize("@ss.hasPermi('monitor:job:query')") @PreAuthorize("@ss.hasPermi('monitor:job:query')")
@GetMapping(value = "/{configId}") @GetMapping(value = "/{configId}")
public AjaxResult getInfo(@PathVariable Long jobLogId) { public AjaxResult getInfo(@PathVariable Long jobLogId) {
return AjaxResult.success(jobLogService.selectJobLogById(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();
}
}

View File

@ -1,81 +1,54 @@
package com.ruoyi.quartz.service.impl; package com.ruoyi.quartz.service.impl;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.domain.SysJobLog;
import com.ruoyi.quartz.mapper.SysJobLogMapper; import com.ruoyi.quartz.mapper.SysJobLogMapper;
import com.ruoyi.quartz.service.ISysJobLogService; import com.ruoyi.quartz.service.ISysJobLogService;
/** /**
* *
* *
* @author ruoyi * @author ruoyi
*/ */
@Service @Service
public class SysJobLogServiceImpl implements ISysJobLogService { public class SysJobLogServiceImpl implements ISysJobLogService {
@Autowired
private SysJobLogMapper jobLogMapper; @Autowired
private SysJobLogMapper jobLogMapper;
/**
* quartz /**
* * quartz
* @param jobLog *
* @return * @param jobLog
*/ * @return
@Override */
public List<SysJobLog> selectJobLogList(SysJobLog jobLog) { @Override
return jobLogMapper.selectJobLogList(jobLog); public List<SysJobLog> selectJobLogList(SysJobLog jobLog) {
} return jobLogMapper.selectJobLogList(jobLog);
}
/**
* ID /**
* * ID
* @param jobLogId ID *
* @return * @param jobLogId ID
*/ * @return
@Override */
public SysJobLog selectJobLogById(Long jobLogId) { @Override
return jobLogMapper.selectJobLogById(jobLogId); public SysJobLog selectJobLogById(Long jobLogId) {
} return jobLogMapper.selectJobLogById(jobLogId);
}
/**
* /**
* *
* @param jobLog *
*/ * @param jobLog
@Override */
public void addJobLog(SysJobLog jobLog) { @Override
jobLogMapper.insertJobLog(jobLog); 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();
}
}

View File

@ -1,97 +1,97 @@
package com.ruoyi.quartz.util; package com.ruoyi.quartz.util;
import java.util.Date; import java.util.Date;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException; import org.quartz.JobExecutionException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.ExceptionUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.domain.SysJob;
import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.domain.SysJobLog;
import com.ruoyi.quartz.service.ISysJobLogService; import com.ruoyi.quartz.service.ISysJobLogService;
/** /**
* quartz * quartz
* *
* @author ruoyi * @author ruoyi
*/ */
public abstract class AbstractQuartzJob implements Job { public abstract class AbstractQuartzJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
/** /**
* 线 * 线
*/ */
private static ThreadLocal<Date> threadLocal = new ThreadLocal<>(); private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
@Override @Override
public void execute(JobExecutionContext context) throws JobExecutionException { public void execute(JobExecutionContext context) throws JobExecutionException {
SysJob sysJob = new SysJob(); SysJob sysJob = new SysJob();
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try { try {
before(context, sysJob); before(context, sysJob);
if (sysJob != null) { if (sysJob != null) {
doExecute(context, sysJob); doExecute(context, sysJob);
} }
after(context, sysJob, null); after(context, sysJob, null);
} catch (Exception e) { } catch (Exception e) {
log.error("任务执行异常 - ", e); log.error("任务执行异常 - ", e);
after(context, sysJob, e); after(context, sysJob, e);
} }
} }
/** /**
* *
* *
* @param context * @param context
* @param sysJob * @param sysJob
*/ */
protected void before(JobExecutionContext context, SysJob sysJob) { protected void before(JobExecutionContext context, SysJob sysJob) {
threadLocal.set(new Date()); threadLocal.set(new Date());
} }
/** /**
* *
* *
* @param context * @param context
* @param sysJob * @param sysJob
*/ */
protected void after(JobExecutionContext context, SysJob sysJob, Exception e) { protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
Date startTime = threadLocal.get(); Date startTime = threadLocal.get();
threadLocal.remove(); threadLocal.remove();
final SysJobLog sysJobLog = new SysJobLog(); final SysJobLog sysJobLog = new SysJobLog();
sysJobLog.setJobName(sysJob.getJobName()); sysJobLog.setJobName(sysJob.getJobName());
sysJobLog.setJobGroup(sysJob.getJobGroup()); sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime); sysJobLog.setStartTime(startTime);
sysJobLog.setStopTime(new Date()); sysJobLog.setStopTime(new Date());
long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null) { if (e != null) {
sysJobLog.setStatus(Constants.FAIL); sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
sysJobLog.setExceptionInfo(errorMsg); sysJobLog.setExceptionInfo(errorMsg);
} else { } else {
sysJobLog.setStatus(Constants.SUCCESS); sysJobLog.setStatus(Constants.SUCCESS);
} }
// 写入数据库当中 // 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
} }
/** /**
* *
* *
* @param context * @param context
* @param sysJob * @param sysJob
* @throws Exception * @throws Exception
*/ */
protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
} }

View File

@ -1,44 +1,44 @@
package com.ruoyi.quartz.util; package com.ruoyi.quartz.util;
import org.quartz.CronScheduleBuilder; import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger; import org.quartz.CronTrigger;
import org.quartz.Job; import org.quartz.Job;
import org.quartz.JobBuilder; import org.quartz.JobBuilder;
import org.quartz.JobDetail; import org.quartz.JobDetail;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder; import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey; import org.quartz.TriggerKey;
import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.exception.job.TaskException;
import com.ruoyi.common.exception.job.TaskException.Code; import com.ruoyi.common.exception.job.TaskException.Code;
import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.domain.SysJob;
/** /**
* *
* *
* @author ruoyi * @author ruoyi
*/ */
public class ScheduleUtils { public class ScheduleUtils {
/** /**
* *
*/ */
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException { throws TaskException {
switch (job.getMisfirePolicy()) { switch (job.getMisfirePolicy()) {
case ScheduleConstants.MISFIRE_DEFAULT: case ScheduleConstants.MISFIRE_DEFAULT:
return cb; return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires(); return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed(); return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING: case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing(); return cb.withMisfireHandlingInstructionDoNothing();
default: default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
} }
} }
} }

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -7,6 +7,12 @@ import org.quartz.*;
/** /**
* {@link org.quartz.Scheduler} * {@link org.quartz.Scheduler}
* *
* 使 jobHandlerName
* 1. Job {@link JobDetail#getKey()}
* 2. Trigger {@link Trigger#getKey()}
*
* jobHandlerName Spring Bean
*
* @author * @author
*/ */
public class SchedulerManager { public class SchedulerManager {
@ -17,6 +23,15 @@ public class SchedulerManager {
this.scheduler = scheduler; 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) public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression)
throws SchedulerException { throws SchedulerException {
// 创建 JobDetail 对象 // 创建 JobDetail 对象
@ -30,6 +45,14 @@ public class SchedulerManager {
scheduler.scheduleJob(jobDetail, trigger); scheduler.scheduleJob(jobDetail, trigger);
} }
/**
* Job Quartz
*
* @param jobHandlerName
* @param jobHandlerParam
* @param cronExpression CRON
* @throws SchedulerException
*/
public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression) public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression)
throws SchedulerException { throws SchedulerException {
// 创建新 Trigger 对象 // 创建新 Trigger 对象
@ -38,19 +61,45 @@ public class SchedulerManager {
scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger); scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);
} }
/**
* Quartz Job
*
* @param jobHandlerName
* @throws SchedulerException
*/
public void deleteJob(String jobHandlerName) throws SchedulerException { public void deleteJob(String jobHandlerName) throws SchedulerException {
scheduler.deleteJob(new JobKey(jobHandlerName)); scheduler.deleteJob(new JobKey(jobHandlerName));
} }
/**
* Quartz Job
*
* @param jobHandlerName
* @throws SchedulerException
*/
public void pauseJob(String jobHandlerName) throws SchedulerException { public void pauseJob(String jobHandlerName) throws SchedulerException {
scheduler.pauseJob(new JobKey(jobHandlerName)); scheduler.pauseJob(new JobKey(jobHandlerName));
} }
/**
* Quartz Job
*
* @param jobHandlerName
* @throws SchedulerException
*/
public void resumeJob(String jobHandlerName) throws SchedulerException { public void resumeJob(String jobHandlerName) throws SchedulerException {
scheduler.resumeJob(new JobKey(jobHandlerName)); scheduler.resumeJob(new JobKey(jobHandlerName));
scheduler.resumeTrigger(new TriggerKey(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) public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)
throws SchedulerException { throws SchedulerException {
JobDataMap data = new JobDataMap(); JobDataMap data = new JobDataMap();

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -17,5 +17,10 @@ public interface InfErrorCodeConstants {
// ========== 定时任务 1001001000 ========== // ========== 定时任务 1001001000 ==========
ErrorCode JOB_NOT_EXISTS = new ErrorCode(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 表达式不正确");
} }

View File

@ -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.InfJobPageReqVO;
import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobUpdateReqVO; import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobUpdateReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO; import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
import org.quartz.SchedulerException;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection; import java.util.Collection;
@ -24,21 +25,36 @@ public interface InfJobService {
* @param createReqVO * @param createReqVO
* @return * @return
*/ */
Long createJob(@Valid InfJobCreateReqVO createReqVO); Long createJob(@Valid InfJobCreateReqVO createReqVO) throws SchedulerException;
/** /**
* *
* *
* @param updateReqVO * @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 * @param id
*/ */
void deleteJob(Long id); void deleteJob(Long id) throws SchedulerException;
/** /**
* *

View File

@ -1,7 +1,8 @@
package cn.iocoder.dashboard.modules.infra.service.job.impl; 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.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.InfJobCreateReqVO;
import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobExportReqVO; import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobExportReqVO;
import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO; 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.convert.job.InfJobConvert;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO; 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.dal.mysql.job.InfJobMapper;
import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum;
import cn.iocoder.dashboard.modules.infra.service.job.InfJobService; import cn.iocoder.dashboard.modules.infra.service.job.InfJobService;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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 * Service
@ -31,41 +37,109 @@ public class InfJobServiceImpl implements InfJobService {
@Resource @Resource
private InfJobMapper jobMapper; private InfJobMapper jobMapper;
@Resource
private SchedulerManager schedulerManager;
@Override @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); InfJobDO job = InfJobConvert.INSTANCE.convert(createReqVO);
if (job.getMonitorTimeout() == null) { job.setStatus(InfJobStatusEnum.INIT.getStatus());
job.setMonitorTimeout(0); fillJobMonitorTimeoutEmpty(job);
}
jobMapper.insert(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(); return job.getId();
} }
@Override @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); InfJobDO updateObj = InfJobConvert.INSTANCE.convert(updateReqVO);
if (updateObj.getMonitorTimeout() == null) { fillJobMonitorTimeoutEmpty(updateObj);
updateObj.setMonitorTimeout(0);
}
jobMapper.updateById(updateObj); jobMapper.updateById(updateObj);
// 更新 Job 到 Quartz 中
schedulerManager.updateJob(job.getHandlerName(), updateReqVO.getHandlerParam(), updateReqVO.getCronExpression());
} }
@Override @Override
public void deleteJob(Long id) { @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);
}
// 校验存在 // 校验存在
this.validateJobExists(id); InfJobDO job = this.validateJobExists(id);
// 更新 // 校验是否已经为当前状态
jobMapper.deleteById(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());
}
} }
private void validateJobExists(Long id) { @Override
if (jobMapper.selectById(id) == null) { public void triggerJob(Long id) throws SchedulerException {
throw ServiceExceptionUtil.exception(JOB_NOT_EXISTS); // 校验存在
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 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); return jobMapper.selectList(exportReqVO);
} }
private static void fillJobMonitorTimeoutEmpty(InfJobDO job) {
if (job.getMonitorTimeout() == null) {
job.setMonitorTimeout(0);
}
}
} }

View File

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

View File

@ -63,7 +63,7 @@ public class ${table.className}Controller {
@GetMapping("/get") @GetMapping("/get")
@ApiOperation("获得${table.classComment}") @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')") @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
public CommonResult<${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) { public CommonResult<${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id); ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
@ -72,7 +72,7 @@ public class ${table.className}Controller {
@GetMapping("/list") @GetMapping("/list")
@ApiOperation("获得${table.classComment}列表") @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')") @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
public CommonResult<List<${table.className}RespVO>> get${simpleClassName}List(@RequestParam("ids") Collection<${primaryColumn.javaType}> ids) { 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); List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(ids);