1. 完成 Job 的 CRUD 功能

This commit is contained in:
YunaiV
2021-02-14 23:01:21 +08:00
parent bc1504d991
commit f60855faa0
27 changed files with 1277 additions and 1374 deletions

View File

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

View File

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

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

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

View File

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

View File

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

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

View File

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