From 2f2aae08ab934009cd39bbe7ec3823c594fa48f8 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Thu, 17 Jul 2025 20:41:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension/tenant):=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?TenantUtils=20=E6=9B=BF=E6=8D=A2=20TenantHandler=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8F=8A=E5=85=B6=E5=AE=9E=E7=8E=B0=E7=B1=BB=20Defaul?= =?UTF-8?q?tTenantHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tenant/TenantDataSourceHandler.java | 5 + .../ConditionalOnEnabledTenant.java} | 29 ++--- .../TenantWebMvcAutoConfiguration.java | 1 + .../tenant/context/TenantContextHolder.java | 38 +++++-- .../TenantInterceptor.java | 11 +- .../extension/tenant/util/TenantUtils.java | 106 ++++++++++++++++++ .../TenantAutoConfiguration.java | 16 +-- .../tenant/handler/DefaultTenantHandler.java | 76 ------------- .../DefaultTenantDataSourceHandler.java | 5 + 9 files changed, 173 insertions(+), 114 deletions(-) rename continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/{TenantHandler.java => annotation/ConditionalOnEnabledTenant.java} (52%) rename continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/{autoconfigure => interceptor}/TenantInterceptor.java (86%) create mode 100644 continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/util/TenantUtils.java delete mode 100644 continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/DefaultTenantHandler.java diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantDataSourceHandler.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantDataSourceHandler.java index 96916ea7..4fb9dac9 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantDataSourceHandler.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantDataSourceHandler.java @@ -57,4 +57,9 @@ public interface TenantDataSourceHandler { * @param dataSourceName 数据源名称 */ void removeDataSource(String dataSourceName); + + /** + * 轮询数据源 + */ + void poll(); } diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantHandler.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/annotation/ConditionalOnEnabledTenant.java similarity index 52% rename from continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantHandler.java rename to continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/annotation/ConditionalOnEnabledTenant.java index a05ccef7..a135fc48 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/TenantHandler.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/annotation/ConditionalOnEnabledTenant.java @@ -14,21 +14,22 @@ * limitations under the License. */ -package top.continew.starter.extension.tenant; +package top.continew.starter.extension.tenant.annotation; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import top.continew.starter.core.constant.PropertiesConstants; + +import java.lang.annotation.*; /** - * 租户处理器 + * 是否启用租户判断注解 * - * @author 小熊 - * @since 2.8.0 + * @author Charles7c + * @since 2.13.1 */ -public interface TenantHandler { - - /** - * 在指定租户中执行 - * - * @param tenantId 租户 ID - * @param runnable 方法 - */ - void execute(Long tenantId, Runnable runnable); -} +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true) +public @interface ConditionalOnEnabledTenant { +} \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantWebMvcAutoConfiguration.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantWebMvcAutoConfiguration.java index 08449bcf..568c695f 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantWebMvcAutoConfiguration.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantWebMvcAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.extension.tenant.config.TenantProvider; +import top.continew.starter.extension.tenant.interceptor.TenantInterceptor; /** * 租户 Web MVC 自动配置 diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/context/TenantContextHolder.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/context/TenantContextHolder.java index ec4018cc..39481891 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/context/TenantContextHolder.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/context/TenantContextHolder.java @@ -18,6 +18,7 @@ package top.continew.starter.extension.tenant.context; import cn.hutool.extra.spring.SpringUtil; import com.alibaba.ttl.TransmittableThreadLocal; +import top.continew.starter.core.util.SpringUtils; import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; import top.continew.starter.extension.tenant.config.TenantDataSource; import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; @@ -35,12 +36,12 @@ public class TenantContextHolder { /** * 租户上下文 */ - private static final TransmittableThreadLocal CONTEXT = new TransmittableThreadLocal<>(); + private static final TransmittableThreadLocal CONTEXT_HOLDER = new TransmittableThreadLocal<>(); /** * 是否忽略租户 */ - private static final TransmittableThreadLocal IGNORE = new TransmittableThreadLocal<>(); + private static final TransmittableThreadLocal IGNORE_HOLDER = new TransmittableThreadLocal<>(); private TenantContextHolder() { } @@ -51,7 +52,7 @@ public class TenantContextHolder { * @param context 上下文 */ public static void setContext(TenantContext context) { - CONTEXT.set(context); + CONTEXT_HOLDER.set(context); } /** @@ -60,7 +61,7 @@ public class TenantContextHolder { * @return 上下文 */ public static TenantContext getContext() { - return CONTEXT.get(); + return CONTEXT_HOLDER.get(); } /** @@ -69,7 +70,7 @@ public class TenantContextHolder { * @param ignore 是否忽略租户 */ public static void setIgnore(boolean ignore) { - IGNORE.set(ignore); + IGNORE_HOLDER.set(ignore); } /** @@ -78,15 +79,15 @@ public class TenantContextHolder { * @return 是否忽略租户 */ public static boolean isIgnore() { - return Boolean.TRUE.equals(IGNORE.get()); + return Boolean.TRUE.equals(IGNORE_HOLDER.get()); } /** * 清除 */ - public static void clearContext() { - CONTEXT.remove(); - IGNORE.remove(); + public static void clear() { + CONTEXT_HOLDER.remove(); + IGNORE_HOLDER.remove(); } /** @@ -117,4 +118,23 @@ public class TenantContextHolder { public static TenantDataSource getDataSource() { return Optional.ofNullable(getContext()).map(TenantContext::getDataSource).orElse(null); } + + /** + * 是否启用了租户 + * + * @return 是否启用了租户 + */ + public static boolean isTenantEnabled() { + TenantProperties tenantProperties = SpringUtils.getBean(TenantProperties.class, true); + return tenantProperties != null && tenantProperties.isEnabled(); + } + + /** + * 是否禁用了租户 + * + * @return 是否禁用了租户 + */ + public static boolean isTenantDisabled() { + return !isTenantEnabled(); + } } diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantInterceptor.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/interceptor/TenantInterceptor.java similarity index 86% rename from continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantInterceptor.java rename to continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/interceptor/TenantInterceptor.java index 0c26ae5f..51377f75 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantInterceptor.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/interceptor/TenantInterceptor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package top.continew.starter.extension.tenant.autoconfigure; +package top.continew.starter.extension.tenant.interceptor; import cn.hutool.core.annotation.AnnotationUtil; import jakarta.servlet.http.HttpServletRequest; @@ -23,6 +23,7 @@ import org.springframework.core.Ordered; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import top.continew.starter.extension.tenant.annotation.TenantIgnore; +import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; import top.continew.starter.extension.tenant.config.TenantProvider; import top.continew.starter.extension.tenant.context.TenantContextHolder; @@ -44,6 +45,7 @@ public class TenantInterceptor implements HandlerInterceptor, Ordered { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + // 忽略租户拦截 if (handler instanceof HandlerMethod handlerMethod) { TenantIgnore methodAnnotation = handlerMethod.getMethodAnnotation(TenantIgnore.class); if (methodAnnotation != null) { @@ -55,11 +57,18 @@ public class TenantInterceptor implements HandlerInterceptor, Ordered { return true; } } + // 设置上下文 String tenantId = request.getHeader(tenantProperties.getTenantIdHeader()); TenantContextHolder.setContext(tenantProvider.getByTenantId(tenantId, true)); return true; } + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { + // 清除上下文 + TenantContextHolder.clear(); + } + @Override public int getOrder() { return Integer.MIN_VALUE; diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/util/TenantUtils.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/util/TenantUtils.java new file mode 100644 index 00000000..dff1a186 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-core/src/main/java/top/continew/starter/extension/tenant/util/TenantUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.extension.tenant.util; + +import cn.hutool.extra.spring.SpringUtil; +import top.continew.starter.extension.tenant.TenantDataSourceHandler; +import top.continew.starter.extension.tenant.config.TenantProvider; +import top.continew.starter.extension.tenant.context.TenantContext; +import top.continew.starter.extension.tenant.context.TenantContextHolder; +import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; + +/** + * 租户工具类 + * + * @author 小熊 + * @author Charles7c + * @since 2.13.1 + */ +public class TenantUtils { + + private TenantUtils() { + } + + /** + * 使用指定租户执行业务逻辑 + * + *

+ * 强制设置 {@code TenantContextHolder.setIgnore(false)},执行完恢复原值。
+ * 适用于在非租户逻辑中执行有租户逻辑的业务逻辑。 + *

+ * + * @param tenantId 租户 ID + * @param runnable 业务逻辑 + */ + public static void execute(Long tenantId, Runnable runnable) { + // 未启用租户,直接执行业务逻辑 + if (TenantContextHolder.isTenantDisabled()) { + runnable.run(); + return; + } + // 原租户上下文 + TenantContext oldContext = TenantContextHolder.getContext(); + boolean oldIgnore = TenantContextHolder.isIgnore(); + boolean isPush = false; + try { + TenantContext newContext = SpringUtil.getBean(TenantProvider.class) + .getByTenantId(tenantId.toString(), false); + // 设置新租户上下文 + TenantContextHolder.setContext(newContext); + TenantContextHolder.setIgnore(false); + // 数据源级隔离:切换数据源 + if (TenantIsolationLevel.DATASOURCE.equals(newContext.getIsolationLevel())) { + SpringUtil.getBean(TenantDataSourceHandler.class).changeDataSource(newContext.getDataSource()); + isPush = true; + } + // 执行业务逻辑 + runnable.run(); + } finally { + // 恢复原租户上下文 + TenantContextHolder.setContext(oldContext); + TenantContextHolder.setIgnore(oldIgnore); + if (isPush) { + SpringUtil.getBean(TenantDataSourceHandler.class).poll(); + } + } + } + + /** + * 忽略租户执行业务逻辑 + * + *

+ * 适用于在租户逻辑中执行非租户逻辑的业务逻辑。 + *

+ * + * @param runnable 业务逻辑 + */ + public void executeIgnore(Runnable runnable) { + // 未启用租户,直接执行业务逻辑 + if (TenantContextHolder.isTenantDisabled()) { + runnable.run(); + return; + } + boolean oldIgnore = TenantContextHolder.isIgnore(); + try { + TenantContextHolder.setIgnore(true); + // 执行业务逻辑 + runnable.run(); + } finally { + TenantContextHolder.setIgnore(oldIgnore); + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantAutoConfiguration.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantAutoConfiguration.java index a4af40e5..98533996 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantAutoConfiguration.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/autoconfigure/TenantAutoConfiguration.java @@ -25,16 +25,13 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.AutoConfiguration; 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.core.ResolvableType; -import top.continew.starter.core.constant.PropertiesConstants; +import top.continew.starter.extension.tenant.annotation.ConditionalOnEnabledTenant; import top.continew.starter.extension.tenant.aop.TenantIgnoreAspect; import top.continew.starter.extension.tenant.config.TenantProvider; -import top.continew.starter.extension.tenant.handler.DefaultTenantHandler; import top.continew.starter.extension.tenant.TenantDataSourceHandler; -import top.continew.starter.extension.tenant.TenantHandler; import top.continew.starter.extension.tenant.handler.datasource.DefaultTenantDataSourceHandler; import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceAdvisor; import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor; @@ -49,8 +46,8 @@ import javax.sql.DataSource; * @since 2.7.0 */ @AutoConfiguration +@ConditionalOnEnabledTenant @EnableConfigurationProperties(TenantProperties.class) -@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true) public class TenantAutoConfiguration { private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class); @@ -130,15 +127,6 @@ public class TenantAutoConfiguration { throw new NoSuchBeanDefinitionException(TenantProvider.class); } - /** - * 租户处理器 - */ - @Bean - @ConditionalOnMissingBean - public TenantHandler tenantHandler(TenantProvider tenantProvider) { - return new DefaultTenantHandler(tenantProperties, tenantProvider); - } - @PostConstruct public void postConstruct() { log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization."); diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/DefaultTenantHandler.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/DefaultTenantHandler.java deleted file mode 100644 index 1ae8ab05..00000000 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/DefaultTenantHandler.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - *

- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.gnu.org/licenses/lgpl.html - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.starter.extension.tenant.handler; - -import cn.hutool.extra.spring.SpringUtil; -import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; -import top.continew.starter.extension.tenant.TenantDataSourceHandler; -import top.continew.starter.extension.tenant.TenantHandler; -import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; -import top.continew.starter.extension.tenant.config.TenantProvider; -import top.continew.starter.extension.tenant.context.TenantContext; -import top.continew.starter.extension.tenant.context.TenantContextHolder; -import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; - -/** - * 租户处理器 - * - * @author 小熊 - * @since 2.8.0 - */ -public class DefaultTenantHandler implements TenantHandler { - - private final TenantProperties tenantProperties; - private final TenantProvider tenantProvider; - - public DefaultTenantHandler(TenantProperties tenantProperties, TenantProvider tenantProvider) { - this.tenantProperties = tenantProperties; - this.tenantProvider = tenantProvider; - } - - @Override - public void execute(Long tenantId, Runnable runnable) { - if (!tenantProperties.isEnabled()) { - return; - } - TenantContext tenantContext = tenantProvider.getByTenantId(tenantId.toString(), false); - // 保存当前的租户上下文 - TenantContext originalContext = TenantContextHolder.getContext(); - boolean isPush = false; - try { - // 设置新的租户上下文 - TenantContextHolder.setContext(tenantContext); - // 切换数据源 - if (TenantIsolationLevel.DATASOURCE.equals(tenantContext.getIsolationLevel())) { - SpringUtil.getBean(TenantDataSourceHandler.class).changeDataSource(tenantContext.getDataSource()); - isPush = true; - } - // 执行业务逻辑 - runnable.run(); - } finally { - // 恢复原始的租户上下文 - if (originalContext != null) { - TenantContextHolder.setContext(originalContext); - } else { - TenantContextHolder.clearContext(); - } - if (isPush) { - DynamicDataSourceContextHolder.poll(); - } - } - } -} diff --git a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/datasource/DefaultTenantDataSourceHandler.java b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/datasource/DefaultTenantDataSourceHandler.java index 6ddef7d7..551ab577 100644 --- a/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/datasource/DefaultTenantDataSourceHandler.java +++ b/continew-starter-extension/continew-starter-extension-tenant/continew-starter-extension-tenant-mp/src/main/java/top/continew/starter/extension/tenant/handler/datasource/DefaultTenantDataSourceHandler.java @@ -82,4 +82,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler { public void removeDataSource(String dataSourceName) { dynamicRoutingDataSource.removeDataSource(dataSourceName); } + + @Override + public void poll() { + DynamicDataSourceContextHolder.poll(); + } } \ No newline at end of file