SpringCloud微服务实战——搭建企业级开发框架(二十二):基于MybatisPlus插件TenantLineInnerInterceptor实现多租户功能
多租户技术的基本概念:
多租户技术(英语:multi-tenancy technology)或称多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。
在云计算的加持之下,多租户技术被广为运用于开发云各式服务,不论是IaaS,PaaS还是SaaS,都可以看到多租户技术的影子。
前面介绍过GitEgg框架与数据库交互使用了Mybatis增强工具Mybatis-Plus,Mybatis-Plus提供了TenantLineInnerInterceptor租户处理器来实现多租户功能,其原理就是Mybatis-Plus实现了自定义Mybatis拦截器(Interceptor),在需要执行的sql后面自动添加租户的查询条件,实际和分页插件,数据权限拦截器是同样的实现方式。
简而言之多租户技术就是可以让一套系统通过配置给不同的客户提供服务,每个客户看到的数据都是属于自己的,就好像每个客户都拥有自己一套独立完善的系统。
下面是在GitEgg系统的应用配置:
1、在gitegg-platform-mybatis工程下新建多租户组件配置文件TenantProperties.java和TenantConfig.java,TenantProperties.java用于系统读取配置文件,这里会在Nacos配置中心设置多组户的具体配置信息,TenantConfig.java是插件需要读取的配置有三个配置项:
TenantId租户ID、TenantIdColumn多租户的字段名、ignoreTable不需要多租户隔离的表。
TenantProperties.java:
package com.gitegg.platform.mybatis.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 白名单配置
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
/**
* 是否开启租户模式
*/
private Boolean enable;
/**
* 多租户字段名称
*/
private String column;
/**
* 需要排除的多租户的表
*/
private List<string> exclusionTable;
}
TenantConfig.java:
package com.gitegg.platform.mybatis.config;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.gitegg.platform.boot.util.GitEggAuthUtils;
import com.gitegg.platform.mybatis.props.TenantProperties;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户配置中心
*
* @author GitEgg
*/
@Configuration
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@AutoConfigureBefore(MybatisPlusConfig.class)
public class TenantConfig {
private final TenantProperties tenantProperties;
/**
* 新多租户插件配置,一缓和二缓遵循mybatis的规则,
* 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免缓存万一出现问题
*
* @return TenantLineInnerInterceptor
*/
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor() {
return new TenantLineInnerInterceptor(new TenantLineHandler() {
/**
* 获取租户ID
* @return Expression
*/
@Override
public Expression getTenantId() {
String tenant = GitEggAuthUtils.getTenantId();
if (tenant != null) {
return new StringValue(GitEggAuthUtils.getTenantId());
}
return new NullValue();
}
/**
* 获取多租户的字段名
* @return String
*/
@Override
public String getTenantIdColumn() {
return tenantProperties.getColumn();
}
/**
* 过滤不需要根据租户隔离的表
* 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
* @param tableName 表名
*/
@Override
public boolean ignoreTable(String tableName) {
return tenantProperties.getExclusionTable().stream().anyMatch(
(t) -> t.equalsIgnoreCase(tableName)
);
}
});
}
}
2、可在工程下新建application.yml,配置将来需要在Nacos上配置的信息:
tenant:
# 是否开启租户模式
enable: true
# 需要排除的多租户的表
exclusionTable:
- "t_sys_district"
- "oauth_client_details"
# 租户字段名称
column: tenant_id
3、修改MybatisPlusConfig.java,把多租户过滤器加载进来使其生效:
package com.gitegg.platform.mybatis.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.gitegg.platform.mybatis.props.TenantProperties;
import lombok.RequiredArgsConstructor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@MapperScan("com.gitegg.**.mapper.**")
public class MybatisPlusConfig {
private final TenantLineInnerInterceptor tenantLineInnerInterceptor;
private final TenantProperties tenantProperties;
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//多租户插件
if (tenantProperties.getEnable()) {
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
}
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//防止全表更新与删除插件: BlockAttackInnerInterceptor
BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
interceptor.addInnerInterceptor(blockAttackInnerInterceptor);
return interceptor;
}
/**
* 乐观锁插件 当要更新一条记录的时候,希望这条记录没有被别人更新
* https://mybatis.plus/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
*/
@Bean
public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
}
4、在GitEggAuthUtils方法中新增获取租户信息的公共方法,租户信息在Gateway进行转发时进行设置,后面会说明如何讲租户信息设置到Header中:
package com.gitegg.platform.boot.util;
import cn.hutool.json.JSONUtil;
import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.platform.base.domain.GitEggUser;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class GitEggAuthUtils {
/**
* 获取用户信息
*
* @return GitEggUser
*/
public static GitEggUser getCurrentUser() {
HttpServletRequest request = GitEggWebUtils.getRequest();
if (request == null) {
return null;
}
try {
String user = request.getHeader(AuthConstant.HEADER_USER);
if (StringUtils.isEmpty(user))
{
return null;
}
String userStr = URLDecoder.decode(user,"UTF-8");
GitEggUser gitEggUser = JSONUtil.toBean(userStr, GitEggUser.class);
return gitEggUser;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取租户Id
*
* @return tenantId
*/
public static String getTenantId() {
HttpServletRequest request = GitEggWebUtils.getRequest();
if (request == null) {
return null;
}
try {
String tenantId = request.getHeader(AuthConstant.TENANT_ID);
String user = request.getHeader(AuthConstant.HEADER_USER);
//如果请求头中的tenantId为空,那么尝试是否能够从登陆用户中去获取租户id
if (StringUtils.isEmpty(tenantId) && !StringUtils.isEmpty(user))
{
String userStr = URLDecoder.decode(user,"UTF-8");
GitEggUser gitEggUser = JSONUtil.toBean(userStr, GitEggUser.class);
if (null != gitEggUser)
{
tenantId = gitEggUser.getTenantId();
}
}
return tenantId;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
}
5、GitEgg-Cloud工程中gitegg-gateway子工程的AuthGlobalFilter增加设置TenantId的过滤方法
String tenantId = exchange.getRequest().getHeaders().getFirst(AuthConstant.TENANT_ID);
String token = exchange.getRequest().getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
if (StrUtil.isEmpty(tenantId) && StrUtil.isEmpty(token)) {
return chain.filter(exchange);
}
Map<string, string=""> addHeaders = new HashMap<>();
// 如果系统配置已开启租户模式,设置tenantId
if (enable && StrUtil.isEmpty(tenantId)) {
addHeaders.put(AuthConstant.TENANT_ID, tenantId);
}
6、以上为后台的多租户功能集成步骤,在实际项目开发过程中,我们需要考虑到前端页面在租户信息上的配置,实现思路,不用的租户拥有不同的域名,前端页面根据当前域名获取到对应的租户信息,并在公共请求方法设置TenantId参数,保证每次请求能够携带租户信息。
// request interceptor
request.interceptors.request.use(config => {
const token = storage.get(ACCESS_TOKEN)
// 如果 token 存在
// 让每个请求携带自定义 token 请根据实际情况自行修改
if (token) {
config.headers['Authorization'] = token
}
config.headers['TenantId'] = process.env.VUE_APP_TENANT_ID
return config
}, errorHandler)
源码地址:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
SpringCloud微服务实战——搭建企业级开发框架(二十二):基于MybatisPlus插件TenantLineInnerInterceptor实现多租户功能的更多相关文章
- SpringCloud微服务实战——搭建企业级开发框架(十二):OpenFeign+Ribbon实现负载均衡
Ribbon是Netflix下的负载均衡项目,它主要实现中间层应用程序的负载均衡.为Ribbon配置服务提供者地址列表后,Ribbon就会基于某种负载均衡算法,自动帮助服务调用者去请求.Ribbo ...
- SpringCloud微服务实战——搭建企业级开发框架(十):使用Nacos分布式配置中心
随着业务的发展.微服务架构的升级,服务的数量.程序的配置日益增多(各种微服务.各种服务器地址.各种参数),传统的配置文件方式和数据库的方式已无法满足开发人员对配置管理的要求: 安全性:配置跟随源代码保 ...
- SpringCloud微服务实战——搭建企业级开发框架(十四):集成Sentinel高可用流量管理框架【限流】
Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流.流量整形.熔断降级.系统负载保护.热点防护等多个维度来帮助开发者保障微服务的稳定性. Sentinel 具有 ...
- SpringCloud微服务实战——搭建企业级开发框架(十五):集成Sentinel高可用流量管理框架【熔断降级】
Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一.由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积.Sentinel ...
- SpringCloud微服务实战——搭建企业级开发框架(十六):集成Sentinel高可用流量管理框架【自定义返回消息】
Sentinel限流之后,默认的响应消息为Blocked by Sentinel (flow limiting),对于系统整体功能提示来说并不统一,参考我们前面设置的统一响应及异常处理方式,返回相同的 ...
- SpringCloud微服务实战——搭建企业级开发框架(十九):Gateway使用knife4j聚合微服务文档
本章介绍Spring Cloud Gateway网关如何集成knife4j,通过网关聚合所有的Swagger微服务文档 1.gitegg-gateway中引入knife4j依赖,如果没有后端代码编 ...
- SpringCloud微服务实战——搭建企业级开发框架(四十六):【移动开发】整合uni-app搭建移动端快速开发框架-环境搭建
近年来uni-app发展势头迅猛,只要会vue.js,就可以开发一套代码,发布移动应用到iOS.Android.Web(响应式).以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/ ...
- SpringCloud微服务实战——搭建企业级开发框架(四十一):扩展JustAuth+SpringSecurity+Vue实现多租户系统微信扫码、钉钉扫码等第三方登录
前面我们详细介绍了SSO.OAuth2的定义和实现原理,也举例说明了如何在微服务框架中使用spring-security-oauth2实现单点登录授权服务器和单点登录客户端.目前很多平台都提供了单 ...
- SpringCloud微服务实战——搭建企业级开发框架(四十三):多租户可配置的电子邮件发送系统设计与实现
在日常生活中,邮件已经被聊天软件.短信等更便捷的信息传送方式代替.但在日常工作中,我们的重要的信息通知等非常有必要去归档追溯,那么邮件就是不可或缺的信息传送渠道.对于我们工作中经常用到的系统,里面 ...
随机推荐
- 题解 [SBCOI2020] 一直在你身旁
题目传送门 题目大意 给出一个长度为 \(n\) 的单调不减的序列,每次可以选出一个点,产生贡献 \(a_k\),我们可以得知我们需要找到的数是否大于 \(k\).问找到要找到的数最小花费. \(n\ ...
- HAOI2012高速公路bzoj2752 (线段树,数学)
题目大意: 给定一个长度为n的链,一共m次操作 对于每次操作 \(C\ l\ r\ x\)表示将第l个点到第r个点之间的所有道路的权值增加v \(Q\ l\ r\)在第l个到第r个点里等概率随机取出两 ...
- 【HMS Core 6.0全球上线】华为钥匙环服务,打造跨应用跨形态无缝登录体验
华为钥匙环服务(Keyring),是HMS Core在安全领域开放的全新服务,为全球开发者提供用户认证凭据(以下简称"凭据")本地存储和跨应用.跨形态共享能力,帮助您在安卓应用.快 ...
- SingnalR 从开发到生产部署闭坑指南
前天倒腾了一份[SignalR在react/go技术栈的实践], 步骤和思路大部分是外围框架的应用, 今天趁热打铁, 给一个我总结的SignalR避坑指南. 1.SignalR 默认协商 不管是.NE ...
- 【数据结构与算法Python版学习笔记】图——最短路径问题、最小生成树
最短路径问题 概念 可以通过"traceroute"命令来跟踪信息传送的路径: traceroute www.lib.pku.edu.cn 可以将互联网路由器体系表示为一个带权边的 ...
- 性能利器 Takin 来了!首个生产环境全链路压测平台正式开源
6 月 25 日,国内知名的系统高可用专家数列科技宣布开源旗下核心产品能力,对外开放生产全链路压测平台产品的源代码,并正式命名为 Takin. 目前中国人寿.顺丰科技.希音.中通快递.中国移动.永辉超 ...
- Intellij IDEA 2021.2.3 最新版免费激活教程(可激活至 2099 年,亲测有效)
申明,本教程 Intellij IDEA 最新版破解.激活码均收集与网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除.如条件允许,建议大家购买正版. 本教程更新于:2021 年 10 月 ...
- hystrix的配置说明
在我们的日常开发中,有些时候需要和第三方系统进行对接操作,或者调用其他系统的 api 接口,但是我们不能保证这些第三方系统的接口一定是稳定的,当系统中产生大量的流量来访问这些第三方接口,这些第三方系统 ...
- 2021.9.25考试总结[NOIP模拟61]
终于有点阳间题了然而挂了60pts 哈哈 T1 交通 类似简单题,限制看似很复杂,但不难发现当确定一条边是否被删后会产生裙带关系,很多边会跟着自动被确定是否被删. 仔细观察可以得出这种关系会构成偶环结 ...
- 如何用PADS进行PCB设计?这6步就够了
在使用PADS进行PCB设计的过程中,需要对印制板的设计流程以及相关的注意事项进行重点关注,这样才能更好的为工作组中的设计人员提供系统的设计规范,同时也方便设计人员之间进行相互的交流和检查. 02 设 ...