1. 接入 spring cloud stream,支持多租户
2. 弱化 spring cloud dubbo 集成,可通过加入依赖自动实现
This commit is contained in:
@@ -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() {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user