实现,页面上配置crontab表达式进行动态创建定时任务
1.前端页面实现增删改查功能
点击立即执行,请求:localhost/job/run/jobId接口,进行任务的立即执行
点击恢复任务,请求:localhost/job/resume/jobId接口,进行任务的正常启动持续执行
2. 后端代码
(1)JobController
@Slf4j
@Validated
@RestController
@RequestMapping("job")
public class JobController extends BaseController {
private String message;
@Autowired
private JobService jobService;
@GetMapping
@RequiresPermissions("job:view")
public Map<String, Object> jobList(QueryRequest request, Job job) {
return getDataTable(this.jobService.findJobs(request, job));
}
@GetMapping("cron/check")
public boolean checkCron(String cron) {
try {
return CronExpression.isValidExpression(cron);
} catch (Exception e) {
return false;
}
}
@Log("新增定时任务")
@PostMapping
@RequiresPermissions("job:add")
public void addJob(@Valid Job job) throws FebsException {
try {
this.jobService.createJob(job);
} catch (Exception e) {
message = "新增定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@Log("删除定时任务")
@DeleteMapping("/{jobIds}")
@RequiresPermissions("job:delete")
public void deleteJob(@NotBlank(message = "{required}") @PathVariable String jobIds) throws FebsException {
try {
String[] ids = jobIds.split(StringPool.COMMA);
this.jobService.deleteJobs(ids);
} catch (Exception e) {
message = "删除定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@Log("修改定时任务")
@PutMapping
@RequiresPermissions("job:update")
public void updateJob(@Valid Job job) throws FebsException {
try {
this.jobService.updateJob(job);
} catch (Exception e) {
message = "修改定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@Log("执行定时任务")
@GetMapping("run/{jobId}")
@RequiresPermissions("job:run")
public void runJob(@NotBlank(message = "{required}") @PathVariable String jobId) throws FebsException {
try {
this.jobService.run(jobId);
} catch (Exception e) {
message = "执行定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@Log("暂停定时任务")
@GetMapping("pause/{jobId}")
@RequiresPermissions("job:pause")
public void pauseJob(@NotBlank(message = "{required}") @PathVariable String jobId) throws FebsException {
try {
this.jobService.pause(jobId);
} catch (Exception e) {
message = "暂停定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@Log("恢复定时任务")
@GetMapping("resume/{jobId}")
@RequiresPermissions("job:resume")
public void resumeJob(@NotBlank(message = "{required}") @PathVariable String jobId) throws FebsException {
try {
this.jobService.resume(jobId);
} catch (Exception e) {
message = "恢复定时任务失败";
log.error(message, e);
throw new FebsException(message);
}
}
@PostMapping("excel")
@RequiresPermissions("job:export")
public void export(QueryRequest request, Job job, HttpServletResponse response) throws FebsException {
try {
List<Job> jobs = this.jobService.findJobs(request, job).getRecords();
ExcelKit.$Export(Job.class, response).downXlsx(jobs, false);
} catch (Exception e) {
message = "导出Excel失败";
log.error(message, e);
throw new FebsException(message);
}
}
}
(2)JobService
@Slf4j
@Service("JobService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements JobService {
@Autowired
private Scheduler scheduler;
/**
* 项目启动时,初始化定时器
*/
@PostConstruct
public void init() {
List<Job> scheduleJobList = this.baseMapper.queryList();
// 如果不存在,则创建
scheduleJobList.forEach(scheduleJob -> {
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
if (cronTrigger == null) {
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
} else {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
}
});
}
@Override
public Job findJob(Long jobId) {
return this.getById(jobId);
}
@Override
public IPage<Job> findJobs(QueryRequest request, Job job) {
try {
LambdaQueryWrapper<Job> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(job.getBeanName())) {
queryWrapper.eq(Job::getBeanName, job.getBeanName());
}
if (StringUtils.isNotBlank(job.getMethodName())) {
queryWrapper.eq(Job::getMethodName, job.getMethodName());
}
if (StringUtils.isNotBlank(job.getParams())) {
queryWrapper.like(Job::getParams, job.getParams());
}
if (StringUtils.isNotBlank(job.getRemark())) {
queryWrapper.like(Job::getRemark, job.getRemark());
}
if (StringUtils.isNotBlank(job.getStatus())) {
queryWrapper.eq(Job::getStatus, job.getStatus());
}
if (StringUtils.isNotBlank(job.getCreateTimeFrom()) && StringUtils.isNotBlank(job.getCreateTimeTo())) {
queryWrapper
.ge(Job::getCreateTime, job.getCreateTimeFrom())
.le(Job::getCreateTime, job.getCreateTimeTo());
}
Page<Job> page = new Page<>(request.getPageNum(), request.getPageSize());
SortUtil.handlePageSort(request, page, "createTime", FebsConstant.ORDER_DESC, true);
return this.page(page, queryWrapper);
} catch (Exception e) {
log.error("获取任务失败", e);
return null;
}
}
@Override
@Transactional
public void createJob(Job job) {
job.setCreateTime(new Date());
job.setStatus(Job.ScheduleStatus.PAUSE.getValue());
this.save(job);
ScheduleUtils.createScheduleJob(scheduler, job);
}
@Override
@Transactional
public void updateJob(Job job) {
ScheduleUtils.updateScheduleJob(scheduler, job);
this.baseMapper.updateById(job);
}
@Override
@Transactional
public void deleteJobs(String[] jobIds) {
List<String> list = Arrays.asList(jobIds);
list.forEach(jobId -> ScheduleUtils.deleteScheduleJob(scheduler, Long.valueOf(jobId)));
this.baseMapper.deleteBatchIds(list);
}
@Override
@Transactional
public int updateBatch(String jobIds, String status) {
List<String> list = Arrays.asList(jobIds.split(StringPool.COMMA));
Job job = new Job();
job.setStatus(status);
return this.baseMapper.update(job, new LambdaQueryWrapper<Job>().in(Job::getJobId, list));
}
@Override
@Transactional
public void run(String jobIds) {
String[] list = jobIds.split(StringPool.COMMA);
Arrays.stream(list).forEach(jobId -> ScheduleUtils.run(scheduler, this.findJob(Long.valueOf(jobId))));
}
@Override
@Transactional
public void pause(String jobIds) {
String[] list = jobIds.split(StringPool.COMMA);
Arrays.stream(list).forEach(jobId -> ScheduleUtils.pauseJob(scheduler, Long.valueOf(jobId)));
this.updateBatch(jobIds, Job.ScheduleStatus.PAUSE.getValue());
}
@Override
@Transactional
public void resume(String jobIds) {
String[] list = jobIds.split(StringPool.COMMA);
Arrays.stream(list).forEach(jobId -> ScheduleUtils.resumeJob(scheduler, Long.valueOf(jobId)));
this.updateBatch(jobIds, Job.ScheduleStatus.NORMAL.getValue());
}
}
(3)ScheduleRunnable
这个是通过启动线程,来执行bean下的method,通过反射获取到具体的方法
/**
* 执行定时任务
*/
@Slf4j
public class ScheduleRunnable implements Runnable {
private Object target;
private Method method;
private String params;
ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
this.target = SpringContextUtil.getBean(beanName);
this.params = params;
if (StringUtils.isNotBlank(params)) {
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
} catch (Exception e) {
log.error("执行定时任务失败", e);
}
}
}
(4)ScheduleJob:具体用来启动配置的runnable job
/**
* 定时任务
*/
@Slf4j
public class ScheduleJob extends QuartzJobBean {
private ExecutorService service = Executors.newSingleThreadExecutor();
@Override
protected void executeInternal(JobExecutionContext context) {
Job scheduleJob = (Job) context.getMergedJobDataMap().get(Job.JOB_PARAM_KEY);
// 获取spring bean
JobLogService scheduleJobLogService = SpringContextUtil.getBean(JobLogService.class);
JobLog jobLog = new JobLog();
jobLog.setJobId(scheduleJob.getJobId());
jobLog.setBeanName(scheduleJob.getBeanName());
jobLog.setMethodName(scheduleJob.getMethodName());
jobLog.setParams(scheduleJob.getParams());
jobLog.setCreateTime(new Date());
long startTime = System.currentTimeMillis();
try {
// 执行任务
log.info("任务准备执行,任务ID:{}", scheduleJob.getJobId());
ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(),
scheduleJob.getParams());
Future<?> future = service.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes(times);
// 任务状态 0:成功 1:失败
jobLog.setStatus(JobLog.JOB_SUCCESS);
log.info("任务执行完毕,任务ID:{} 总共耗时:{} 毫秒", scheduleJob.getJobId(), times);
} catch (Exception e) {
log.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
long times = System.currentTimeMillis() - startTime;
jobLog.setTimes(times);
// 任务状态 0:成功 1:失败
jobLog.setStatus(JobLog.JOB_FAIL);
jobLog.setError(StringUtils.substring(e.toString(), 0, 2000));
} finally {
scheduleJobLogService.saveJobLog(jobLog);
}
}
}
(5)定时任务工具类
/**
* 定时任务工具类
*/
@Slf4j
public class ScheduleUtils {
protected ScheduleUtils() {
}
private static final String JOB_NAME_PREFIX = "TASK_";
/**
* 获取触发器key
*/
private static TriggerKey getTriggerKey(Long jobId) {
return TriggerKey.triggerKey(JOB_NAME_PREFIX + jobId);
}
/**
* 获取jobKey
*/
private static JobKey getJobKey(Long jobId) {
return JobKey.jobKey(JOB_NAME_PREFIX + jobId);
}
/**
* 获取表达式触发器
*/
public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
} catch (SchedulerException e) {
log.error("获取Cron表达式失败", e);
}
return null;
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, Job scheduleJob) {
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId()))
.build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId()))
.withSchedule(scheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
pauseJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException e) {
log.error("创建定时任务失败", e);
}
}
/**
* 更新定时任务
*/
public static void updateScheduleJob(Scheduler scheduler, Job scheduleJob) {
try {
TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());
if (trigger == null) {
throw new SchedulerException("获取Cron表达式失败");
} else {
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 参数
trigger.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
}
scheduler.rescheduleJob(triggerKey, trigger);
// 暂停任务
if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
pauseJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException e) {
log.error("更新定时任务失败", e);
}
}
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, Job scheduleJob) {
try {
// 参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(Job.JOB_PARAM_KEY, scheduleJob);
scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
} catch (SchedulerException e) {
log.error("执行定时任务失败", e);
}
}
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, Long jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("暂停定时任务失败", e);
}
}
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, Long jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("恢复定时任务失败", e);
}
}
/**
* 删除定时任务
*/
public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {
try {
scheduler.deleteJob(getJobKey(jobId));
} catch (SchedulerException e) {
log.error("删除定时任务失败", e);
}
}
}
(6)测试任务类
@Slf4j
@Component
public class TestTask {
public void test(String params) {
log.info("我是带参数的test方法,正在被执行,参数为:{}" , params);
}
public void test1() {
log.info("我是不带参数的test1方法,正在被执行");
}
}
(7)添加配置类
/**
* 定时任务配置
*
*/
@Configuration
public class ScheduleConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
// quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "MyScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
// JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 集群配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
factory.setQuartzProperties(prop);
factory.setSchedulerName("MyScheduler");
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler
// 启动时更新己存在的 Job
factory.setOverwriteExistingJobs(true);
// 设置自动启动,默认为 true
factory.setAutoStartup(true);
return factory;
}
}