1. 接入 spring cloud stream,支持多租户

2. 弱化 spring cloud dubbo 集成,可通过加入依赖自动实现
This commit is contained in:
YunaiV
2022-06-22 23:59:19 +08:00
parent 4807547d73
commit 4381d938be
21 changed files with 125 additions and 474 deletions

View File

@@ -8,7 +8,8 @@ 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.mq.TenantRedisMessageInterceptor;
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;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
@@ -23,8 +24,10 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.GlobalChannelInterceptor;
@Configuration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@@ -82,14 +85,19 @@ public class YudaoTenantAutoConfiguration {
// ========== MQ ==========
@Bean
public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
return new TenantRedisMessageInterceptor();
@GlobalChannelInterceptor // 必须添加在方法上,否则无法生效
public TenantChannelInterceptor tenantChannelInterceptor() {
return new TenantChannelInterceptor();
}
@Bean
public FunctionAroundWrapper functionAroundWrapper() {
return new TenantFunctionAroundWrapper();
}
// ========== Job ==========
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.tenant.core.mq;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import java.util.Map;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
/**
* 多租户的 {@link ChannelInterceptor} 实现类
* 发送消息时,设置租户编号到 Header 上
*
* @author 芋道源码
*/
public class TenantChannelInterceptor implements ChannelInterceptor {
@Override
@SuppressWarnings({"unchecked", "NullableProblems"})
public Message<?> preSend(Message<?> message, MessageChannel channel) {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
Map<String, Object> headers = (Map<String, Object>) ReflectUtil.getFieldValue(message.getHeaders(), "headers");
headers.put(HEADER_TENANT_ID, tenantId);
}
return message;
}
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.tenant.core.mq;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry;
import org.springframework.messaging.Message;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
/**
* 多租户 FunctionAroundWrapper 实现类
* 消费消息时,设置租户编号到 Context 上
*
* @author 芋道源码
*/
public class TenantFunctionAroundWrapper extends FunctionAroundWrapper {
@Override
protected Object doApply(Object input, SimpleFunctionRegistry.FunctionInvocationWrapper targetFunction) {
// 如果不是 MQ 消息,则直接跳过
if (!(input instanceof Message)) {
return targetFunction.apply(input);
}
// 如果没有多租户,则直接跳过
Message<?> message = (Message<?>) input;
Long tenantId = MapUtil.getLong(message.getHeaders(), HEADER_TENANT_ID);
if (tenantId == null) {
return targetFunction.apply(input);
}
// 如果有多租户,则使用多租户上下文
return TenantUtils.execute(tenantId, () -> targetFunction.apply(input));
}
}

View File

@@ -1,42 +0,0 @@
package cn.iocoder.yudao.framework.tenant.core.mq;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
/**
* 多租户 {@link AbstractRedisMessage} 拦截器
*
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中
*
* @author 芋道源码
*/
public class TenantRedisMessageInterceptor implements RedisMessageInterceptor {
private static final String HEADER_TENANT_ID = "tenant-id";
@Override
public void sendMessageBefore(AbstractRedisMessage message) {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
message.addHeader(HEADER_TENANT_ID, tenantId.toString());
}
}
@Override
public void consumeMessageBefore(AbstractRedisMessage message) {
String tenantIdStr = message.getHeader(HEADER_TENANT_ID);
if (StrUtil.isNotEmpty(tenantIdStr)) {
TenantContextHolder.setTenantId(Long.valueOf(tenantIdStr));
}
}
@Override
public void consumeMessageAfter(AbstractRedisMessage message) {
// 注意Consumer 是一个逻辑的入口,所以不考虑原本上下文就存在租户编号的情况
TenantContextHolder.clear();
}
}

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.tenant.core.util;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import java.util.function.Supplier;
/**
* 多租户 Util
*
@@ -32,4 +34,27 @@ public class TenantUtils {
}
}
/**
* 使用指定租户,执行对应的逻辑
*
* 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
* 当然,执行完成后,还是会恢复回去
*
* @param tenantId 租户编号
* @param supplier 逻辑
*/
public static <T> T execute(Long tenantId, Supplier<T> supplier) {
Long oldTenantId = TenantContextHolder.getTenantId();
Boolean oldIgnore = TenantContextHolder.isIgnore();
try {
TenantContextHolder.setTenantId(tenantId);
TenantContextHolder.setIgnore(false);
// 执行逻辑
return supplier.get();
} finally {
TenantContextHolder.setTenantId(oldTenantId);
TenantContextHolder.setIgnore(oldIgnore);
}
}
}