首页 > 其他分享 >MybatisPlus对租户模式的支持(一)

MybatisPlus对租户模式的支持(一)

时间:2022-10-20 15:37:00浏览次数:42  
标签:MybatisPlus 租户 模式 org import com id tenant

前言

最近接到一个任务,要将现有的用户系统改成租户模式。改造成租户模式最简单的方式就是为需要进行数据隔离的表加上租户 id 字段,然后前端调接口查询数据时,根据当前用户的租户 id,在查询的 sql 中的 where 条件中,对数据的查询范围进行限定。

一开始对系统进行租户模式改造时,写了很多重复的根据租户 id 限定数据范围的冗余代码。后面查看网上资料时,发现 Mybatis-plus 本身对租户模式的支持,于是就有了这篇心得分享。


具体操作

首先来看看官方的 Mybatis-plus 提供的接口:TenantHandler.java

package com.baomidou.mybatisplus.extension.plugins.tenant;

import net.sf.jsqlparser.expression.Expression;

public interface TenantHandler {
Expression getTenantId();

String getTenantIdColumn();

boolean doTableFilter(String tableName);
}

只要能实现以上三个接口方法,Mybatis-plus 就能自动处理查询时对租户 id 字段的限定,和增删改时自动为语句增加租户 id 字段的维护。

接下来创建 MyTenantHandler.java,继承 TenantHandler 接口的同时,增加一个 setTenantId 的接口,以便设置当前用户的租户 id。

package com.rjkj.quickboot.base.tenant;

import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;

public interface MyTenantHandler extends TenantHandler {
void setTenantId(String tenantId);
}

然后在 MybatisPlusConfig 配置中,实现接口方法:MybatisPlusConfig.java

package com.rjkj.quickboot.base.config;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.parser.SqlParserHelper;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.google.common.collect.Lists;
import com.rjkj.quickboot.base.tenant.MyTenantHandler;
import com.rjkj.quickboot.base.tenant.MyTenantSqlParser;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.regex.Pattern;

@Configuration
public class MybatisPlusConfig {

@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

TenantSqlParser tenantSqlParser = new TenantSqlParser()
.setTenantHandler(new MyTenantHandler() {

//进行多租户sql条件改写处理的表
private final List<String> TENANT_TABLES = Lists.newArrayList("user", "sys_user", "sys_role");

//线程局部变量
private final ThreadLocal<String> TENANT_CONTEXT = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return null;
}
};

@Override
public void setTenantId(String tenantId) {
TENANT_CONTEXT.set(tenantId);
}

@Override
public Expression getTenantId() {
String tenantId = TENANT_CONTEXT.get();
if (null == tenantId) {
return null;
}
return new StringValue(tenantId);
}

@Override
//用来区分不同租户数据的字段名
public String getTenantIdColumn() {
return "tenant_id";
}

@Override
public boolean doTableFilter(String tableName) {
// 忽略掉一些表,不在TENANT_TABLES中的表忽略掉
return TENANT_TABLES.stream().noneMatch((e) -> e.equalsIgnoreCase(tableName));
}
});

paginationInterceptor.setSqlParserList(Lists.newArrayList(tenantSqlParser));
return paginationInterceptor;
}
}

接下来增加一个拦截器,获取当前会话的用户的租户id。

小编是通过 Shiro 获取当前登录用户的信息,也可以通过解析 token 获取当前用户信息,很多方法都可以。

package com.rjkj.quickboot.base.inteceptor;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.rjkj.quickboot.base.system.vo.LoginUser;
import com.rjkj.quickboot.base.tenant.MyTenantHandler;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class TenantInterceptor implements HandlerInterceptor {

@Autowired
private PaginationInterceptor pi;

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
TenantSqlParser tenantSqlParser = (TenantSqlParser) pi.getSqlParserList().get(0);
MyTenantHandler myTenantHandler = (MyTenantHandler) tenantSqlParser.getTenantHandler();

//获取当前用户
LoginUser currentUser = getCurrentUser();
myTenantHandler.setTenantId(currentUser.getTenantId());
return true;
}

private static LoginUser getCurrentUser(){
return (LoginUser) SecurityUtils.getSubject().getPrincipal();
}
}

最后通过实现 WebMvcConfigurer接口,在自定义拦截器中添加刚刚的拦截器。

package com.rjkj.quickboot.base.config;

import com.rjkj.quickboot.base.inteceptor.TenantInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

@Resource
private TenantInterceptor tenantInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
//租户拦截器
registry.addInterceptor(tenantInterceptor)
.addPathPatterns(
"/sys/user/**",
"/user/**",
"/sysRole/**")
.excludePathPatterns("/sys/user/login", "/user/login");
}

}

当查询 “user” ,  “sys_user” , “sys_role” 这些表时,会在 where 条件中自动加上对 tenant_id 字段的条件限定,并且匹配拦截器路径时会设置tenant_id 的值为当前用户的 tenant_id。

目前大致能实现租户模式的功能,但当自测进行使用时还是发现有一些问题。

假设小编是系统管理员,想看到租户A 数据的同时,也能看到租户B 的数据,这样就不能给系统管理员的账号设置租户id 。但如果不设置租户id ,当系统管理员进行查询时,sql 的 where 条件会加上 ”tenant_id = null” ,结果当然就是租户A 和租户B 的数据都看不到了。

这样就需要在对租户sql解析进行重写,增加对当前用户是否系统管理员的判断,这个放到下文继续研究。


以上就是本期分享,如果大家对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!

标签:MybatisPlus,租户,模式,org,import,com,id,tenant
From: https://blog.51cto.com/u_15345191/5775627

相关文章

  • 设计模式之UML类图
    UML图示简介在UML中,类使用包含类名、属性和操作且带有分割线的长方形来表示,如图所示,定义一个Student类,它包含属性name、age和id,以及操作modifyInfo()。其对应的......
  • 【多线程那些事儿】如何使用C++写一个线程安全的单例模式?
    如何写一个线程安全的单例模式?单例模式的简单实现单例模式大概是流传最为广泛的设计模式之一了。一份简单的实现代码大概是下面这个样子的:classsingleton{public: s......
  • 数字化转型后企业的管理模式有哪些改变呢?
    数字化转型后的企业在管理模式上整体呈现的就是数字化的管理,这是因为数字化转型的主题就是要构建“业务数字化、数字资产化、资产服务化、服务业务化”闭环,通过数字化技术能......
  • 02- 快速入门MybatisPlus
    创建表现有一张User表,其表结构如下:[email protected]@[email protected]......
  • JAVA设计模式-代理模式
    JAVA设计模式-代理模式一、介绍代理模式是一种结构型模式,它指的是给某一个对象提供一个代理对象,并且由代理对象控制原有对象的引用,可以增强原有对象的功能以及降低系统......
  • SpringBoot+MybatisPlus--使用
    1、在entity包下面创建数据实体类,添加注解@Data,如果和数据库名字不一样的话,还需要+@TableField注解。字段名字不一样也需要添加此注解@TableName(value="user")publi......
  • SpringBoot+MybatisPlus--文件上传
    文件上传时,对页面的form表单有如下要求: 采用post方式提交数据   method="post"采用multipart格式上传文件  enctype="multipart/form-data"使用inp......
  • Javascript事件设计模式(七)
    一:事件设计概述事件机制可以使程序逻辑更加符合现实世界,在JavaScript中很多对象都有自己的事件,例如按钮就有onclick事件,下拉列表框就有onchange事件,通过这些事件可......
  • springboot + mybatisplus出现was not registered for synchronization because synch
    原因一:缺少事务注解,底层mybatisplus的接口方法有事务原因二:该服务器被限制访问要连接的数据库原因三:乐观锁失效乐观锁由@version注解标注,有以下使用要求支持的......
  • 《ASCE1885的设计模式》---观察者模式
    观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。这一模式镇中关键对象是目标(subject)和观察者(observer)。......