¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. |
| | | * |
| | | * Redistribution and use in source and binary forms, with or without |
| | | * modification, are permitted provided that the following conditions are met: |
| | | * |
| | | * Redistributions of source code must retain the above copyright notice, |
| | | * this list of conditions and the following disclaimer. |
| | | * Redistributions in binary form must reproduce the above copyright |
| | | * notice, this list of conditions and the following disclaimer in the |
| | | * documentation and/or other materials provided with the distribution. |
| | | * Neither the name of the dreamlu.net developer nor the names of its |
| | | * contributors may be used to endorse or promote products derived from |
| | | * this software without specific prior written permission. |
| | | * Author: Chill åºéª (smallchill@163.com) |
| | | */ |
| | | package org.springblade.core.tenant; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; |
| | | import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; |
| | | import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | 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.EqualsTo; |
| | | 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 org.springblade.core.secure.utils.AuthUtil; |
| | | import org.springblade.core.tool.utils.CollectionUtil; |
| | | import org.springblade.core.tool.utils.StringPool; |
| | | |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * ç§æ·æ¦æªå¨ |
| | | * |
| | | * @author Chill |
| | | */ |
| | | @Data |
| | | @ToString(callSuper = true) |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class BladeTenantInterceptor extends TenantLineInnerInterceptor { |
| | | |
| | | /** |
| | | * ç§æ·å¤çå¨ |
| | | */ |
| | | private TenantLineHandler tenantLineHandler; |
| | | /** |
| | | * ç§æ·é
ç½®æä»¶ |
| | | */ |
| | | private BladeTenantProperties tenantProperties; |
| | | /** |
| | | * è¶
管éè¦å¯ç¨ç§æ·è¿æ»¤ç表 |
| | | */ |
| | | private List<String> adminTenantTables = Arrays.asList("blade_top_menu", "blade_dict_biz"); |
| | | |
| | | @Override |
| | | public void setTenantLineHandler(TenantLineHandler tenantLineHandler) { |
| | | super.setTenantLineHandler(tenantLineHandler); |
| | | this.tenantLineHandler = tenantLineHandler; |
| | | } |
| | | |
| | | @Override |
| | | protected void processInsert(Insert insert, int index, String sql, Object obj) { |
| | | // æªå¯ç¨ç§æ·å¢å¼ºï¼å使ç¨åçé»è¾ |
| | | if (!tenantProperties.getEnhance()) { |
| | | super.processInsert(insert, index, sql, obj); |
| | | return; |
| | | } |
| | | if (tenantLineHandler.ignoreTable(insert.getTable().getName())) { |
| | | // è¿æ»¤éåºæ§è¡ |
| | | return; |
| | | } |
| | | List<Column> columns = insert.getColumns(); |
| | | if (CollectionUtils.isEmpty(columns)) { |
| | | // é对ä¸ç»ååçinsert ä¸å¤ç |
| | | return; |
| | | } |
| | | String tenantIdColumn = tenantLineHandler.getTenantIdColumn(); |
| | | if (columns.stream().map(Column::getColumnName).anyMatch(i -> i.equals(tenantIdColumn))) { |
| | | // é对已ç»åºç§æ·åçinsert ä¸å¤ç |
| | | return; |
| | | } |
| | | columns.add(new Column(tenantIdColumn)); |
| | | |
| | | // fixed gitee pulls/141 duplicate update |
| | | List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList(); |
| | | if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) { |
| | | EqualsTo equalsTo = new EqualsTo(); |
| | | equalsTo.setLeftExpression(new StringValue(tenantIdColumn)); |
| | | equalsTo.setRightExpression(tenantLineHandler.getTenantId()); |
| | | duplicateUpdateColumns.add(equalsTo); |
| | | } |
| | | |
| | | Select select = insert.getSelect(); |
| | | if (select != null) { |
| | | this.processInsertSelect(select.getSelectBody()); |
| | | } else if (insert.getItemsList() != null) { |
| | | // fixed github pull/295 |
| | | ItemsList itemsList = insert.getItemsList(); |
| | | if (itemsList instanceof MultiExpressionList) { |
| | | ((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(tenantLineHandler.getTenantId())); |
| | | } else { |
| | | ((ExpressionList) itemsList).getExpressions().add(tenantLineHandler.getTenantId()); |
| | | } |
| | | } else { |
| | | throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¤ç PlainSelect |
| | | */ |
| | | @Override |
| | | protected void processPlainSelect(PlainSelect plainSelect) { |
| | | //#3087 github |
| | | List<SelectItem> selectItems = plainSelect.getSelectItems(); |
| | | if (CollectionUtils.isNotEmpty(selectItems)) { |
| | | selectItems.forEach(this::processSelectItem); |
| | | } |
| | | |
| | | // å¤ç where ä¸çåæ¥è¯¢ |
| | | Expression where = plainSelect.getWhere(); |
| | | processWhereSubSelect(where); |
| | | |
| | | // å¤ç fromItem |
| | | FromItem fromItem = plainSelect.getFromItem(); |
| | | List<Table> list = processFromItem(fromItem); |
| | | List<Table> mainTables = new ArrayList<>(list); |
| | | |
| | | // å¤ç join |
| | | List<Join> joins = plainSelect.getJoins(); |
| | | if (CollectionUtils.isNotEmpty(joins)) { |
| | | mainTables = processJoins(mainTables, joins); |
| | | } |
| | | |
| | | // 彿 mainTable æ¶ï¼è¿è¡ where æ¡ä»¶è¿½å |
| | | if (CollectionUtils.isNotEmpty(mainTables) && !doTenantFilters(mainTables)) { |
| | | plainSelect.setWhere(builderExpression(where, mainTables)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * update è¯å¥å¤ç |
| | | */ |
| | | @Override |
| | | protected void processUpdate(Update update, int index, String sql, Object obj) { |
| | | final Table table = update.getTable(); |
| | | if (tenantLineHandler.ignoreTable(table.getName())) { |
| | | // è¿æ»¤éåºæ§è¡ |
| | | return; |
| | | } |
| | | if (doTenantFilter(table.getName())) { |
| | | // è¿æ»¤éåºæ§è¡ |
| | | return; |
| | | } |
| | | update.setWhere(this.andExpression(table, update.getWhere())); |
| | | } |
| | | |
| | | /** |
| | | * delete è¯å¥å¤ç |
| | | */ |
| | | @Override |
| | | protected void processDelete(Delete delete, int index, String sql, Object obj) { |
| | | final Table table = delete.getTable(); |
| | | if (tenantLineHandler.ignoreTable(table.getName())) { |
| | | // è¿æ»¤éåºæ§è¡ |
| | | return; |
| | | } |
| | | if (doTenantFilter(table.getName())) { |
| | | // è¿æ»¤éåºæ§è¡ |
| | | return; |
| | | } |
| | | delete.setWhere(this.andExpression(table, delete.getWhere())); |
| | | } |
| | | |
| | | /** |
| | | * delete update è¯å¥ where å¤ç |
| | | */ |
| | | @Override |
| | | protected BinaryExpression andExpression(Table table, Expression where) { |
| | | //è·å¾æ¡ä»¶è¡¨è¾¾å¼ |
| | | EqualsTo equalsTo = new EqualsTo(); |
| | | Expression leftExpression = this.getAliasColumn(table); |
| | | Expression rightExpression = tenantLineHandler.getTenantId(); |
| | | // è¥æ¯è¶
管åä¸è¿è¡è¿æ»¤ |
| | | if (doTenantFilter(table.getName())) { |
| | | leftExpression = rightExpression = new StringValue(StringPool.ONE); |
| | | } |
| | | equalsTo.setLeftExpression(leftExpression); |
| | | equalsTo.setRightExpression(rightExpression); |
| | | if (null != where) { |
| | | if (where instanceof OrExpression) { |
| | | return new AndExpression(equalsTo, new Parenthesis(where)); |
| | | } else { |
| | | return new AndExpression(equalsTo, where); |
| | | } |
| | | } |
| | | return equalsTo; |
| | | } |
| | | |
| | | /** |
| | | * å¢å¼ºæä»¶ä½¿è¶
级管çåå¯ä»¥çå°ææç§æ·æ°æ® |
| | | */ |
| | | @Override |
| | | protected Expression builderExpression(Expression currentExpression, List<Table> tables) { |
| | | // 没æè¡¨éè¦å¤çç´æ¥è¿å |
| | | if (CollectionUtils.isEmpty(tables)) { |
| | | return currentExpression; |
| | | } |
| | | // ç§æ· |
| | | Expression tenantId = tenantLineHandler.getTenantId(); |
| | | // æé æ¯å¼ è¡¨çæ¡ä»¶ |
| | | List<EqualsTo> equalsTos = tables.stream() |
| | | // ç§æ·å¿½ç¥è¡¨ |
| | | .filter(x -> !tenantLineHandler.ignoreTable(x.getName())) |
| | | // è¶
管忽ç¥è¡¨ |
| | | .filter(x -> !doTenantFilter(x.getName())) |
| | | .map(item -> new EqualsTo(getAliasColumn(item), tenantId)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (CollectionUtils.isEmpty(equalsTos)) { |
| | | return currentExpression; |
| | | } |
| | | |
| | | // 注å
¥çè¡¨è¾¾å¼ |
| | | Expression injectExpression = equalsTos.get(0); |
| | | // 妿æå¤è¡¨ï¼åç¨ and è¿æ¥ |
| | | if (equalsTos.size() > 1) { |
| | | for (int i = 1; i < equalsTos.size(); i++) { |
| | | injectExpression = new AndExpression(injectExpression, equalsTos.get(i)); |
| | | } |
| | | } |
| | | |
| | | if (currentExpression == null) { |
| | | return injectExpression; |
| | | } |
| | | if (currentExpression instanceof OrExpression) { |
| | | return new AndExpression(new Parenthesis(currentExpression), injectExpression); |
| | | } else { |
| | | return new AndExpression(currentExpression, injectExpression); |
| | | } |
| | | |
| | | } |
| | | |
| | | private List<Table> processFromItem(FromItem fromItem) { |
| | | // å¤çæ¬å·æ¬èµ·æ¥çè¡¨è¾¾å¼ |
| | | 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); |
| | | mainTables.addAll(tables); |
| | | } else { |
| | | // å¤çä¸ fromItem |
| | | processOtherFromItem(fromItem); |
| | | } |
| | | return mainTables; |
| | | } |
| | | |
| | | /** |
| | | * å¤ç sub join |
| | | * |
| | | * @param subJoin subJoin |
| | | * @return Table subJoin ä¸ç主表 |
| | | */ |
| | | private List<Table> processSubJoin(SubJoin subJoin) { |
| | | List<Table> mainTables = new ArrayList<>(); |
| | | if (subJoin.getJoinList() != null) { |
| | | List<Table> list = processFromItem(subJoin.getLeft()); |
| | | mainTables.addAll(list); |
| | | mainTables = processJoins(mainTables, subJoin.getJoinList()); |
| | | } |
| | | return mainTables; |
| | | } |
| | | |
| | | /** |
| | | * å¤ç joins |
| | | * |
| | | * @param mainTables å¯ä»¥ä¸º null |
| | | * @param joins join éå |
| | | * @return List<Table> å³è¿æ¥æ¥è¯¢ç Table å表 |
| | | */ |
| | | private List<Table> processJoins(List<Table> mainTables, List<Join> joins) { |
| | | // join 表达å¼ä¸æç»ç主表 |
| | | Table mainTable = null; |
| | | // å½å join ç左表 |
| | | Table leftTable = null; |
| | | |
| | | if (mainTables == null) { |
| | | mainTables = new ArrayList<>(); |
| | | } else 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); |
| | | } |
| | | |
| | | if (joinTables != null) { |
| | | |
| | | // 妿æ¯éå¼å
è¿æ¥ |
| | | if (join.isSimple()) { |
| | | mainTables.addAll(joinTables); |
| | | continue; |
| | | } |
| | | |
| | | // å½å表æ¯å¦å¿½ç¥ |
| | | Table joinTable = joinTables.get(0); |
| | | |
| | | List<Table> onTables = null; |
| | | // 妿ä¸è¦å¿½ç¥ï¼ä¸æ¯å³è¿æ¥ï¼åè®°å½ä¸å½å表 |
| | | if (join.isRight()) { |
| | | mainTable = joinTable; |
| | | if (leftTable != null) { |
| | | onTables = Collections.singletonList(leftTable); |
| | | } |
| | | } else if (join.isLeft()) { |
| | | onTables = Collections.singletonList(joinTable); |
| | | } else if (join.isInner()) { |
| | | if (mainTable == null) { |
| | | onTables = Collections.singletonList(joinTable); |
| | | } else { |
| | | onTables = Arrays.asList(mainTable, joinTable); |
| | | } |
| | | mainTable = null; |
| | | } |
| | | |
| | | mainTables = new ArrayList<>(); |
| | | if (mainTable != null) { |
| | | 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)); |
| | | join.setOnExpressions(onExpressions); |
| | | leftTable = joinTable; |
| | | 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 (CollectionUtils.isEmpty(currentTableList)) { |
| | | onExpressions.add(originOnExpression); |
| | | } else { |
| | | onExpressions.add(builderExpression(originOnExpression, currentTableList)); |
| | | } |
| | | } |
| | | join.setOnExpressions(onExpressions); |
| | | } |
| | | leftTable = joinTable; |
| | | } else { |
| | | processOtherFromItem(joinItem); |
| | | leftTable = null; |
| | | } |
| | | } |
| | | |
| | | return mainTables; |
| | | } |
| | | |
| | | /** |
| | | * 夿å½åæä½æ¯å¦éè¦è¿è¡è¿æ»¤ |
| | | * |
| | | * @param tableName 表å |
| | | */ |
| | | public boolean doTenantFilter(String tableName) { |
| | | return AuthUtil.isAdministrator() && !adminTenantTables.contains(tableName); |
| | | } |
| | | |
| | | /** |
| | | * 夿å½åæä½æ¯å¦éè¦è¿è¡è¿æ»¤ |
| | | * |
| | | * @param tables 表å |
| | | */ |
| | | public boolean doTenantFilters(List<Table> tables) { |
| | | List<String> tableNames = tables.stream().map(Table::getName).collect(Collectors.toList()); |
| | | return AuthUtil.isAdministrator() && !CollectionUtil.containsAny(adminTenantTables, tableNames); |
| | | } |
| | | |
| | | } |