完成 xxl-job 的接入

pull/4/head
YunaiV 2022-06-25 10:18:04 +08:00
parent 4381d938be
commit 3774afe553
27 changed files with 284 additions and 715 deletions

View File

@ -34,6 +34,7 @@
<!-- Config 配置中心相关 -->
<apollo.version>1.9.2</apollo.version>
<!-- Job 定时任务相关 -->
<xxl-job.version>2.3.1</xxl-job.version>
<!-- 服务保障相关 -->
<lock4j.version>2.2.0</lock4j.version>
<resilience4j.version>1.7.1</resilience4j.version>
@ -251,6 +252,11 @@
<!-- Config 配置中心相关 -->
<!-- Job 定时任务相关 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -12,10 +12,7 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>任务拓展
1. 定时任务,基于 Quartz 拓展
2. 异步任务,基于 Spring Async 拓展
</description>
<description>任务拓展,基于 XXL-Job 实现</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
@ -24,10 +21,22 @@
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<!-- Spring 核心 -->
<dependency>
<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>
<!-- 工具类相关 -->

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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, // 每次重试间隔
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -1,7 +1,5 @@
/**
* 1. Quartz
* 使 Quartz MySQL
*
* 1. XXL-Job
* 2. Spring Async
*/
package cn.iocoder.yudao.framework.quartz;

View File

@ -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

View File

@ -94,11 +94,11 @@
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Job 定时任务相关 TODO 芋艿:暂时去掉 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-spring-boot-starter-job</artifactId>-->
<!-- </dependency>-->
<!-- Job 定时任务相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>

View File

@ -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 # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################

View File

@ -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 # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################

View File

@ -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:

View File

@ -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("美滋滋");
}
}

View File

@ -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 # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################

View File

@ -83,6 +83,11 @@ spring:
--- #################### 定时任务相关配置 ####################
xxl:
job:
admin:
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项

View File

@ -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

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration