diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 09aac1ba8..12b2da69a 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -34,6 +34,7 @@ 1.9.2 + 2.3.1 2.2.0 1.7.1 @@ -251,6 +252,11 @@ + + com.xuxueli + xxl-job-core + ${xxl-job.version} + cn.iocoder.cloud yudao-spring-boot-starter-job diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java index 5fbacd93c..e338e2c73 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java @@ -1,13 +1,10 @@ package cn.iocoder.yudao.framework.tenant.config; -import cn.hutool.core.annotation.AnnotationUtil; import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect; import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect; import cn.iocoder.yudao.framework.tenant.core.mq.TenantChannelInterceptor; import cn.iocoder.yudao.framework.tenant.core.mq.TenantFunctionAroundWrapper; import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; @@ -19,6 +16,7 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.module.system.api.tenant.TenantApi; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import com.xxl.job.core.executor.XxlJobExecutor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -103,19 +101,25 @@ public class YudaoTenantAutoConfiguration { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (!(bean instanceof JobHandler)) { + if (!(bean instanceof XxlJobExecutor)) { return bean; } - // 有 TenantJob 注解的情况下,才会进行处理 - if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { - return bean; - } - - // 使用 TenantJobHandlerDecorator 装饰 - return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); +// // 有 TenantJob 注解的情况下,才会进行处理 +// if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { +// return bean; +// } +// +// // 使用 TenantJobHandlerDecorator 装饰 +// return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); + return bean; } }; } + @Bean + public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { + return new TenantJobAspect(tenantFrameworkService); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java new file mode 100644 index 000000000..d3d0a1ac6 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.framework.tenant.core.job; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Aspect +@RequiredArgsConstructor +@Slf4j +public class TenantJobAspect { + + private final TenantFrameworkService tenantFrameworkService; + + @Around("@annotation(xxlJob)") + public Object around(ProceedingJoinPoint joinPoint, XxlJob xxlJob) throws Throwable { + // 如果非多租户 Job,则跳过 + TenantJob tenantJob = getClassAnnotation(joinPoint, TenantJob.class); + if (tenantJob == null) { + return joinPoint.proceed(); + } + + // 如果是多租户 Job,则会按照租户逐个执行 Job 的逻辑 + execute(joinPoint, xxlJob); + return null; // JobHandler 无返回 + } + + private void execute(ProceedingJoinPoint joinPoint, XxlJob xxlJob) { + // 获得租户列表 + List tenantIds = tenantFrameworkService.getTenantIds(); + if (CollUtil.isEmpty(tenantIds)) { + return; + } + + // 逐个租户,执行 Job + Map results = new ConcurrentHashMap<>(); + tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况 + TenantUtils.execute(tenantId, () -> { + try { + joinPoint.proceed(); + } catch (Throwable e) { + results.put(tenantId, ExceptionUtil.getRootCauseMessage(e)); + // 打印异常 + XxlJobHelper.log(StrUtil.format("[多租户({}) 执行任务({}),发生异常:{}]", + tenantId, xxlJob.value(), ExceptionUtils.getStackTrace(e))); + } + }); + }); + // 如果 results 非空,说明发生了异常,标记 XXL-Job 执行失败 + if (CollUtil.isNotEmpty(results)) { + XxlJobHelper.handleFail(JsonUtils.toJsonString(results)); + } + } + + @SuppressWarnings("SameParameterValue") + private static T getClassAnnotation(ProceedingJoinPoint joinPoint, Class annotationClass) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java deleted file mode 100644 index 25a6e016d..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobHandlerDecorator.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.framework.tenant.core.job; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; -import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; -import lombok.AllArgsConstructor; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 多租户 JobHandler 装饰器 - * 任务执行时,会按照租户逐个执行 Job 的逻辑 - * - * 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。 - * - * @author 芋道源码 - */ -@AllArgsConstructor -public class TenantJobHandlerDecorator implements JobHandler { - - private final TenantFrameworkService tenantFrameworkService; - /** - * 被装饰的 Job - */ - private final JobHandler jobHandler; - - @Override - public final String execute(String param) throws Exception { - // 获得租户列表 - List tenantIds = tenantFrameworkService.getTenantIds(); - if (CollUtil.isEmpty(tenantIds)) { - return null; - } - - // 逐个租户,执行 Job - Map results = new ConcurrentHashMap<>(); - tenantIds.parallelStream().forEach(tenantId -> { // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况 - try { - // 设置租户 - TenantContextHolder.setTenantId(tenantId); - // 执行 Job - String result = jobHandler.execute(param); - // 添加结果 - results.put(tenantId, result); - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - TenantContextHolder.clear(); - } - }); - return JsonUtils.toJsonString(results); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/pom.xml b/yudao-framework/yudao-spring-boot-starter-job/pom.xml index a3bcc12dd..012a0fb46 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-job/pom.xml @@ -12,10 +12,7 @@ jar ${project.artifactId} - 任务拓展 - 1. 定时任务,基于 Quartz 拓展 - 2. 异步任务,基于 Spring Async 拓展 - + 任务拓展,基于 XXL-Job 实现 https://github.com/YunaiV/ruoyi-vue-pro @@ -24,10 +21,22 @@ yudao-common - + org.springframework.boot - spring-boot-starter-quartz + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter + true + + + + + com.xuxueli + xxl-job-core diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/XxlJobProperties.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/XxlJobProperties.java new file mode 100644 index 000000000..037c0ce02 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/XxlJobProperties.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.framework.quartz.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * XXL-Job 配置类 + */ +@ConfigurationProperties("xxl.job") +@Validated +@Data +public class XxlJobProperties { + + /** + * 是否开启,默认为 true 关闭 + */ + private Boolean enabled = true; + /** + * 访问令牌 + */ + private String accessToken; + /** + * 控制器配置 + */ + @NotNull(message = "控制器配置不能为空") + private AdminProperties admin; + /** + * 执行器配置 + */ + @NotNull(message = "执行器配置不能为空") + private ExecutorProperties executor; + + /** + * XXL-Job 调度器配置类 + */ + @Data + @Valid + public static class AdminProperties { + + /** + * 调度器地址 + */ + @NotEmpty(message = "调度器地址不能为空") + private String addresses; + + } + + /** + * XXL-Job 执行器配置类 + */ + @Data + @Valid + public static class ExecutorProperties { + + /** + * 默认端口 + * + * 这里使用 -1 表示随机 + */ + private static final Integer PORT_DEFAULT = -1; + + /** + * 默认日志保留天数 + * + * 如果想永久保留,则设置为 -1 + */ + private static final Integer LOG_RETENTION_DAYS_DEFAULT = 30; + + /** + * 应用名 + */ + @NotEmpty(message = "应用名不能为空") + private String appName; + /** + * 执行器的 IP + */ + private String ip; + /** + * 执行器的 Port + */ + private Integer port = PORT_DEFAULT; + /** + * 日志地址 + */ + @NotEmpty(message = "日志地址不能为空") + private String logPath; + /** + * 日志保留天数 + */ + private Integer logRetentionDays = LOG_RETENTION_DAYS_DEFAULT; + + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java deleted file mode 100644 index 144e4773d..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoQuartzAutoConfiguration.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.config; - -import cn.iocoder.yudao.framework.quartz.core.scheduler.SchedulerManager; -import org.quartz.Scheduler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - * 定时任务 Configuration - */ -@Configuration -@EnableScheduling // 开启 Spring 自带的定时任务 -public class YudaoQuartzAutoConfiguration { - - @Bean - public SchedulerManager schedulerManager(Scheduler scheduler) { - return new SchedulerManager(scheduler); - } - -} diff --git a/归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoXxlJobAutoConfiguration.java similarity index 58% rename from 归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobAutoConfiguration.java rename to yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoXxlJobAutoConfiguration.java index 40c96fc61..50420311b 100644 --- a/归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoXxlJobAutoConfiguration.java @@ -1,45 +1,35 @@ -package cn.iocoder.mall.xxljob.config; +package cn.iocoder.yudao.framework.quartz.config; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - -import java.util.Objects; +import org.springframework.scheduling.annotation.EnableScheduling; /** * XXL-Job 自动配置类 + * + * @author 芋道源码 */ @Configuration @ConditionalOnClass(XxlJobSpringExecutor.class) @ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties({XxlJobProperties.class}) -public class XxlJobAutoConfiguration { - - private static final Logger LOGGER = LoggerFactory.getLogger(XxlJobAutoConfiguration.class); - - private final XxlJobProperties properties; - - public XxlJobAutoConfiguration(XxlJobProperties properties) { - this.properties = properties; - } +@EnableScheduling // 开启 Spring 自带的定时任务 +@Slf4j +public class YudaoXxlJobAutoConfiguration { @Bean @ConditionalOnMissingBean - public XxlJobExecutor xxlJobExecutor() { - LOGGER.info("初始化 XXL-Job 执行器的配置"); - - // 参数校验 - XxlJobProperties.AdminProperties admin = this.properties.getAdmin(); - XxlJobProperties.ExecutorProperties executor = this.properties.getExecutor(); - Objects.requireNonNull(admin, "xxl job admin properties must not be null."); - Objects.requireNonNull(executor, "xxl job executor properties must not be null."); + public XxlJobExecutor xxlJobExecutor(XxlJobProperties properties) { + log.info("[xxlJobExecutor][初始化 XXL-Job 执行器的配置]"); + XxlJobProperties.AdminProperties admin = properties.getAdmin(); + XxlJobProperties.ExecutorProperties executor = properties.getExecutor(); // 初始化执行器 XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor(); @@ -49,7 +39,7 @@ public class XxlJobAutoConfiguration { xxlJobExecutor.setLogPath(executor.getLogPath()); xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays()); xxlJobExecutor.setAdminAddresses(admin.getAddresses()); - xxlJobExecutor.setAccessToken(this.properties.getAccessToken()); + xxlJobExecutor.setAccessToken(properties.getAccessToken()); return xxlJobExecutor; } diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/enums/JobDataKeyEnum.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/enums/JobDataKeyEnum.java deleted file mode 100644 index 3154c4007..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/enums/JobDataKeyEnum.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.enums; - -/** - * Quartz Job Data 的 key 枚举 - */ -public enum JobDataKeyEnum { - - JOB_ID, - JOB_HANDLER_NAME, - JOB_HANDLER_PARAM, - JOB_RETRY_COUNT, // 最大重试次数 - JOB_RETRY_INTERVAL, // 每次重试间隔 - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandler.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandler.java deleted file mode 100644 index 381e132df..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.handler; - -/** - * 任务处理器 - * - * @author 芋道源码 - */ -public interface JobHandler { - - /** - * 执行任务 - * - * @param param 参数 - * @return 结果 - * @throws Exception 异常 - */ - String execute(String param) throws Exception; - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandlerInvoker.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandlerInvoker.java deleted file mode 100644 index d47be8352..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandlerInvoker.java +++ /dev/null @@ -1,113 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.handler; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.thread.ThreadUtil; -import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum; -import cn.iocoder.yudao.framework.quartz.core.service.JobLogFrameworkService; -import lombok.extern.slf4j.Slf4j; -import org.quartz.DisallowConcurrentExecution; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.quartz.PersistJobDataAfterExecution; -import org.springframework.context.ApplicationContext; -import org.springframework.scheduling.quartz.QuartzJobBean; - -import javax.annotation.Resource; -import java.util.Date; - -import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.diff; - -/** - * 基础 Job 调用者,负责调用 {@link JobHandler#execute(String)} 执行任务 - * - * @author 芋道源码 - */ -@DisallowConcurrentExecution -@PersistJobDataAfterExecution -@Slf4j -public class JobHandlerInvoker extends QuartzJobBean { - - @Resource - private ApplicationContext applicationContext; - - @Resource - private JobLogFrameworkService jobLogFrameworkService; - - @Override - protected void executeInternal(JobExecutionContext executionContext) throws JobExecutionException { - // 第一步,获得 Job 数据 - Long jobId = executionContext.getMergedJobDataMap().getLong(JobDataKeyEnum.JOB_ID.name()); - String jobHandlerName = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_NAME.name()); - String jobHandlerParam = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_PARAM.name()); - int refireCount = executionContext.getRefireCount(); - int retryCount = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_COUNT.name(), 0); - int retryInterval = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), 0); - - // 第二步,执行任务 - Long jobLogId = null; - Date startTime = new Date(); - String data = null; - Throwable exception = null; - try { - // 记录 Job 日志(初始) - jobLogId = jobLogFrameworkService.createJobLog(jobId, startTime, jobHandlerName, jobHandlerParam, refireCount + 1); - // 执行任务 - data = this.executeInternal(jobHandlerName, jobHandlerParam); - } catch (Throwable ex) { - exception = ex; - } - - // 第三步,记录执行日志 - this.updateJobLogResultAsync(jobLogId, startTime, data, exception, executionContext); - - // 第四步,处理有异常的情况 - handleException(exception, refireCount, retryCount, retryInterval); - } - - private String executeInternal(String jobHandlerName, String jobHandlerParam) throws Exception { - // 获得 JobHandler 对象 - JobHandler jobHandler = applicationContext.getBean(jobHandlerName, JobHandler.class); - Assert.notNull(jobHandler, "JobHandler 不会为空"); - // 执行任务 - return jobHandler.execute(jobHandlerParam); - } - - private void updateJobLogResultAsync(Long jobLogId, Date startTime, String data, Throwable exception, - JobExecutionContext executionContext) { - Date endTime = new Date(); - // 处理是否成功 - boolean success = exception == null; - if (!success) { - data = getRootCauseMessage(exception); - } - // 更新日志 - try { - jobLogFrameworkService.updateJobLogResultAsync(jobLogId, endTime, (int) diff(endTime, startTime), success, data); - } catch (Exception ex) { - log.error("[executeInternal][Job({}) logId({}) 记录执行日志失败({}/{})]", - executionContext.getJobDetail().getKey(), jobLogId, success, data); - } - } - - private void handleException(Throwable exception, - int refireCount, int retryCount, int retryInterval) throws JobExecutionException { - // 如果有异常,则进行重试 - if (exception == null) { - return; - } - // 情况一:如果到达重试上限,则直接抛出异常即可 - if (refireCount >= retryCount) { - throw new JobExecutionException(exception); - } - - // 情况二:如果未到达重试上限,则 sleep 一定间隔时间,然后重试 - // 这里使用 sleep 来实现,主要还是希望实现比较简单。因为,同一时间,不会存在大量失败的 Job。 - if (retryInterval > 0) { - ThreadUtil.sleep(retryInterval); - } - // 第二个参数,refireImmediately = true,表示立即重试 - throw new JobExecutionException(exception, true); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java deleted file mode 100644 index 1c8aa7ba1..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java +++ /dev/null @@ -1,130 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.scheduler; - -import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum; -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker; -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 { - - private final Scheduler scheduler; - - public SchedulerManager(Scheduler scheduler) { - this.scheduler = scheduler; - } - - /** - * 添加 Job 到 Quartz 中 - * - * @param jobId 任务编号 - * @param jobHandlerName 任务处理器的名字 - * @param jobHandlerParam 任务处理器的参数 - * @param cronExpression CRON 表达式 - * @param retryCount 重试次数 - * @param retryInterval 重试间隔 - * @throws SchedulerException 添加异常 - */ - public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression, - Integer retryCount, Integer retryInterval) - throws SchedulerException { - // 创建 JobDetail 对象 - JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class) - .usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId) - .usingJobData(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName) - .withIdentity(jobHandlerName).build(); - // 创建 Trigger 对象 - Trigger trigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval); - // 新增调度 - scheduler.scheduleJob(jobDetail, trigger); - } - - /** - * 更新 Job 到 Quartz - * - * @param jobHandlerName 任务处理器的名字 - * @param jobHandlerParam 任务处理器的参数 - * @param cronExpression CRON 表达式 - * @param retryCount 重试次数 - * @param retryInterval 重试间隔 - * @throws SchedulerException 更新异常 - */ - public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression, - Integer retryCount, Integer retryInterval) - throws SchedulerException { - // 创建新 Trigger 对象 - Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval); - // 修改调度 - 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(); // 无需重试,所以不设置 retryCount 和 retryInterval - data.put(JobDataKeyEnum.JOB_ID.name(), jobId); - data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName); - data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam); - // 触发任务 - scheduler.triggerJob(new JobKey(jobHandlerName), data); - } - - private Trigger buildTrigger(String jobHandlerName, String jobHandlerParam, String cronExpression, - Integer retryCount, Integer retryInterval) { - return TriggerBuilder.newTrigger() - .withIdentity(jobHandlerName) - .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)) - .usingJobData(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam) - .usingJobData(JobDataKeyEnum.JOB_RETRY_COUNT.name(), retryCount) - .usingJobData(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), retryInterval) - .build(); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/service/JobLogFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/service/JobLogFrameworkService.java deleted file mode 100644 index ca5d0ccbc..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/service/JobLogFrameworkService.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.service; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.util.Date; - -/** - * Job 日志 Framework Service 接口 - * - * @author 芋道源码 - */ -public interface JobLogFrameworkService { - - /** - * 创建 Job 日志 - * - * @param jobId 任务编号 - * @param beginTime 开始时间 - * @param jobHandlerName Job 处理器的名字 - * @param jobHandlerParam Job 处理器的参数 - * @param executeIndex 第几次执行 - * @return Job 日志的编号 - */ - Long createJobLog(@NotNull(message = "任务编号不能为空") Long jobId, - @NotNull(message = "开始时间") Date beginTime, - @NotEmpty(message = "Job 处理器的名字不能为空") String jobHandlerName, - String jobHandlerParam, - @NotNull(message = "第几次执行不能为空") Integer executeIndex); - - /** - * 更新 Job 日志的执行结果 - * - * @param logId 日志编号 - * @param endTime 结束时间。因为是异步,避免记录时间不准去 - * @param duration 运行时长,单位:毫秒 - * @param success 是否成功 - * @param result 成功数据 - */ - void updateJobLogResultAsync(@NotNull(message = "日志编号不能为空") Long logId, - @NotNull(message = "结束时间不能为空") Date endTime, - @NotNull(message = "运行时长不能为空") Integer duration, - boolean success, String result); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java deleted file mode 100644 index ccef9b374..000000000 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/util/CronUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.framework.quartz.core.util; - -import org.quartz.CronExpression; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Quartz Cron 表达式的工具类 - * - * @author 芋道源码 - */ -public class CronUtils { - - /** - * 校验 CRON 表达式是否有效 - * - * @param cronExpression CRON 表达式 - * @return 是否有效 - */ - public static boolean isValid(String cronExpression) { - return CronExpression.isValidExpression(cronExpression); - } - - /** - * 基于 CRON 表达式,获得下 n 个满足执行的时间 - * - * @param cronExpression CRON 表达式 - * @param n 数量 - * @return 满足条件的执行时间 - */ - public static List getNextTimes(String cronExpression, int n) { - // 获得 CronExpression 对象 - CronExpression cron; - try { - cron = new CronExpression(cronExpression); - } catch (ParseException e) { - throw new IllegalArgumentException(e.getMessage()); - } - // 从当前开始计算,n 个满足条件的 - Date now = new Date(); - List nextTimes = new ArrayList<>(n); - for (int i = 0; i < n; i++) { - Date nextTime = cron.getNextValidTimeAfter(now); - nextTimes.add(nextTime); - // 切换现在,为下一个触发时间; - now = nextTime; - } - return nextTimes; - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/package-info.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/package-info.java index cfd237b21..9781e8223 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/package-info.java +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/package-info.java @@ -1,7 +1,5 @@ /** - * 1. 定时任务,采用 Quartz 实现进程内的任务执行。 - * 考虑到高可用,使用 Quartz 自带的 MySQL 集群方案。 - * + * 1. 定时任务,基于 XXL-Job 实现。 * 2. 异步任务,采用 Spring Async 异步执行。 */ package cn.iocoder.yudao.framework.quartz; diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-job/src/main/resources/META-INF/spring.factories index cecc4094c..8504223e0 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/resources/META-INF/spring.factories +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/resources/META-INF/spring.factories @@ -1,3 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - cn.iocoder.yudao.framework.quartz.config.YudaoQuartzAutoConfiguration,\ + cn.iocoder.yudao.framework.quartz.config.YudaoXxlJobAutoConfiguration,\ cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration diff --git a/yudao-module-infra/yudao-module-infra-biz/pom.xml b/yudao-module-infra/yudao-module-infra-biz/pom.xml index bc0959d31..c6e61d6e8 100644 --- a/yudao-module-infra/yudao-module-infra-biz/pom.xml +++ b/yudao-module-infra/yudao-module-infra-biz/pom.xml @@ -94,11 +94,11 @@ spring-cloud-starter-alibaba-nacos-config - - - - - + + + cn.iocoder.cloud + yudao-spring-boot-starter-job + diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml index a7c65af00..04331a4a7 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml @@ -72,8 +72,10 @@ spring: name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 --- #################### 定时任务相关配置 #################### - ---- #################### 配置中心相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 --- #################### 服务保障相关配置 #################### diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml index b3805ba9c..fb06a723f 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml @@ -83,8 +83,10 @@ spring: name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 --- #################### 定时任务相关配置 #################### - ---- #################### 配置中心相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 --- #################### 服务保障相关配置 #################### diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml index 8805738b2..71fdaebc7 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml @@ -84,6 +84,15 @@ spring: id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式 destination: springCloudBus # 目标消息队列,默认为 springCloudBus +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + --- #################### 芋道相关配置 #################### yudao: diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/demo/DemoJob.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/demo/DemoJob.java new file mode 100644 index 000000000..6fa4ad62d --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/demo/DemoJob.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.system.job.demo; + +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +@Component +@TenantJob +public class DemoJob { + + @XxlJob("demoJob") + public void execute() { + System.out.println("美滋滋"); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml b/yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml index 11e6d04c5..0d8092d3b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml @@ -72,6 +72,10 @@ spring: name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 --- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 --- #################### 服务保障相关配置 #################### diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml b/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml index 2a58d430b..6133f5914 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml @@ -83,6 +83,11 @@ spring: --- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + --- #################### 服务保障相关配置 #################### # Lock4j 配置项 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml b/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml index 8c17aa7d3..a9d5f6e27 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml @@ -82,6 +82,15 @@ spring: id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式 destination: springCloudBus # 目标消息队列,默认为 springCloudBus +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + --- #################### 芋道相关配置 #################### yudao: @@ -110,6 +119,7 @@ yudao: - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 - /rpc-api/system/tenant/valid # 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验 + - /rpc-api/system/tenant/id-list # 获得租户列表的时候,无需传递租户编号 - /rpc-api/system/error-code/* # 错误码的自动创建与下载的接口,无法带上租户编号 ignore-tables: - system_tenant diff --git a/归档/common/mall-spring-boot-starter-xxl-job/pom.xml b/归档/common/mall-spring-boot-starter-xxl-job/pom.xml deleted file mode 100644 index 50dfe6de8..000000000 --- a/归档/common/mall-spring-boot-starter-xxl-job/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - common - cn.iocoder.mall - 1.0-SNAPSHOT - - 4.0.0 - - mall-spring-boot-starter-xxl-job - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter - true - - - - - com.xuxueli - xxl-job-core - - - - diff --git a/归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobProperties.java b/归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobProperties.java deleted file mode 100644 index 9b777a2cc..000000000 --- a/归档/common/mall-spring-boot-starter-xxl-job/src/main/java/cn/iocoder/mall/xxljob/config/XxlJobProperties.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.iocoder.mall.xxljob.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * XXL-Job 配置类 - */ -@ConfigurationProperties("xxl.job") -public class XxlJobProperties { - - /** - * 是否开启,默认为 true 关闭 - */ - private Boolean enabled = true; - /** - * 访问令牌 - */ - private String accessToken; - /** - * 控制器配置 - */ - private AdminProperties admin; - /** - * 执行器配置 - */ - private ExecutorProperties executor; - - public Boolean getEnabled() { - return enabled; - } - - public void setEnabled(Boolean enabled) { - if (enabled != null) { - this.enabled = enabled; - } - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - if (accessToken != null && accessToken.trim().length() > 0) { - this.accessToken = accessToken; - } - } - - public AdminProperties getAdmin() { - return admin; - } - - public void setAdmin(AdminProperties admin) { - this.admin = admin; - } - - public ExecutorProperties getExecutor() { - return executor; - } - - public void setExecutor(ExecutorProperties executor) { - this.executor = executor; - } - - /** - * XXL-Job 调度器配置类 - */ - public static class AdminProperties { - - /** - * 调度器地址 - */ - private String addresses; - - public String getAddresses() { - return addresses; - } - - public void setAddresses(String addresses) { - this.addresses = addresses; - } - - @Override - public String toString() { - return "AdminProperties{" + - "addresses='" + addresses + '\'' + - '}'; - } - - } - - /** - * XXL-Job 执行器配置类 - */ - public static class ExecutorProperties { - - /** - * 默认端口 - * - * 这里使用 -1 表示随机 - */ - private static final Integer PORT_DEFAULT = -1; - - /** - * 默认日志保留天数 - * - * 默认为 -1,不清理,永久保留 - */ - private static final Integer LOG_RETENTION_DAYS_DEFAULT = -1; - - /** - * 应用名 - */ - private String appName; - /** - * 执行器的 IP - */ - private String ip; - /** - * 执行器的 Port - */ - private Integer port = PORT_DEFAULT; - /** - * 日志地址 - */ - private String logPath; - /** - * 日志保留天数 - */ - private Integer logRetentionDays = LOG_RETENTION_DAYS_DEFAULT; - - public String getAppName() { - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getLogPath() { - return logPath; - } - - public void setLogPath(String logPath) { - this.logPath = logPath; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public Integer getLogRetentionDays() { - return logRetentionDays; - } - - public void setLogRetentionDays(Integer logRetentionDays) { - this.logRetentionDays = logRetentionDays; - } - } - -} diff --git a/归档/common/mall-spring-boot-starter-xxl-job/src/main/resources/META-INF/spring.factories b/归档/common/mall-spring-boot-starter-xxl-job/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 337f8f596..000000000 --- a/归档/common/mall-spring-boot-starter-xxl-job/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration