1. 增加 yudao-spring-boot-starter-tenant 租户的组件

2. 改造 UserDO,接入多租户
This commit is contained in:
YunaiV
2021-12-04 21:09:49 +08:00
parent ccb56b3b99
commit 7c8fe2fc50
35 changed files with 426 additions and 15 deletions

View File

@ -32,6 +32,7 @@
<module>yudao-spring-boot-starter-biz-pay</module>
<module>yudao-spring-boot-starter-biz-weixin</module>
<module>yudao-spring-boot-starter-extension</module>
<module>yudao-spring-boot-starter-tenant</module>
</modules>
<artifactId>yudao-framework</artifactId>

View File

@ -122,6 +122,17 @@
<artifactId>jakarta.validation-api</artifactId>
<scope>provided</scope> <!-- 设置为 provided主要是 PageParam 使用到 -->
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -17,9 +17,11 @@ public interface WebFilterOrderEnum {
// OrderedRequestContextFilter 默认为 -105用于国际化上下文等等
int API_ACCESS_LOG_FILTER = -104; // 需要保证在 RequestBodyCacheFilter
int TENANT_FILTER = - 100; // 需要保证在 ApiAccessLogFilter
int XSS_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
int API_ACCESS_LOG_FILTER = -90; // 需要保证在 RequestBodyCacheFilter 后面
int XSS_FILTER = -80; // 需要保证在 RequestBodyCacheFilter 后面
// Spring Security Filter 默认为 -100可见 SecurityProperties 配置属性类

View File

@ -10,9 +10,11 @@ import java.util.Date;
/**
* 基础实体对象
*
* @author 芋道源码
*/
@Data
public class BaseDO implements Serializable {
public abstract class BaseDO implements Serializable {
/**
* 创建时间

View File

@ -4,9 +4,13 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.SortingField;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
@ -30,4 +34,17 @@ public class MyBatisUtils {
return page;
}
/**
* 将拦截器添加到链中
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
*
* @param interceptor 链
* @param inner 拦截器
*/
public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner) {
List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
inners.add(0, inner);
interceptor.setInterceptors(inners);
}
}

View File

@ -0,0 +1,37 @@
<?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">
<parent>
<artifactId>yudao-framework</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-tenant</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<description>多租户</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.framework.tenant.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Set;
/**
* 多租户配置
*
* @author 芋道源码
*/
@ConfigurationProperties(prefix = "yudao.tenant")
@Data
public class TenantProperties {
/**
* 需要多租户的表
*
* 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。
* 如果需要,你可以改成 ignoreTables 来取消部分不需要的表
*/
private Set<String> tables;
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* 多租户针对 DB 的自动配置
*
* @author 芋道源码
*/
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantDatabaseAutoConfiguration {
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties) {
return new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));
}
@Bean
public BeanPostProcessor mybatisPlusInterceptorBeanPostProcessor(TenantLineInnerInterceptor tenantLineInnerInterceptor) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof MybatisPlusInterceptor)) {
return bean;
}
// 将 TenantDatabaseInterceptor 添加到最前面
MybatisPlusInterceptor interceptor = (MybatisPlusInterceptor) bean;
MyBatisUtils.addInterceptor(interceptor, tenantLineInnerInterceptor);
return bean;
}
};
}
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.tenant.core.web.TenantWebFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* 多租户针对 Web 的自动配置
*
* @author 芋道源码
*/
public class YudaoTenantWebAutoConfiguration {
@Bean
public FilterRegistrationBean<TenantWebFilter> tenantWebFilter() {
FilterRegistrationBean<TenantWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TenantWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.TENANT_FILTER);
return registrationBean;
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.framework.tenant.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* 多租户上下文 Holder
*
* @author 芋道源码
*/
public class TenantContextHolder {
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
public static Long getTenantId() {
return TENANT_ID.get();
}
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static void clear() {
TENANT_ID.remove();
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.framework.tenant.core.db;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 拓展多租户的 BaseDO 基类
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
public abstract class TenantBaseDO extends BaseDO {
/**
* 多租户编号
*/
private Long tenantId;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.framework.tenant.core.db;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
/**
* 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
*
* @author 芋道源码
*/
@AllArgsConstructor
public class TenantDatabaseInterceptor implements TenantLineHandler {
private final TenantProperties properties;
@Override
public Expression getTenantId() {
// TODO 芋艿:暂时不考虑获取不到的情况。此时,会存在 NPE 的报错
return new StringValue(TenantContextHolder.getTenantId().toString());
}
@Override
public boolean ignoreTable(String tableName) {
// 不包含,说明要过滤
return !CollUtil.contains(properties.getTables(), tableName);
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.framework.tenant.core.web;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 多租户 Web 过滤器
* 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号
*
* @author 芋道源码
*/
public class TenantWebFilter extends OncePerRequestFilter {
private static final String HEADER_TENANT_ID = "tenant-id";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 设置
String tenantId = request.getHeader(HEADER_TENANT_ID);
if (StrUtil.isNotEmpty(tenantId)) {
TenantContextHolder.setTenantId(Long.valueOf(tenantId));
}
try {
chain.doFilter(request, response);
} finally {
// 清理
TenantContextHolder.clear();
}
}
}

View File

@ -0,0 +1,8 @@
/**
* 多租户,支持如下层面:
* 1. DB基于 MyBatis Plus 多租户的功能实现
* 2. JobTODO
* 3. MQTODO
* 4. WebTODO
*/
package cn.iocoder.yudao.framework.tenant;

View File

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