完成 xxl-job 的接入
parent
4381d938be
commit
3774afe553
|
@ -34,6 +34,7 @@
|
||||||
<!-- Config 配置中心相关 -->
|
<!-- Config 配置中心相关 -->
|
||||||
<apollo.version>1.9.2</apollo.version>
|
<apollo.version>1.9.2</apollo.version>
|
||||||
<!-- Job 定时任务相关 -->
|
<!-- Job 定时任务相关 -->
|
||||||
|
<xxl-job.version>2.3.1</xxl-job.version>
|
||||||
<!-- 服务保障相关 -->
|
<!-- 服务保障相关 -->
|
||||||
<lock4j.version>2.2.0</lock4j.version>
|
<lock4j.version>2.2.0</lock4j.version>
|
||||||
<resilience4j.version>1.7.1</resilience4j.version>
|
<resilience4j.version>1.7.1</resilience4j.version>
|
||||||
|
@ -251,6 +252,11 @@
|
||||||
<!-- Config 配置中心相关 -->
|
<!-- Config 配置中心相关 -->
|
||||||
|
|
||||||
<!-- Job 定时任务相关 -->
|
<!-- Job 定时任务相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xuxueli</groupId>
|
||||||
|
<artifactId>xxl-job-core</artifactId>
|
||||||
|
<version>${xxl-job.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.cloud</groupId>
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
package cn.iocoder.yudao.framework.tenant.config;
|
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.common.enums.WebFilterOrderEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
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.aop.TenantIgnoreAspect;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
|
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.TenantJobAspect;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
|
|
||||||
import cn.iocoder.yudao.framework.tenant.core.mq.TenantChannelInterceptor;
|
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.mq.TenantFunctionAroundWrapper;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
|
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 cn.iocoder.yudao.module.system.api.tenant.TenantApi;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||||
|
import com.xxl.job.core.executor.XxlJobExecutor;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
@ -103,19 +101,25 @@ public class YudaoTenantAutoConfiguration {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||||
if (!(bean instanceof JobHandler)) {
|
if (!(bean instanceof XxlJobExecutor)) {
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
// 有 TenantJob 注解的情况下,才会进行处理
|
// // 有 TenantJob 注解的情况下,才会进行处理
|
||||||
if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
|
// if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
|
||||||
|
// return bean;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 使用 TenantJobHandlerDecorator 装饰
|
||||||
|
// return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 TenantJobHandlerDecorator 装饰
|
|
||||||
return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
|
||||||
|
return new TenantJobAspect(tenantFrameworkService);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Long> tenantIds = tenantFrameworkService.getTenantIds();
|
||||||
|
if (CollUtil.isEmpty(tenantIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个租户,执行 Job
|
||||||
|
Map<Long, String> 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 extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
|
||||||
|
return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Long> tenantIds = tenantFrameworkService.getTenantIds();
|
|
||||||
if (CollUtil.isEmpty(tenantIds)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 逐个租户,执行 Job
|
|
||||||
Map<Long, String> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -12,10 +12,7 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>任务拓展
|
<description>任务拓展,基于 XXL-Job 实现</description>
|
||||||
1. 定时任务,基于 Quartz 拓展
|
|
||||||
2. 异步任务,基于 Spring Async 拓展
|
|
||||||
</description>
|
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -24,10 +21,22 @@
|
||||||
<artifactId>yudao-common</artifactId>
|
<artifactId>yudao-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Job 定时任务相关 -->
|
<!-- Spring 核心 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Job 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xuxueli</groupId>
|
||||||
|
<artifactId>xxl-job-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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.XxlJobExecutor;
|
||||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
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.annotation.EnableScheduling;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XXL-Job 自动配置类
|
* XXL-Job 自动配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass(XxlJobSpringExecutor.class)
|
@ConditionalOnClass(XxlJobSpringExecutor.class)
|
||||||
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true)
|
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||||
@EnableConfigurationProperties({XxlJobProperties.class})
|
@EnableConfigurationProperties({XxlJobProperties.class})
|
||||||
public class XxlJobAutoConfiguration {
|
@EnableScheduling // 开启 Spring 自带的定时任务
|
||||||
|
@Slf4j
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(XxlJobAutoConfiguration.class);
|
public class YudaoXxlJobAutoConfiguration {
|
||||||
|
|
||||||
private final XxlJobProperties properties;
|
|
||||||
|
|
||||||
public XxlJobAutoConfiguration(XxlJobProperties properties) {
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public XxlJobExecutor xxlJobExecutor() {
|
public XxlJobExecutor xxlJobExecutor(XxlJobProperties properties) {
|
||||||
LOGGER.info("初始化 XXL-Job 执行器的配置");
|
log.info("[xxlJobExecutor][初始化 XXL-Job 执行器的配置]");
|
||||||
|
XxlJobProperties.AdminProperties admin = properties.getAdmin();
|
||||||
// 参数校验
|
XxlJobProperties.ExecutorProperties executor = properties.getExecutor();
|
||||||
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.");
|
|
||||||
|
|
||||||
// 初始化执行器
|
// 初始化执行器
|
||||||
XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor();
|
XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor();
|
||||||
|
@ -49,7 +39,7 @@ public class XxlJobAutoConfiguration {
|
||||||
xxlJobExecutor.setLogPath(executor.getLogPath());
|
xxlJobExecutor.setLogPath(executor.getLogPath());
|
||||||
xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays());
|
xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays());
|
||||||
xxlJobExecutor.setAdminAddresses(admin.getAddresses());
|
xxlJobExecutor.setAdminAddresses(admin.getAddresses());
|
||||||
xxlJobExecutor.setAccessToken(this.properties.getAccessToken());
|
xxlJobExecutor.setAccessToken(properties.getAccessToken());
|
||||||
return xxlJobExecutor;
|
return xxlJobExecutor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, // 每次重试间隔
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
|
@ -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<Date> 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<Date> nextTimes = new ArrayList<>(n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
Date nextTime = cron.getNextValidTimeAfter(now);
|
|
||||||
nextTimes.add(nextTime);
|
|
||||||
// 切换现在,为下一个触发时间;
|
|
||||||
now = nextTime;
|
|
||||||
}
|
|
||||||
return nextTimes;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* 1. 定时任务,采用 Quartz 实现进程内的任务执行。
|
* 1. 定时任务,基于 XXL-Job 实现。
|
||||||
* 考虑到高可用,使用 Quartz 自带的 MySQL 集群方案。
|
|
||||||
*
|
|
||||||
* 2. 异步任务,采用 Spring Async 异步执行。
|
* 2. 异步任务,采用 Spring Async 异步执行。
|
||||||
*/
|
*/
|
||||||
package cn.iocoder.yudao.framework.quartz;
|
package cn.iocoder.yudao.framework.quartz;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
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
|
cn.iocoder.yudao.framework.quartz.config.YudaoAsyncAutoConfiguration
|
||||||
|
|
|
@ -94,11 +94,11 @@
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Job 定时任务相关 TODO 芋艿:暂时去掉 -->
|
<!-- Job 定时任务相关 -->
|
||||||
<!-- <dependency>-->
|
<dependency>
|
||||||
<!-- <groupId>cn.iocoder.cloud</groupId>-->
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
<!-- <artifactId>yudao-spring-boot-starter-job</artifactId>-->
|
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
||||||
<!-- </dependency>-->
|
</dependency>
|
||||||
|
|
||||||
<!-- 消息队列相关 -->
|
<!-- 消息队列相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -72,8 +72,10 @@ spring:
|
||||||
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
||||||
|
|
||||||
--- #################### 定时任务相关配置 ####################
|
--- #################### 定时任务相关配置 ####################
|
||||||
|
xxl:
|
||||||
--- #################### 配置中心相关配置 ####################
|
job:
|
||||||
|
admin:
|
||||||
|
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||||
|
|
||||||
--- #################### 服务保障相关配置 ####################
|
--- #################### 服务保障相关配置 ####################
|
||||||
|
|
||||||
|
|
|
@ -83,8 +83,10 @@ spring:
|
||||||
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
||||||
|
|
||||||
--- #################### 定时任务相关配置 ####################
|
--- #################### 定时任务相关配置 ####################
|
||||||
|
xxl:
|
||||||
--- #################### 配置中心相关配置 ####################
|
job:
|
||||||
|
admin:
|
||||||
|
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||||
|
|
||||||
--- #################### 服务保障相关配置 ####################
|
--- #################### 服务保障相关配置 ####################
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,15 @@ spring:
|
||||||
id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式
|
id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式
|
||||||
destination: springCloudBus # 目标消息队列,默认为 springCloudBus
|
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:
|
yudao:
|
||||||
|
|
|
@ -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("美滋滋");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -72,6 +72,10 @@ spring:
|
||||||
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址
|
||||||
|
|
||||||
--- #################### 定时任务相关配置 ####################
|
--- #################### 定时任务相关配置 ####################
|
||||||
|
xxl:
|
||||||
|
job:
|
||||||
|
admin:
|
||||||
|
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||||
|
|
||||||
--- #################### 服务保障相关配置 ####################
|
--- #################### 服务保障相关配置 ####################
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,11 @@ spring:
|
||||||
|
|
||||||
--- #################### 定时任务相关配置 ####################
|
--- #################### 定时任务相关配置 ####################
|
||||||
|
|
||||||
|
xxl:
|
||||||
|
job:
|
||||||
|
admin:
|
||||||
|
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||||
|
|
||||||
--- #################### 服务保障相关配置 ####################
|
--- #################### 服务保障相关配置 ####################
|
||||||
|
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
|
|
|
@ -82,6 +82,15 @@ spring:
|
||||||
id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式
|
id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式
|
||||||
destination: springCloudBus # 目标消息队列,默认为 springCloudBus
|
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:
|
yudao:
|
||||||
|
@ -110,6 +119,7 @@ yudao:
|
||||||
- /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
|
- /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
|
||||||
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
|
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
|
||||||
- /rpc-api/system/tenant/valid # 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验
|
- /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/* # 错误码的自动创建与下载的接口,无法带上租户编号
|
- /rpc-api/system/error-code/* # 错误码的自动创建与下载的接口,无法带上租户编号
|
||||||
ignore-tables:
|
ignore-tables:
|
||||||
- system_tenant
|
- system_tenant
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>common</artifactId>
|
|
||||||
<groupId>cn.iocoder.mall</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- Spring 核心 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Job 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.xuxueli</groupId>
|
|
||||||
<artifactId>xxl-job-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|
||||||
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration
|
|
Loading…
Reference in New Issue