前言
上期说到,假设我是系统管理员,想看到租户A数据的同时也能看到租户B的数据,这样就不能给系统管理员的账号设置租户id。
但不设置租户id的话,当系统管理员进行查询时,sql 的 where 条件会加上 ”tenant_id = null”,这个条件会导致管理员连 租户A 和 租户B 的数据都看不到。
我们可以在加上 ”tenant_id = ?” 条件前判断当前用户是否为系统管理员,如果是系统管理员,则直接返回,不处理为 sql 语句添加租户判断条件的代码逻辑。
通过查看 MybatisPlus 源码,得知处理租户条件的 sql 解析器位置如下:
com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser
那么我们可以参考和重写它,让 MybatisPlus 使用我们自定义的租户 sql 解析器。
具体操作
首先,在 MyTenantHandler 加一下 doUserFilter() 接口,用于判断当前用户是否为系统管理员。
package com.rjkj.quickboot.base.tenant;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
public interface MyTenantHandler extends TenantHandler {
// 判断当前用户是否为系统管理员
boolean doUserFilter();
void setTenantId(String tenantId);
}
然后,参考 TenantSqlParser 创建我们自定义的租户 sql 解析器,对 CRUD 的处理方法进行重写,代码可以直接复制,在方法开始前额外加上以下判断:
MyTenantHandler tenantHandler = (MyTenantHandler) getTenantHandler();
// 过滤系统管理员
if (tenantHandler.doUserFilter()){
return;
}
整体代码如下:
package com.rjkj.quickboot.base.tenant;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import javax.annotation.PostConstruct;
import java.util.List;
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class MyTenantSqlParser extends TenantSqlParser {
/**
* select 语句处理
*/
@Override
public void processSelectBody(SelectBody selectBody) {
MyTenantHandler tenantHandler = (MyTenantHandler) getTenantHandler();
if (tenantHandler.doUserFilter()){
// 过滤系统管理员
return;
}
if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody);
} else if (selectBody instanceof WithItem) {
WithItem withItem = (WithItem) selectBody;
if (withItem.getSelectBody() != null) {
processSelectBody(withItem.getSelectBody());
}
} else {
SetOperationList operationList = (SetOperationList) selectBody;
if (operationList.getSelects() != null && operationList.getSelects().size() > 0) {
operationList.getSelects().forEach(this::processSelectBody);
}
}
}
/**
* insert 语句处理
*/
@Override
public void processInsert(Insert insert) {
MyTenantHandler tenantHandler = (MyTenantHandler) getTenantHandler();
if (tenantHandler.doTableFilter(insert.getTable().getName())) {
// 过滤退出执行
return;
}
if (tenantHandler.doUserFilter()){
// 过滤系统管理员
return;
}
insert.getColumns().add(new Column(tenantHandler.getTenantIdColumn()));
if (insert.getSelect() != null) {
processPlainSelect((PlainSelect) insert.getSelect().getSelectBody(), true);
} else if (insert.getItemsList() != null) {
// fixed github pull/295
ItemsList itemsList = insert.getItemsList();
if (itemsList instanceof MultiExpressionList) {
((MultiExpressionList) itemsList).getExprList().forEach(el -> el.getExpressions().add(tenantHandler.getTenantId()));
} else {
((ExpressionList) insert.getItemsList()).getExpressions().add(tenantHandler.getTenantId());
}
} else {
throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
}
}
/**
* update 语句处理
*/
@Override
public void processUpdate(Update update) {
MyTenantHandler tenantHandler = (MyTenantHandler) getTenantHandler();
if (tenantHandler.doUserFilter()){
// 过滤系统管理员
return;
}
List<Table> tableList = update.getTables();
Assert.isTrue(null != tableList && tableList.size() < 2,
"Failed to process multiple-table update, please exclude the statementId");
Table table = tableList.get(0);
if (tenantHandler.doTableFilter(table.getName())) {
// 过滤退出执行
return;
}
update.setWhere(this.andExpression(table, update.getWhere()));
}
/**
* delete 语句处理
*/
@Override
public void processDelete(Delete delete) {
MyTenantHandler tenantHandler = (MyTenantHandler) getTenantHandler();
if (tenantHandler.doUserFilter()){
// 过滤系统管理员
return;
}
if (tenantHandler.doTableFilter(delete.getTable().getName())) {
// 过滤退出执行
return;
}
delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
}
}
同时对上期文章中的部分代码进行修改,主要是对处理类进行类型转换。(为了简单显示,只打印需要改动的代码部分)
MybatisPlusConfig.java
@Configuration
public class MybatisPlusConfig {
...
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 自定义租户sql解析器
MyTenantSqlParser tenantSqlParser = new MyTenantSqlParser();
// 自定义租户sql处理类
tenantSqlParser.setTenantHandler(new MyTenantHandler() {
@Override
public boolean doUserFilter() {
// 如果是系统管理员,它的租户id为null
return ObjectUtil.isNull(getTenantId());
}
...
}
}
...
TenantInterceptor.java
@Component
public class TenantInterceptor implements HandlerInterceptor {
@Autowired
private PaginationInterceptor pi;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 自定义租户sql解析器
MyTenantSqlParser tenantSqlParser = (MyTenantSqlParser) pi.getSqlParserList().get(0);
MyTenantHandler myTenantHandler = (MyTenantHandler) tenantSqlParser.getTenantHandler();
//获取当前用户
LoginUser currentUser = getCurrentUser();
myTenantHandler.setTenantId(currentUser.getTenantId());
return true;
}
...
}
相比 Mybatis,Mybatis-Plus 的支持更加丰富,除了本文描述的对租户支持,还内置了各种插件,如性能分析、分页插件等,并且使用强大的条件构造器,足以替代手写 sql,满足大部分的查询需求。
下次有机会再继续研究 Mybatis-Plus 的其他的特性,希望本文对大家有所帮助。
以上就是本期分享,如果大家对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!