init
This commit is contained in:
37
jeelowcode-framework/jeelowcode-tenant/pom.xml
Normal file
37
jeelowcode-framework/jeelowcode-tenant/pom.xml
Normal 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>jeelowcode-framework</artifactId>
|
||||
<groupId>com.jeelowcode</groupId>
|
||||
<version>${jeelowcode.version}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jeelowcode-tenant</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<version>${jeelowcode.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
<description> JeeLowCode低代码平台 - 租户 </description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jeelowcode</groupId>
|
||||
<artifactId>jeelowcode-utils</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jeelowcode</groupId>
|
||||
<artifactId>tool-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
package com.jeelowcode.framework.tenant.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 自定义租户过滤
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE,ElementType.METHOD})
|
||||
public @interface JeeLowCodeTenantIgnore {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
|
||||
package com.jeelowcode.framework.tenant.aspect;
|
||||
|
||||
|
||||
import com.jeelowcode.tool.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.jeelowcode.framework.tenant.utils.JeeLowCodeTenantUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
|
||||
|
||||
@Aspect
|
||||
public class JeeLowCodeTenantAspect {
|
||||
|
||||
public JeeLowCodeTenantAspect() {
|
||||
}
|
||||
|
||||
|
||||
// 定义一个切点,用于匹配带有Administrator注解的类
|
||||
@Pointcut("@within(com.jeelowcode.framework.tenant.annotation.JeeLowCodeTenantIgnore)")
|
||||
private void pointcutClass() {}
|
||||
|
||||
// 定义一个切点,用于匹配带有Administrator注解的方法
|
||||
@Pointcut("@annotation(com.jeelowcode.framework.tenant.annotation.JeeLowCodeTenantIgnore)")
|
||||
private void pointcutMethod() {}
|
||||
|
||||
@Around("pointcutClass() || pointcutMethod()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
Object obj;
|
||||
boolean oldjeelowcodeIgnore = JeeLowCodeTenantUtils.isIgnore();
|
||||
boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
JeeLowCodeTenantUtils.setIgnore(Boolean.TRUE);
|
||||
TenantContextHolder.setIgnore(true);//芋道
|
||||
obj = point.proceed();
|
||||
} finally {
|
||||
JeeLowCodeTenantUtils.setIgnore(oldjeelowcodeIgnore);
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
package com.jeelowcode.framework.tenant.auto;
|
||||
|
||||
import com.jeelowcode.framework.tenant.aspect.JeeLowCodeTenantAspect;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class JeeLowCodeTenantAutoConfiguration {
|
||||
|
||||
// ========== AOP ==========
|
||||
|
||||
@Bean
|
||||
public JeeLowCodeTenantAspect jeeLowCodeTenantAspect() {
|
||||
return new JeeLowCodeTenantAspect();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
|
||||
package com.jeelowcode.framework.tenant.parse;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.jeelowcode.framework.exception.JeeLowCodeException;
|
||||
import com.jeelowcode.framework.tenant.utils.JeeLowCodeTenantUtils;
|
||||
import com.jeelowcode.framework.utils.utils.FuncBase;
|
||||
import com.jeelowcode.framework.utils.utils.FuncWebBase;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.*;
|
||||
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 net.sf.jsqlparser.statement.update.UpdateSet;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 租户工具类
|
||||
*/
|
||||
public class JeeLowCodeTenantParse {
|
||||
private final AntPathMatcher pathMatcher;
|
||||
|
||||
public JeeLowCodeTenantParse() {
|
||||
this.pathMatcher = new AntPathMatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* select 处理查询语句
|
||||
* @param select
|
||||
*/
|
||||
public static void processSelect(Select select) {
|
||||
JeeLowCodeTenantParse parse=new JeeLowCodeTenantParse();
|
||||
|
||||
final String whereSegment = "";
|
||||
parse.processSelectBody(select.getSelectBody(), whereSegment);
|
||||
List<WithItem> withItemsList = select.getWithItemsList();
|
||||
if (!FuncBase.isEmpty(withItemsList)) {
|
||||
withItemsList.forEach(withItem -> parse.processSelectBody(withItem, whereSegment));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update 语句处理
|
||||
*/
|
||||
public static void processUpdate(Update update) {
|
||||
JeeLowCodeTenantParse parse=new JeeLowCodeTenantParse();
|
||||
final Table table = update.getTable();
|
||||
final Object obj=null;
|
||||
ArrayList<UpdateSet> sets = update.getUpdateSets();
|
||||
if (!FuncBase.isEmpty(sets)) {
|
||||
sets.forEach(us -> us.getExpressions().forEach(ex -> {
|
||||
if (ex instanceof SubSelect) {
|
||||
parse.processSelectBody(((SubSelect) ex).getSelectBody(), (String) obj);
|
||||
}
|
||||
}));
|
||||
}
|
||||
update.setWhere(parse.andExpression(table, update.getWhere(), (String) obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* delete 语句处理
|
||||
*/
|
||||
public static void processDelete(Delete delete) {
|
||||
JeeLowCodeTenantParse parse=new JeeLowCodeTenantParse();
|
||||
final Object obj=null;
|
||||
delete.setWhere(parse.andExpression(delete.getTable(), delete.getWhere(), (String) obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param insert
|
||||
*/
|
||||
public static void processInsert(Insert insert) {
|
||||
JeeLowCodeTenantParse parse=new JeeLowCodeTenantParse();
|
||||
|
||||
Object obj=null;
|
||||
if (parse.ignoreTable(insert.getTable().getName())) {
|
||||
// 过滤退出执行
|
||||
return;
|
||||
}
|
||||
List<Column> columns = insert.getColumns();
|
||||
if (FuncBase.isEmpty(columns)) {
|
||||
// 针对不给列名的insert 不处理
|
||||
return;
|
||||
}
|
||||
String tenantIdColumn = JeeLowCodeTenantUtils.TENANT_ID_COLUMN;
|
||||
if (parse.ignoreInsert(columns, tenantIdColumn)) {
|
||||
// 针对已给出租户列的insert 不处理
|
||||
return;
|
||||
}
|
||||
columns.add(new Column(tenantIdColumn));
|
||||
|
||||
// fixed gitee pulls/141 duplicate update
|
||||
List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
|
||||
if (FuncBase.isNotEmpty(duplicateUpdateColumns)) {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
|
||||
equalsTo.setRightExpression(parse.getTenantId());
|
||||
duplicateUpdateColumns.add(equalsTo);
|
||||
}
|
||||
|
||||
Select select = insert.getSelect();
|
||||
if (select != null && (select.getSelectBody() instanceof PlainSelect)) { //fix github issue 4998 修复升级到4.5版本的问题
|
||||
parse.processInsertSelect(select.getSelectBody(), (String) obj);
|
||||
} else if (insert.getItemsList() != null) {
|
||||
// fixed github pull/295
|
||||
ItemsList itemsList = insert.getItemsList();
|
||||
Expression tenantId = parse.getTenantId();
|
||||
if (itemsList instanceof MultiExpressionList) {
|
||||
((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(tenantId));
|
||||
} else {
|
||||
List<Expression> expressions = ((ExpressionList) itemsList).getExpressions();
|
||||
if (FuncBase.isNotEmpty(expressions)) {//fix github issue 4998 jsqlparse 4.5 批量insert ItemsList不是MultiExpressionList 了,需要特殊处理
|
||||
int len = expressions.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
Expression expression = expressions.get(i);
|
||||
if (expression instanceof RowConstructor) {
|
||||
((RowConstructor) expression).getExprList().getExpressions().add(tenantId);
|
||||
} else if (expression instanceof Parenthesis) {
|
||||
RowConstructor rowConstructor = new RowConstructor()
|
||||
.withExprList(new ExpressionList(((Parenthesis) expression).getExpression(), tenantId));
|
||||
expressions.set(i, rowConstructor);
|
||||
} else {
|
||||
if (len - 1 == i) { // (?,?) 只有最后一个expre的时候才拼接tenantId
|
||||
expressions.add(tenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
expressions.add(tenantId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JeeLowCodeException("Failed to process multiple-table update, please exclude the tableName or statementId");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加 SelectItem ok
|
||||
*
|
||||
* @param selectItems SelectItem
|
||||
*/
|
||||
private void appendSelectItem(List<SelectItem> selectItems) {
|
||||
if (FuncBase.isEmpty(selectItems)) {
|
||||
return;
|
||||
}
|
||||
if (selectItems.size() == 1) {
|
||||
SelectItem item = selectItems.get(0);
|
||||
if (item instanceof AllColumns || item instanceof AllTableColumns) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectItems.add(new SelectExpressionItem(new Column("tenant_id")));
|
||||
}
|
||||
|
||||
/**
|
||||
* ok
|
||||
* 处理 insert into select
|
||||
* <p>
|
||||
* 进入这里表示需要 insert 的表启用了多租户,则 select 的表都启动了
|
||||
*
|
||||
* @param selectBody SelectBody
|
||||
*/
|
||||
private void processInsertSelect(SelectBody selectBody, final String whereSegment) {
|
||||
PlainSelect plainSelect = (PlainSelect) selectBody;
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
if (fromItem instanceof Table) {
|
||||
// fixed gitee pulls/141 duplicate update
|
||||
processPlainSelect(plainSelect, whereSegment);
|
||||
appendSelectItem(plainSelect.getSelectItems());
|
||||
} else if (fromItem instanceof SubSelect) {
|
||||
SubSelect subSelect = (SubSelect) fromItem;
|
||||
appendSelectItem(plainSelect.getSelectItems());
|
||||
processInsertSelect(subSelect.getSelectBody(), whereSegment);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ok
|
||||
* delete update 语句 where 处理
|
||||
*/
|
||||
private Expression andExpression(Table table, Expression where, final String whereSegment) {
|
||||
//获得where条件表达式
|
||||
final Expression expression = buildTableExpression(table, where, whereSegment);
|
||||
if (expression == null) {
|
||||
return where;
|
||||
}
|
||||
if (where != null) {
|
||||
if (where instanceof OrExpression) {
|
||||
return new AndExpression(new Parenthesis(where), expression);
|
||||
} else {
|
||||
return new AndExpression(where, expression);
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
//ok
|
||||
private void processSelectBody(SelectBody selectBody, final String whereSegment) {
|
||||
if (selectBody == null) {
|
||||
return;
|
||||
}
|
||||
if (selectBody instanceof PlainSelect) {
|
||||
processPlainSelect((PlainSelect) selectBody, whereSegment);
|
||||
} else if (selectBody instanceof WithItem) {
|
||||
WithItem withItem = (WithItem) selectBody;
|
||||
processSelectBody(withItem.getSubSelect().getSelectBody(), whereSegment);
|
||||
} else {
|
||||
SetOperationList operationList = (SetOperationList) selectBody;
|
||||
List<SelectBody> selectBodyList = operationList.getSelects();
|
||||
if (FuncBase.isNotEmpty(selectBodyList)) {
|
||||
selectBodyList.forEach(body -> processSelectBody(body, whereSegment));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ok
|
||||
* 处理 PlainSelect
|
||||
*/
|
||||
private void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
|
||||
//#3087 github
|
||||
List<SelectItem> selectItems = plainSelect.getSelectItems();
|
||||
if (FuncBase.isNotEmpty(selectItems)) {
|
||||
selectItems.forEach(selectItem -> processSelectItem(selectItem, whereSegment));
|
||||
}
|
||||
|
||||
// 处理 where 中的子查询
|
||||
Expression where = plainSelect.getWhere();
|
||||
processWhereSubSelect(where, whereSegment);
|
||||
|
||||
// 处理 fromItem
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
List<Table> list = processFromItem(fromItem, whereSegment);
|
||||
List<Table> mainTables = new ArrayList<>(list);
|
||||
|
||||
// 处理 join
|
||||
List<Join> joins = plainSelect.getJoins();
|
||||
if (FuncBase.isNotEmpty(joins)) {
|
||||
mainTables = processJoins(mainTables, joins, whereSegment);
|
||||
}
|
||||
|
||||
// 当有 mainTable 时,进行 where 条件追加
|
||||
if (FuncBase.isNotEmpty(mainTables)) {
|
||||
plainSelect.setWhere(builderExpression(where, mainTables, whereSegment));
|
||||
}
|
||||
//System.out.println("plainSelect======"+JSONUtil.toJsonStr(plainSelect));
|
||||
}
|
||||
|
||||
//ok
|
||||
private void processSelectItem(SelectItem selectItem, final String whereSegment) {
|
||||
if (selectItem instanceof SelectExpressionItem) {
|
||||
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
|
||||
final Expression expression = selectExpressionItem.getExpression();
|
||||
if (expression instanceof SubSelect) {
|
||||
processSelectBody(((SubSelect) expression).getSelectBody(), whereSegment);
|
||||
} else if (expression instanceof Function) {
|
||||
processFunction((Function) expression, whereSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ok
|
||||
* 处理函数
|
||||
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
|
||||
* <p> fixed gitee pulls/141</p>
|
||||
*
|
||||
* @param function
|
||||
*/
|
||||
private void processFunction(Function function, final String whereSegment) {
|
||||
ExpressionList parameters = function.getParameters();
|
||||
if (parameters != null) {
|
||||
parameters.getExpressions().forEach(expression -> {
|
||||
if (expression instanceof SubSelect) {
|
||||
processSelectBody(((SubSelect) expression).getSelectBody(), whereSegment);
|
||||
} else if (expression instanceof Function) {
|
||||
processFunction((Function) expression, whereSegment);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//ok
|
||||
private void processWhereSubSelect(Expression where, final String whereSegment) {
|
||||
if (where == null) {
|
||||
return;
|
||||
}
|
||||
if (where instanceof FromItem) {
|
||||
processOtherFromItem((FromItem) where, whereSegment);
|
||||
return;
|
||||
}
|
||||
if (where.toString().indexOf("SELECT") > 0) {
|
||||
// 有子查询
|
||||
if (where instanceof BinaryExpression) {
|
||||
// 比较符号 , and , or , 等等
|
||||
BinaryExpression expression = (BinaryExpression) where;
|
||||
processWhereSubSelect(expression.getLeftExpression(), whereSegment);
|
||||
processWhereSubSelect(expression.getRightExpression(), whereSegment);
|
||||
} else if (where instanceof InExpression) {
|
||||
// in
|
||||
InExpression expression = (InExpression) where;
|
||||
Expression inExpression = expression.getRightExpression();
|
||||
if (inExpression instanceof SubSelect) {
|
||||
processSelectBody(((SubSelect) inExpression).getSelectBody(), whereSegment);
|
||||
}
|
||||
} else if (where instanceof ExistsExpression) {
|
||||
// exists
|
||||
ExistsExpression expression = (ExistsExpression) where;
|
||||
processWhereSubSelect(expression.getRightExpression(), whereSegment);
|
||||
} else if (where instanceof NotExpression) {
|
||||
// not exists
|
||||
NotExpression expression = (NotExpression) where;
|
||||
processWhereSubSelect(expression.getExpression(), whereSegment);
|
||||
} else if (where instanceof Parenthesis) {
|
||||
Parenthesis expression = (Parenthesis) where;
|
||||
processWhereSubSelect(expression.getExpression(), whereSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ok
|
||||
* 处理子查询等
|
||||
*/
|
||||
private void processOtherFromItem(FromItem fromItem, final String whereSegment) {
|
||||
// 去除括号
|
||||
while (fromItem instanceof ParenthesisFromItem) {
|
||||
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
|
||||
}
|
||||
|
||||
if (fromItem instanceof SubSelect) {
|
||||
SubSelect subSelect = (SubSelect) fromItem;
|
||||
if (subSelect.getSelectBody() != null) {
|
||||
processSelectBody(subSelect.getSelectBody(), whereSegment);
|
||||
}
|
||||
} else if (fromItem instanceof ValuesList) {
|
||||
//logger.debug("Perform a subQuery, if you do not give us feedback");
|
||||
} else if (fromItem instanceof LateralSubSelect) {
|
||||
LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
|
||||
if (lateralSubSelect.getSubSelect() != null) {
|
||||
SubSelect subSelect = lateralSubSelect.getSubSelect();
|
||||
if (subSelect.getSelectBody() != null) {
|
||||
processSelectBody(subSelect.getSelectBody(), whereSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ok
|
||||
private List<Table> processFromItem(FromItem fromItem, final String whereSegment) {
|
||||
// 处理括号括起来的表达式
|
||||
while (fromItem instanceof ParenthesisFromItem) {
|
||||
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
|
||||
}
|
||||
|
||||
List<Table> mainTables = new ArrayList<>();
|
||||
// 无 join 时的处理逻辑
|
||||
if (fromItem instanceof Table) {
|
||||
Table fromTable = (Table) fromItem;
|
||||
mainTables.add(fromTable);
|
||||
} else if (fromItem instanceof SubJoin) {
|
||||
// SubJoin 类型则还需要添加上 where 条件
|
||||
List<Table> tables = processSubJoin((SubJoin) fromItem, whereSegment);
|
||||
mainTables.addAll(tables);
|
||||
} else {
|
||||
// 处理下 fromItem
|
||||
processOtherFromItem(fromItem, whereSegment);
|
||||
}
|
||||
return mainTables;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ok
|
||||
* 处理 sub join
|
||||
*
|
||||
* @param subJoin subJoin
|
||||
* @return Table subJoin 中的主表
|
||||
*/
|
||||
private List<Table> processSubJoin(SubJoin subJoin, final String whereSegment) {
|
||||
List<Table> mainTables = new ArrayList<>();
|
||||
if (subJoin.getJoinList() != null) {
|
||||
List<Table> list = processFromItem(subJoin.getLeft(), whereSegment);
|
||||
mainTables.addAll(list);
|
||||
mainTables = processJoins(mainTables, subJoin.getJoinList(), whereSegment);
|
||||
}
|
||||
return mainTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 joins
|
||||
*ok
|
||||
* @param mainTables 可以为 null
|
||||
* @param joins join 集合
|
||||
* @return List<Table> 右连接查询的 Table 列表
|
||||
*/
|
||||
private List<Table> processJoins(List<Table> mainTables, List<Join> joins, final String whereSegment) {
|
||||
// join 表达式中最终的主表
|
||||
Table mainTable = null;
|
||||
// 当前 join 的左表
|
||||
Table leftTable = null;
|
||||
|
||||
if (mainTables.size() == 1) {
|
||||
mainTable = mainTables.get(0);
|
||||
leftTable = mainTable;
|
||||
}
|
||||
|
||||
//对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
|
||||
Deque<List<Table>> onTableDeque = new LinkedList<>();
|
||||
for (Join join : joins) {
|
||||
// 处理 on 表达式
|
||||
FromItem joinItem = join.getRightItem();
|
||||
|
||||
// 获取当前 join 的表,subJoint 可以看作是一张表
|
||||
List<Table> joinTables = null;
|
||||
if (joinItem instanceof Table) {
|
||||
joinTables = new ArrayList<>();
|
||||
joinTables.add((Table) joinItem);
|
||||
} else if (joinItem instanceof SubJoin) {
|
||||
joinTables = processSubJoin((SubJoin) joinItem, whereSegment);
|
||||
}
|
||||
|
||||
if (joinTables != null) {
|
||||
|
||||
// 如果是隐式内连接
|
||||
if (join.isSimple()) {
|
||||
mainTables.addAll(joinTables);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 当前表是否忽略
|
||||
Table joinTable = joinTables.get(0);
|
||||
|
||||
List<Table> onTables = null;
|
||||
// 如果不要忽略,且是右连接,则记录下当前表
|
||||
if (join.isRight()) {
|
||||
mainTable = joinTable;
|
||||
mainTables.clear();
|
||||
if (leftTable != null) {
|
||||
onTables = Collections.singletonList(leftTable);
|
||||
}
|
||||
} else if (join.isInner()) {
|
||||
if (mainTable == null) {
|
||||
onTables = Collections.singletonList(joinTable);
|
||||
} else {
|
||||
onTables = Arrays.asList(mainTable, joinTable);
|
||||
}
|
||||
mainTable = null;
|
||||
mainTables.clear();
|
||||
} else {
|
||||
onTables = Collections.singletonList(joinTable);
|
||||
}
|
||||
|
||||
if (mainTable != null && !mainTables.contains(mainTable)) {
|
||||
mainTables.add(mainTable);
|
||||
}
|
||||
|
||||
// 获取 join 尾缀的 on 表达式列表
|
||||
Collection<Expression> originOnExpressions = join.getOnExpressions();
|
||||
// 正常 join on 表达式只有一个,立刻处理
|
||||
if (originOnExpressions.size() == 1 && onTables != null) {
|
||||
List<Expression> onExpressions = new LinkedList<>();
|
||||
onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables, whereSegment));
|
||||
join.setOnExpressions(onExpressions);
|
||||
leftTable = mainTable == null ? joinTable : mainTable;
|
||||
continue;
|
||||
}
|
||||
// 表名压栈,忽略的表压入 null,以便后续不处理
|
||||
onTableDeque.push(onTables);
|
||||
// 尾缀多个 on 表达式的时候统一处理
|
||||
if (originOnExpressions.size() > 1) {
|
||||
Collection<Expression> onExpressions = new LinkedList<>();
|
||||
for (Expression originOnExpression : originOnExpressions) {
|
||||
List<Table> currentTableList = onTableDeque.poll();
|
||||
if (FuncBase.isEmpty(currentTableList)) {
|
||||
onExpressions.add(originOnExpression);
|
||||
} else {
|
||||
onExpressions.add(builderExpression(originOnExpression, currentTableList, whereSegment));
|
||||
}
|
||||
}
|
||||
join.setOnExpressions(onExpressions);
|
||||
}
|
||||
leftTable = joinTable;
|
||||
} else {
|
||||
processOtherFromItem(joinItem, whereSegment);
|
||||
leftTable = null;
|
||||
}
|
||||
}
|
||||
|
||||
return mainTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* ok
|
||||
* 处理条件
|
||||
*/
|
||||
private Expression builderExpression(Expression currentExpression, List<Table> tables, final String whereSegment) {
|
||||
// 没有表需要处理直接返回
|
||||
if (FuncBase.isEmpty(tables)) {
|
||||
return currentExpression;
|
||||
}
|
||||
// 构造每张表的条件
|
||||
List<Expression> expressions = tables.stream()
|
||||
.map(item -> buildTableExpression(item, currentExpression, whereSegment))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 没有表需要处理直接返回
|
||||
if (FuncBase.isEmpty(expressions)) {
|
||||
return currentExpression;
|
||||
}
|
||||
|
||||
// 注入的表达式
|
||||
Expression injectExpression = expressions.get(0);
|
||||
// 如果有多表,则用 and 连接
|
||||
if (expressions.size() > 1) {
|
||||
for (int i = 1; i < expressions.size(); i++) {
|
||||
injectExpression = new AndExpression(injectExpression, expressions.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (currentExpression == null) {
|
||||
return injectExpression;
|
||||
}
|
||||
if (currentExpression instanceof OrExpression) {
|
||||
return new AndExpression(new Parenthesis(currentExpression), injectExpression);
|
||||
} else {
|
||||
return new AndExpression(currentExpression, injectExpression);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建租户条件表达式
|
||||
*
|
||||
* @param table 表对象
|
||||
* @param where 当前where条件
|
||||
* @param whereSegment 所属Mapper对象全路径(在原租户拦截器功能中,这个参数并不需要参与相关判断)
|
||||
* @return 租户条件表达式
|
||||
*
|
||||
*/
|
||||
private Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
|
||||
if (ignoreTable(table.getName())) {
|
||||
return null;
|
||||
}
|
||||
return new EqualsTo(getAliasColumn(table), getTenantId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 租户字段别名设置
|
||||
* <p>tenantId 或 tableAlias.tenantId</p>
|
||||
*
|
||||
* @param table 表对象
|
||||
* @return 字段
|
||||
*/
|
||||
private Column getAliasColumn(Table table) {
|
||||
StringBuilder column = new StringBuilder();
|
||||
// todo 该起别名就要起别名,禁止修改此处逻辑
|
||||
if (table.getAlias() != null) {
|
||||
column.append(table.getAlias().getName()).append(".");
|
||||
}
|
||||
column.append(JeeLowCodeTenantUtils.TENANT_ID_COLUMN);
|
||||
return new Column(column.toString());
|
||||
}
|
||||
|
||||
//判断是否忽略租户
|
||||
private boolean ignoreTable(String tableName) {
|
||||
Set<String> ignoreLikeTables = JeeLowCodeTenantUtils.getIgnoreLikeTables();
|
||||
for(String table:ignoreLikeTables){
|
||||
if(table.endsWith("*")){// *结尾 -> tbl_*
|
||||
table = table.substring(0, table.length() - 1);
|
||||
if(tableName.startsWith(table)){//前缀开头
|
||||
return true;//不需要进行租户过滤
|
||||
}
|
||||
}else if(table.startsWith("*")){// *开头 -> *_seq
|
||||
table = table.substring(1);
|
||||
if(tableName.endsWith(table)){//前缀开头
|
||||
return true;//不需要进行租户过滤
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
HttpServletRequest request = FuncWebBase.getRequest();
|
||||
return JeeLowCodeTenantUtils.isIgnore() // 情况一,全局忽略多租户
|
||||
|| JeeLowCodeTenantUtils.getIgnoreTables().contains(tableName) // 情况二,忽略多租户的表
|
||||
|| isIgnoreUrl(request);// 情况二,忽略url
|
||||
|
||||
}
|
||||
|
||||
private boolean isIgnoreUrl(HttpServletRequest request) {
|
||||
if(FuncBase.isEmpty(request)){
|
||||
return false;
|
||||
}
|
||||
String requestURI = request.getRequestURI();
|
||||
if(FuncBase.isEmpty(requestURI)){
|
||||
return false;
|
||||
}
|
||||
// 快速匹配,保证性能
|
||||
if (CollUtil.contains(JeeLowCodeTenantUtils.getIGNORE_URLS(),requestURI)) {
|
||||
return true;
|
||||
}
|
||||
// 逐个 Ant 路径匹配
|
||||
for (String url : JeeLowCodeTenantUtils.getIGNORE_URLS()) {
|
||||
if (pathMatcher.match(url, request.getRequestURI())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//获取租户id
|
||||
private Expression getTenantId() {
|
||||
Long tenantId = JeeLowCodeTenantUtils.getTenantId();
|
||||
if(FuncBase.isEmpty(tenantId)){
|
||||
tenantId=-1L;
|
||||
}
|
||||
//拿到当前租户
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略插入租户字段逻辑
|
||||
*
|
||||
* @param columns 插入字段
|
||||
* @param tenantIdColumn 租户 ID 字段
|
||||
* @return
|
||||
*/
|
||||
private boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
|
||||
return columns.stream().map(Column::getColumnName).anyMatch(i -> i.equalsIgnoreCase(tenantIdColumn));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
|
||||
|
||||
package com.jeelowcode.framework.tenant.utils;
|
||||
|
||||
|
||||
import com.jeelowcode.framework.tenant.parse.JeeLowCodeTenantParse;
|
||||
import com.jeelowcode.framework.tenant.parse.JeeLowCodeTenantParse;
|
||||
import com.jeelowcode.framework.utils.utils.FuncBase;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.delete.Delete;
|
||||
import net.sf.jsqlparser.statement.insert.Insert;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import net.sf.jsqlparser.statement.update.Update;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 租户工具类
|
||||
*/
|
||||
public class JeeLowCodeTenantUtils {
|
||||
|
||||
//租户列
|
||||
public static final String TENANT_ID_COLUMN = "tenant_id";
|
||||
|
||||
//获取当前线程租户编号
|
||||
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
|
||||
//当前线程是否忽略多租户 true=忽略 false=不忽略
|
||||
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
|
||||
//不进行多租户处理的表列表
|
||||
private static Set<String> IGNORE_TABLES = new HashSet<>();
|
||||
//不进行多租户的url
|
||||
private static Set<String> IGNORE_URLS = Collections.emptySet();
|
||||
//不进行多租户处理的表列表-以*结尾
|
||||
private static Set<String> IGNORE_LIKE_TABLES = new HashSet<>();
|
||||
|
||||
private static String TMP_PARAM="jeelowcode_param_mjkj_tmp_";
|
||||
|
||||
|
||||
private static String fomatSql(String sql) {
|
||||
try{
|
||||
sql=sql.replaceAll(",jdbcType=","_jeelowcode_jdbcType_");
|
||||
String patternString = "#\\{([^}]+)\\}"; // 正则表达式字符串
|
||||
Pattern pattern = Pattern.compile(patternString);
|
||||
Matcher matcher = pattern.matcher(sql);
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (matcher.find()) {
|
||||
String variableName = matcher.group(1); // 获取#{}内的变量名
|
||||
matcher.appendReplacement(sb, TMP_PARAM + variableName);
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
|
||||
sql = sb.toString();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
private static String resetSql(String sql){
|
||||
try{
|
||||
sql=sql.replaceAll("_jeelowcode_jdbcType_",",jdbcType=");
|
||||
String patternString = TMP_PARAM+"([a-zA-Z0-9_.]+)(?:,(\\s*jdbcType\\s*=\\s*[a-zA-Z]+))?"; // 正则表达式字符串
|
||||
Pattern pattern = Pattern.compile(patternString);
|
||||
Matcher matcher = pattern.matcher(sql);
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (matcher.find()) {
|
||||
String variableName = matcher.group(1); // 获取括号内的变量名
|
||||
String jdbcType = matcher.group(2); // 获取括号内的变量名
|
||||
if(FuncBase.isEmpty(jdbcType)){
|
||||
matcher.appendReplacement(sb, "#{" + variableName + "}");
|
||||
}else{
|
||||
matcher.appendReplacement(sb, "#{" + variableName+","+jdbcType + "}");
|
||||
}
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
|
||||
return sb.toString();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析多租户sql
|
||||
*
|
||||
* @param sql
|
||||
* @return
|
||||
*/
|
||||
public static String parseTenantSql(String sql) throws JSQLParserException {
|
||||
Statement statement = CCJSqlParserUtil.parse(fomatSql(sql));
|
||||
if (statement instanceof Select) {//查询
|
||||
Select select = (Select) statement;
|
||||
JeeLowCodeTenantParse.processSelect(select);
|
||||
} else if (statement instanceof Update) {//修改
|
||||
Update update = (Update) statement;
|
||||
JeeLowCodeTenantParse.processUpdate(update);
|
||||
} else if (statement instanceof Delete) {//删除
|
||||
Delete delete = (Delete) statement;
|
||||
JeeLowCodeTenantParse.processDelete(delete);
|
||||
} else if (statement instanceof Insert) {//新增
|
||||
Insert insert = (Insert) statement;
|
||||
JeeLowCodeTenantParse.processInsert(insert);
|
||||
}
|
||||
String new_sql = statement.toString();
|
||||
return resetSql(new_sql);
|
||||
}
|
||||
|
||||
//获得租户编号
|
||||
public static Long getTenantId() {
|
||||
return TENANT_ID.get();
|
||||
}
|
||||
|
||||
//设置租户
|
||||
public static void setTenantId(Long tenantId) {
|
||||
TENANT_ID.set(tenantId);
|
||||
}
|
||||
|
||||
//设置当前线程是否忽略多租户
|
||||
public static void setIgnore(Boolean ignore) {
|
||||
IGNORE.set(ignore);
|
||||
}
|
||||
|
||||
//当前是否忽略租户
|
||||
public static boolean isIgnore() {
|
||||
return Boolean.TRUE.equals(IGNORE.get());
|
||||
}
|
||||
|
||||
//忽略列表
|
||||
public static Set<String> getIgnoreTables() {
|
||||
return IGNORE_TABLES;
|
||||
}
|
||||
|
||||
//忽略列表
|
||||
public static Set<String> getIgnoreLikeTables() {
|
||||
return IGNORE_LIKE_TABLES;
|
||||
}
|
||||
|
||||
|
||||
public static Set<String> getIGNORE_URLS() {
|
||||
return IGNORE_URLS;
|
||||
}
|
||||
|
||||
public void setIGNORE_URLS(Set<String> IGNORE_URLS) {
|
||||
this.IGNORE_URLS = IGNORE_URLS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化-排除表
|
||||
*
|
||||
* @param tables
|
||||
*/
|
||||
public static void initIgnoreTables(Set<String> tables) {
|
||||
if (IGNORE_TABLES != null && IGNORE_TABLES.size() > 0) {//只需要初始化一次
|
||||
return;
|
||||
}
|
||||
for (String table : tables) {
|
||||
if (table.contains("*")) {
|
||||
IGNORE_LIKE_TABLES.add(table);
|
||||
}else {
|
||||
IGNORE_TABLES.add(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化-排除url
|
||||
*
|
||||
* @param urls
|
||||
*/
|
||||
public static void initIgnoreUrl(Set<String> urls) {
|
||||
if (IGNORE_URLS != null && IGNORE_URLS.size() > 0) {//只需要初始化一次
|
||||
return;
|
||||
}
|
||||
IGNORE_URLS=urls;
|
||||
}
|
||||
|
||||
//清空当前线程
|
||||
public static void clearIgnore() {
|
||||
IGNORE.remove();
|
||||
}
|
||||
|
||||
|
||||
//清空当前线程
|
||||
public static void clear() {
|
||||
TENANT_ID.remove();
|
||||
IGNORE.remove();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.jeelowcode.framework.tenant.auto.JeeLowCodeTenantAutoConfiguration
|
||||
Reference in New Issue
Block a user