SAAS multi-tenant data logical isolation

基于Mybatis 的SAAS应用多租户数据逻辑隔离

package com.opencloud.common.interceptor;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;

/**
*多租户数据逻辑隔离
* auth:breka
Time *: 2019-11-01
* realize data isolation between logic multi-tenant through sql interception mechanism, we need to add tentant_id (Long) field in the data table, and need to add logic to delete is_delete (tinyint) field
* need mybatis- config.xml file to add this plug, and set the table need to be isolated and need to set the table tombstone
* <plugin Interceptor = "com.tianque.saas.platform.filter.MybatisSaasInterceptor">
* <-! tenant data isolation table ->
* <Property name = "tentant_filte_tables" value = "| uc_sys_organization | uc_sys_position | uc_sys_role | uc_sys_user |" />
* <- tombstone data sheet ->!
* <Property name = "is_deleted_tables" value = "| uc_sys_organization | uc_sys_position | uc_sys_role | uc_sys_user | "/>
* </ plugin>
* /
@Component
@Intercepts ({
@Signature (
type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class
})
})
public class MybatisSaasInterceptor implements Interceptor {

public static final String SAAS_SQL_SPACE = " ";
public static final String SAAS_SQL_SPACE_EQUES_SPACE = " = ";
public static final String SAAS_SQL_EQUS = "=";
public static final String SAAS_SQL_SPACE_DOT_SPACE = " , ";
public static final String SAAS_SQL_DOT = ",";
public static final String SAAS_SQL_GOT_N="\n";
public static final String SAAS_SQL_GOT_T="\t";
public static final String SAAS_SQL_COL="|";
public static final String SAAS_SQL_SELECT = "select";
public static final String SAAS_SQL_FROM = "from";
public static final String SAAS_SQL_JOIN = "join";
public static final String SAAS_SQL_WHERE = "where";
public static final String SAAS_SQL_WHERE_SPACE = "where ";
public static final String SAAS_SQL_RIGNT_SIGN = ")";
public static final String SAAS_SQL_SPACE_RIGNT_SIGN = " )";
public static final String SAAS_SQL_LEFT_SIGN = "(";
public static final String SAAS_SQL_LEFT_SIGN_SPACE = "( ";
public static final String SAAS_SQL_INSERT = "insert";
public static final String SAAS_SQL_UPDATE = "update";
public static final String SAAS_SQL_UPDATE_SPACE = "update ";
public static final String SAAS_SQL_CREATE_USER = "create_user";
public static final String SAAS_SQL_CREATE_USER_DOT = "create_user,";
public static final String SAAS_SQL_CREATE_DATE = "create_date";
public static final String SAAS_SQL_CREATE_DATE_DOT = "create_date,";
public static final String SAAS_SQL_UPDATE_USER = "update_user";
public static final String SAAS_SQL_UPDATE_DATE = "update_date";
public static final String SAAS_SQL_DELETE = "delete";
public static final String SAAS_SQL_TENTANT_ID = "tentant_id";
public static final String SAAS_SQL_TENTANT_ID_EQUS = "tentant_id=";
public static final String SAAS_SQL_SAPCE_TENTANT_ID_EQUS = " tentant_id=";
public static final String SAAS_SQL_TENTANT_ID_DOT = "tentant_id,";
public static final String SAAS_SQL_DOT_TENTANT_ID_EQUS = ".tentant_id=";
public static final String SAAS_SQL_SPACE_AND_SPACE_TENTANT_ID_EQUS = " and tentant_id=";
public static final String SAAS_SQL_DOT_IS_DELETED_EQUS_ZERO_AND_SPACE = ".is_deleted=0 and ";
public static final String SAAS_SQL_IS_DELETED_EQUS_ZERO_AND_SPACE = "is_deleted=0 and ";
public static final String SAAS_SQL_AS = "as";
public static final String SAAS_SQL_SPACE_SET_SPACE = " set ";
public static final String SAAS_SQL_VALUES = "values";
public static final String SAAS_SQL_SPACE_AND_SPANCE = " and ";
public static final String SAAS_SQL_UPDATE_USER_EQUS_TAG = "update_user='";
public static final String SAAS_SQL_UPDATE_DATE_EQUS_NOW_SIGN_DOT = "update_date=now(),";
public static final String SAAS_SQL_NOW_SIGN_DOT = "now(),";
public static final String SAAS_SQL_SEMEGENT_ONE="',update_date=now() where";
public static final String SAAS_SQL_SEMEGENT_TOW=" set is_deleted=1,update_user='";
public static final String SAAS_SQL_SQL="sql";
public static final String SAAS_SQL_DELEGATE="delegate.mappedStatement";


private static String tentant_filte_tables=""; //配置租户表
private static String is_deleted_tables="";// Configure tombstone table

@Override
Intercept Object public (the Invocation Invocation) throws the Throwable {
String the userName = ""; // Sign current session user name
Long tentantId = 0l; // current tenant signed session Id

// current tenant id value attached to the user session with the user name value attached
IF // (!. ThreadVariable.getSession () = null && ThreadVariable.getSession () getTentantId ()> 0)
// {
// the userName = ThreadVariable.getSession (), getUserName ();.
// tentantId ThreadVariable.getSession = (). getTentantId ();
//}


statementHandler statementHandler = (statementHandler) invocation.getTarget ();
MetaObject as Metaobject = MetaObject.forObject (statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new new DefaultReflectorFactory ());
// first intercepted RoutingStatementHandler, which has a variable StatementHandler delegate type, which is BaseStatementHandler implementation class, and then on to the member variable BaseStatementHandler mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue (SAAS_SQL_DELEGATE);
// ID mapper is performed the full path name of the method, such as com.uv.dao.UserMapper.insertUser
String ID = mappedStatement.getId ();
// SQL statement type SELECT, Delete, INSERT, Update
. sqlCommandType String = mappedStatement.getSqlCommandType () toString ();
boundSql = statementHandler.getBoundSql boundSql ();

// get the original sql statement
String sql = boundSql.getSql ();
// get tenant background data isolation Sql
String NewSQL = this.getSaasSql (sql, tentantId, userName);

// sql statement modified by reflection
Field field = boundSql.getClass().getDeclaredField(SAAS_SQL_SQL);
field.setAccessible(true);
field.set(boundSql, newSql);
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}

}

@Override
public void setProperties(Properties properties) {
//初始化租户数据隔离表与逻辑删除表
tentant_filte_tables = (String) properties.get("tentant_filte_tables");
is_deleted_tables = (String) properties.get("is_deleted_tables");
System.out.println("tentant_filte_tables:" + tentant_filte_tables + " is_deleted_tables:" + is_deleted_tables);
}

public int tentant_filte_tables_indexof(String tableName)
{
return tentant_filte_tables.indexOf(SAAS_SQL_COL+tableName+SAAS_SQL_COL);
}
private int is_deleted_tables_indexof(String tableName)
{
return is_deleted_tables.indexOf(SAAS_SQL_COL+tableName+SAAS_SQL_COL);
}
private String getSaasSql(String sql,Long tentantId,String userName)
{
sql=sql.toLowerCase().trim();
sql=sql.replace(SAAS_SQL_GOT_N,SAAS_SQL_SPACE)
.replace(SAAS_SQL_GOT_T,SAAS_SQL_SPACE)
.replace(SAAS_SQL_EQUS,SAAS_SQL_SPACE_EQUES_SPACE)
.replace(SAAS_SQL_DOT,SAAS_SQL_SPACE_DOT_SPACE)
.replace(SAAS_SQL_LEFT_SIGN_SPACE,SAAS_SQL_LEFT_SIGN)
.replace(SAAS_SQL_SPACE_RIGNT_SIGN,SAAS_SQL_RIGNT_SIGN)
.replaceAll(" +",SAAS_SQL_SPACE);

String newSql = sql;
String[] arrSql=sql.split(SAAS_SQL_SPACE);
String sqlCommandType =arrSql[0];
if(sqlCommandType.equals(SAAS_SQL_SELECT))
{
//region 查询SQL 租户与逻辑删除表替换
for(int i=1;i<arrSql.length;i++)
{
if(arrSql[i-1].equals(SAAS_SQL_FROM)||arrSql[i-1].equals(SAAS_SQL_JOIN))
{
//from where
String tableName=arrSql[i];
String smallName="";
if(tableName.indexOf(SAAS_SQL_LEFT_SIGN)==0)
continue;

if(tentant_filte_tables_indexof(tableName)>-1||is_deleted_tables_indexof(tableName)>-1)
{
String strWhere="";
//region单表查询
if(arrSql[i+1].equals(SAAS_SQL_WHERE))
{
if(tentant_filte_tables.indexOf(tableName)>-1)
strWhere+=SAAS_SQL_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;
if(is_deleted_tables.indexOf(tableName)>-1)
strWhere+=SAAS_SQL_IS_DELETED_EQUS_ZERO_AND_SPACE;
arrSql[i+1]=SAAS_SQL_WHERE_SPACE+strWhere;
i++;
continue;
}
if(arrSql[i+2].equals(SAAS_SQL_WHERE))
{
smallName=arrSql[i+1];
if(tentant_filte_tables.indexOf(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;
if(is_deleted_tables.indexOf(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_IS_DELETED_EQUS_ZERO_AND_SPACE;
arrSql[i+2]=SAAS_SQL_WHERE_SPACE+strWhere;
i=i+2;
continue;
}
//endregion

//region多表查询
if(arrSql[i+1].equals(SAAS_SQL_AS))
i=i+1;
smallName=arrSql[i+1];
if(tentant_filte_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;
if(is_deleted_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_IS_DELETED_EQUS_ZERO_AND_SPACE;
if(arrSql[i+2].equals(SAAS_SQL_DOT))
{
//region多表查询3表
i=i+3;
tableName=arrSql[i];
if(tentant_filte_tables_indexof(tableName)>-1||is_deleted_tables_indexof(tableName)>-1) {
if(arrSql[i+1].equals(SAAS_SQL_AS))
i=i+1;
smallName=arrSql[i+1];
if(tentant_filte_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;
if(is_deleted_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_IS_DELETED_EQUS_ZERO_AND_SPACE;
}
if(arrSql[i+2].equals(SAAS_SQL_DOT))
{
//多表查询3表
i=i+3;
tableName=arrSql[i];
if(tentant_filte_tables_indexof(tableName)>-1||is_deleted_tables_indexof(tableName)>-1) {
if(arrSql[i+1].equals(SAAS_SQL_AS))
i=i+1;
smallName=arrSql[i+1];
if(tentant_filte_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;
if(is_deleted_tables_indexof(tableName)>-1)
strWhere+=smallName+SAAS_SQL_DOT_IS_DELETED_EQUS_ZERO_AND_SPACE;
}
}
//endregion
}
//endregion
for(int j=i;j<arrSql.length;j++)
{
if(arrSql[j].indexOf(SAAS_SQL_WHERE)==0)
{
arrSql[j]=SAAS_SQL_WHERE_SPACE+strWhere +SAAS_SQL_SPACE+arrSql[j].replace(SAAS_SQL_WHERE,SAAS_SQL_SPACE);
}
}
}
}
}
NewSQL = StringUtils.join (arrSql, SAAS_SQL_SPACE);
// endregion
}
the else IF (sqlCommandType.equals (SAAS_SQL_INSERT))
{
// Region new tenant environment SQL table with Added Added logic, non-insertion query
if (sql. the indexOf (SAAS_SQL_SELECT) == -. 1) {
String strTags = "";
String strValues = "";
IF (sql.indexOf (SAAS_SQL_CREATE_USER) == -1) {
// add the default created
strTags + = SAAS_SQL_CREATE_USER_DOT;
strValues + = " '" + + the userName "',";
}
IF (SQL.indexOf(SAAS_SQL_CREATE_DATE) == -1) {
// default to create time
strTags + = SAAS_SQL_CREATE_DATE_DOT;
strValues + = SAAS_SQL_NOW_SIGN_DOT;
}
String tableName = arrSql [2]; // current table name
IF (tentant_filte_tables_indexof (tableName)> -1) {
// tenants filter table, no current Add the rental inserted Id add
IF (sql.indexOf (SAAS_SQL_TENTANT_ID) == -1) {
strTags + = SAAS_SQL_TENTANT_ID_DOT;
strValues + + = tentantId SAAS_SQL_DOT;
}
}
arrSql [. 3] + = SAAS_SQL_LEFT_SIGN arrSql strTags + [. 3] .replace (SAAS_SQL_LEFT_SIGN, "");
for (int i = 1; i < arrSql.length; i++) {
if (arrSql[i].indexOf(SAAS_SQL_LEFT_SIGN) == -1)
continue;
;
if (arrSql[i - 1].equals(SAAS_SQL_VALUES)) {
arrSql[i] = SAAS_SQL_LEFT_SIGN + strValues + arrSql[i].replace(SAAS_SQL_LEFT_SIGN, "");
}
//支持批量插入
if (arrSql[i - 1].equals(SAAS_SQL_DOT) && arrSql[i - 2].indexOf(SAAS_SQL_RIGNT_SIGN) > -1) {
arrSql[i] = SAAS_SQL_LEFT_SIGN + strValues + arrSql[i].replace(SAAS_SQL_LEFT_SIGN, "");
}
}
}
= StringUtils.join NewSQL (arrSql, SAAS_SQL_SPACE);
// endregion
}
the else IF (sqlCommandType.equals (SAAS_SQL_UPDATE))
{
// update the SQL Region, with the tenant Updated table, updated replacement
String strUpdate = SAAS_SQL_SPACE;
IF (SQL. the indexOf (SAAS_SQL_UPDATE_USER) == -. 1)
{
IF (strUpdate.length ()>. 1)
strUpdate + = SAAS_SQL_DOT;
strUpdate the userName + + + = SAAS_SQL_UPDATE_USER_EQUS_TAG " '"; // default Add Modifier
}
IF (sql.indexOf (SAAS_SQL_UPDATE_DATE) == -1)
{
IF (strUpdate.length ()>. 1)
strUpdate + = SAAS_SQL_DOT;
strUpdate + = SAAS_SQL_UPDATE_DATE_EQUS_NOW_SIGN_DOT; // default to the modified
}
IF (strUpdate.length ()>. 1)
{
int = indexSet sql.indexOf (SAAS_SQL_SPACE_SET_SPACE);
NewSQL = sql.replace (SAAS_SQL_SPACE_SET_SPACE, SAAS_SQL_SPACE_SET_SPACE strUpdate +);
}

for (int I . 1 =; I <arrSql.length; I ++)
{
IF (arrSql [-I. 1] .equals (SAAS_SQL_UPDATE))
{
String tableName = arrSql [I]; // current table name
if (tentant_filte_tables_indexof (tableName)> - 1)
{
// tenants filter table, the update condition is not added conditions tenant ID, the user ID adding conditions
IF (sql.indexOf (SAAS_SQL_TENTANT_ID) == -. 1)
{
NewSQL + + = SAAS_SQL_SPACE_AND_SPACE_TENTANT_ID_EQUS tentantId;
BREAK;
}
}
}
}
// endregion
}
the else IF (sqlCommandType.equals (SAAS_SQL_DELETE))
{
// Region SQL delete logical table tenant drop table logical substitutions;
for (int I =. 1; I <arrSql.length; I ++)
{
IF (arrSql [-I. 1] .equals (SAAS_SQL_FROM))
{
String tableName = arrSql [I]; // current table name
if(is_deleted_tables_indexof(tableName)>-1)
{
//改成逻辑删除
newSql=SAAS_SQL_UPDATE_SPACE+tableName+SAAS_SQL_SEMEGENT_TOW+userName+SAAS_SQL_SEMEGENT_ONE;
if(tentant_filte_tables_indexof(tableName)>-1)
newSql+=SAAS_SQL_SAPCE_TENTANT_ID_EQUS+tentantId+SAAS_SQL_SPACE_AND_SPANCE;//租户表
int indexWhere=sql.indexOf(SAAS_SQL_WHERE)+5;
newSql+=sql.substring(indexWhere,sql.length());
break;
}
else
{
//租户物理删除表
if(tentant_filte_tables_indexof(tableName)>-1)
{
if(sql.indexOf(SAAS_SQL_TENTANT_ID)==-1)
{
newSql=sql+SAAS_SQL_SPACE_AND_SPACE_TENTANT_ID_EQUS+tentantId;//添加租户ID条件限制
break;
}
}
}
}
}
//endregion
}
newSql=newSql.replace(SAAS_SQL_SPACE_DOT_SPACE,SAAS_SQL_DOT)
.replace(SAAS_SQL_SPACE_EQUES_SPACE,SAAS_SQL_EQUS)
.replaceAll(" +",SAAS_SQL_SPACE);
System.out.println("---------当前租户:"+ tentantId +"---------"+newSql);
return newSql;
}


}

Guess you like

Origin www.cnblogs.com/breka/p/11996848.html