package com.ruoyi.cron.runner; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.ruoyi.common.utils.IdUtils; import com.ruoyi.common.utils.JsonUtils; import com.ruoyi.common.utils.MongoUtil; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.cron.document.CronTask; import com.ruoyi.cron.document.CronTaskLog; import com.ruoyi.cron.event.CronTaskChangeEvent; import com.ruoyi.cron.event.CronTaskEndEvent; import com.ruoyi.cron.event.CronTaskEvent; import com.ruoyi.cron.event.CronTaskStartEvent; import com.ruoyi.cron.vo.CronTaskVo; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; @Configuration @Slf4j @RequiredArgsConstructor public class CronRunner implements ApplicationRunner { private final CronBeanPostProcessor processor; private final ThreadPoolTaskScheduler threadPoolTaskScheduler; private final MongoTemplate template; private final ApplicationContext act; private final ExpressionParser parser = new SpelExpressionParser(); @Getter private final Map> cronMap = MapUtil.newHashMap(); @Getter private final List currentLogs = new CopyOnWriteArrayList<>(); @Override public void run(ApplicationArguments args) throws Exception { log.info("本地定时模块启动成功"); ListUtil.sort(processor.getList(), (o2, o1) -> o1.getOrder() - o2.getOrder()); if (log.isDebugEnabled()) { log.debug("可用的本地定时任务列表: {}", JsonUtils.toPrettyString(processor.getList())); } template.findAll(CronTask.class).forEach(cronTask -> { addCronTask(cronTask); }); } private void addCronTask(CronTask cronTask) { //未激活不定时 if (!cronTask.getEnabled()) { return; } //手动任务不定时 if (StrUtil.isBlank(cronTask.getCron())) { return; } CronTrigger trigger = null; try { trigger = new CronTrigger(cronTask.getCron()); } catch (Exception ex) { log.warn("本地定时任务cron表达式错误,定时任务编号:" + cronTask.getId(), ex); return; } final CronTaskVo vo = processor.getList().stream().filter(bean -> bean.getId().equals(cronTask.getTaskId())).findFirst().orElse(null); if (ObjUtil.isNull(vo)) { log.warn("未发现任务:" + cronTask.getTaskId()); return; } cronMap.put(cronTask.getId(), threadPoolTaskScheduler.schedule(() -> { SpringUtils.publishEvent(new CronTaskEvent(cronTask.getId(), cronTask.getTaskId(), cronTask.getParamELs())); }, trigger)); log.info("本地定时任务启动成功,定时任务编号:{},cron:{}", cronTask.getId(), cronTask.getCron()); } @EventListener @Async public void onCronTaskChange(CronTaskChangeEvent event) { CronTask cronTask = event.getSource(); if (event.getChangeType() == CronTaskChangeEvent.ChangeType.ADD) { addCronTask(cronTask); } else if (event.getChangeType() == CronTaskChangeEvent.ChangeType.DELETE) { if (cronMap.containsKey(cronTask.getId())) { cronMap.get(cronTask.getId()).cancel(true); cronMap.remove(cronTask.getId()); log.info("本地定时任务停止成功,定时任务编号:{}", cronTask.getId()); } } else if (event.getChangeType() == CronTaskChangeEvent.ChangeType.UPDATE) { if (cronMap.containsKey(cronTask.getId())) { cronMap.get(cronTask.getId()).cancel(true); cronMap.remove(cronTask.getId()); log.info("本地定时任务停止成功,定时任务编号:{}", cronTask.getId()); } addCronTask(cronTask); } } @EventListener @Async public void onCronTask(CronTaskEvent event) { long startTime = System.currentTimeMillis(); final CronTaskVo vo = processor.getList().stream().filter(bean -> bean.getId().equals(event.getTaskId())).findFirst().orElse(null); if (ObjUtil.isNull(vo)) { log.warn("未发现任务:" + event.getTaskId()); return; } CronTaskLog taskLog = new CronTaskLog(); taskLog.setId(Long.valueOf(IdUtils.nextDateId(CronTaskLog.class.getSimpleName(), 17))); taskLog.setTaskId(vo.getId()); taskLog.setGroupId(vo.getGroup()); taskLog.setStartTime(new Date(startTime)); TaskLogImpl.logHolder.set(taskLog); try { Object[] param = null; if (vo.getMethod().getParameterCount() > 0) { EvaluationContext context = new StandardEvaluationContext(act); context.setVariable("act", act); context.setVariable("bean", vo.getBean()); param = new Object[vo.getMethod().getParameterCount()]; for (int i = 0; i < param.length; i++) { param[i] = parser.parseExpression(event.getParamELs().get(i)).getValue(context, vo.getMethod().getParameterTypes()[i]); } } taskLog.setParams(JsonUtils.toJsonString(param)); currentLogs.add(taskLog); log.info("开始执行任务:" + vo.getName() + "\t参数:" + taskLog.getParams()); SpringUtils.publishEvent(new CronTaskStartEvent(event.getCronId(), vo.getId(),vo.getGroup())); vo.getMethod().invoke(vo.getBean(), param); taskLog.setOk(true); log.info("执行任务成功:" + vo.getName()); // 执行成功后,继续执行子任务 if (ObjUtil.isNotNull(event.getCronId() != null)) { List list = MongoUtil.find(CronTask.class, MongoUtil.conditions().put("pid", event.getCronId()).put("enabled", true)); if (CollUtil.isNotEmpty(list)) { list.forEach(cronTask -> { SpringUtils.publishEvent(new CronTaskEvent(cronTask.getId(), cronTask.getTaskId(), cronTask.getParamELs())); }); } } } catch (Exception ex) { taskLog.setOk(false); taskLog.setMsg(ex.getCause().getMessage()); if (StrUtil.isBlank(taskLog.getEx())) { taskLog.setEx(ExceptionUtil.stacktraceToString(ex.getCause())); } else { taskLog.setEx(taskLog.getEx() + "\n" + ExceptionUtil.stacktraceToString(ex.getCause())); } log.warn("执行任务失败:" + vo.getName(), ex); } finally { TaskLogImpl.logHolder.remove(); } taskLog.setEndTime(new Date()); SpringUtils.publishEvent(new CronTaskEndEvent(event.getCronId(), vo.getId(),vo.getGroup())); currentLogs.remove(taskLog); template.insert(taskLog); } }