1. 增加 Job 的多租户的能力

This commit is contained in:
YunaiV
2021-12-05 10:44:17 +08:00
parent 535d3c9c01
commit 6cd9b3bf7e
19 changed files with 327 additions and 10 deletions

View File

@ -32,6 +32,12 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
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.service.TenantFrameworkService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
/**
* 多租户针对 Job 的自动配置
*
* @author 芋道源码
*/
public class YudaoTenantJobAutoConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof JobHandler)) {
return bean;
}
// 有 TenantJob 注解的情况下,才会进行处理
if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
return bean;
}
// 使用 TenantJobHandlerDecorator 装饰
return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
}
};
}
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 多租户 Job 注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TenantJob {
}

View File

@ -0,0 +1,58 @@
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

@ -0,0 +1,14 @@
package cn.iocoder.yudao.framework.tenant.core.job;
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker;
/**
* 多租户 JobHandlerInvoker 拓展实现类
*
* @author 芋道源码
*/
public class TenantJobHandlerInvoker extends JobHandlerInvoker {
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.tenant.core.service;
import java.util.List;
/**
* Tenant 框架 Service 接口,定义获取租户信息
*
* @author 芋道源码
*/
public interface TenantFrameworkService {
/**
* 获得所有租户
*
* @return 租户编号数组
*/
List<Long> getTenantIds();
}

View File

@ -12,7 +12,10 @@ import java.io.IOException;
/**
* 多租户 Web 过滤器
* 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号
* 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号
*
* Q会不会存在模拟 tenant-id 导致跨租户的问题?
* A用户登陆后获得的 Token 是基于租户级别隔离,从而保证授权失败。
*
* @author 芋道源码
*/

View File

@ -1,8 +1,8 @@
/**
* 多租户,支持如下层面:
* 1. DB基于 MyBatis Plus 多租户的功能实现
* 2. JobTODO
* 3. MQTODO
* 4. WebTODO
* 1. DB基于 MyBatis Plus 多租户的功能实现
* 2. Web请求 HTTP API 时Header 带上 tenant-id 租户编号。
* 3. Job在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
* 4. MQTODO
*/
package cn.iocoder.yudao.framework.tenant;

View File

@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration
cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\
cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration