feat(extension/tenant): 新增 continew-starter-extension-tenant 多租户模块(暂时仅支持行级隔离)

This commit is contained in:
2024-09-11 23:42:30 +08:00
parent 0c334dadcc
commit 1a97a1b709
12 changed files with 411 additions and 2 deletions

View File

@@ -119,6 +119,11 @@ public class PropertiesConstants {
*/
public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket";
/**
* 多租户配置
*/
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
private PropertiesConstants() {
}
}

View File

@@ -346,14 +346,12 @@
<artifactId>continew-starter-extension-crud-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - CRUD - MyBatis Flex ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
@@ -361,6 +359,19 @@
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<version>${revision}</version>
</dependency>
<!-- 认证模块 - JustAuth -->
<dependency>
<groupId>top.continew</groupId>

View File

@@ -0,0 +1,22 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-tenant-core</artifactId>
<description>ContiNew Starter 扩展模块 - 多租户 - 核心模块</description>
<dependencies>
<!-- TTL线程间传递 ThreadLocal异步执行时上下文传递的解决方案 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import java.util.List;
/**
* 租户配置属性
*
* @author Charles7c
* @since 2.7.0
*/
@ConfigurationProperties(PropertiesConstants.TENANT)
public class TenantProperties {
/**
* 是否启用多租户
*/
private boolean enabled = true;
/**
* 租户隔离级别
*/
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
/**
* 租户 ID 列名
*/
private String tenantIdColumn = "tenant_id";
/**
* 超级租户 ID
*/
private Long superTenantId = 1L;
/**
* 忽略表(忽略拼接多租户条件)
*/
private List<String> ignoreTables;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public TenantIsolationLevel getIsolationLevel() {
return isolationLevel;
}
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
this.isolationLevel = isolationLevel;
}
public String getTenantIdColumn() {
return tenantIdColumn;
}
public void setTenantIdColumn(String tenantIdColumn) {
this.tenantIdColumn = tenantIdColumn;
}
public Long getSuperTenantId() {
return superTenantId;
}
public void setSuperTenantId(Long superTenantId) {
this.superTenantId = superTenantId;
}
public List<String> getIgnoreTables() {
return ignoreTables;
}
public void setIgnoreTables(List<String> ignoreTables) {
this.ignoreTables = ignoreTables;
}
}

View File

@@ -0,0 +1,23 @@
package top.continew.starter.extension.tenant.context;
/**
* 租户上下文
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantContext {
/**
* 租户 ID
*/
private Long tenantId;
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
}

View File

@@ -0,0 +1,53 @@
package top.continew.starter.extension.tenant.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.Optional;
/**
* 租户上下文 Holder
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantContextHolder {
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
private TenantContextHolder() {
}
/**
* 设置上下文
*
* @param context 上下文
*/
public static void setContext(TenantContext context) {
CONTEXT_HOLDER.set(context);
}
/**
* 获取上下文
*
* @return 上下文
*/
public static TenantContext getContext() {
return CONTEXT_HOLDER.get();
}
/**
* 清除上下文
*/
public static void clearContext() {
CONTEXT_HOLDER.remove();
}
/**
* 获取租户 ID
*
* @return 租户 ID
*/
public static Long getTenantId() {
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
}
}

View File

@@ -0,0 +1,20 @@
package top.continew.starter.extension.tenant.enums;
/**
* 租户隔离级别
*
* @author Charles7c
* @since 2.7.0
*/
public enum TenantIsolationLevel {
/**
* 行级
*/
LINE,
/**
* 数据源级
*/
DATASOURCE
}

View File

@@ -0,0 +1,35 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
<description>ContiNew Starter 扩展模块 - 多租户 - MyBatis Plus ORM 模块</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-tenant-core</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.autoconfigure;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
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 top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.handler.TenantLineHandlerImpl;
/**
* 多租户自动配置
*
* @author Charles7c
* @since 2.7.0
*/
@AutoConfiguration
@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);
private TenantAutoConfiguration() {
}
/**
* 租户隔离级别:行级
*/
@AutoConfiguration
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "line", matchIfMissing = true)
public static class Line {
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-Line' completed initialization.");
}
/**
* 租户行级隔离拦截器
*/
@Bean
@ConditionalOnMissingBean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
return new TenantLineInnerInterceptor(tenantLineHandler);
}
/**
* 租户行级隔离处理器
*/
@Bean
@ConditionalOnMissingBean
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
return new TenantLineHandlerImpl(properties);
}
}
}

View File

@@ -0,0 +1,46 @@
package top.continew.starter.extension.tenant.handler;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
/**
* 租户行级隔离处理器
*
* @author Charles7c
* @since 2.7.0
*/
public class TenantLineHandlerImpl implements TenantLineHandler {
private final TenantProperties tenantProperties;
public TenantLineHandlerImpl(TenantProperties tenantProperties) {
this.tenantProperties = tenantProperties;
}
@Override
public Expression getTenantId() {
Long tenantId = TenantContextHolder.getTenantId();
if (null != tenantId) {
return new LongValue(tenantId);
}
return null;
}
@Override
public String getTenantIdColumn() {
return tenantProperties.getTenantIdColumn();
}
@Override
public boolean ignoreTable(String tableName) {
Long tenantId = TenantContextHolder.getTenantId();
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
return true;
}
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
}
}

View File

@@ -0,0 +1,20 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-extension-tenant</artifactId>
<packaging>pom</packaging>
<description>ContiNew Starter 扩展模块 - 多租户</description>
<modules>
<module>continew-starter-extension-tenant-core</module>
<module>continew-starter-extension-tenant-mp</module>
</modules>
</project>