zoukankan      html  css  js  c++  java
  • SAAS多租户数据逻辑隔离

    基于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
    * 通过sql拦截机制实现多租户之间的数据逻辑隔离,需要先对数据表添加tentant_id(Long)字段以及需要添加逻辑删除的is_delete(tinyint)字段
    * 需要在mybatis-config.xml文件里添加此插件,并设置需要隔离的表与需要设置逻辑删除的表
    * <plugin interceptor="com.tianque.saas.platform.filter.MybatisSaasInterceptor">
    * <!--租户数据隔离表-->
    * <property name="tentant_filte_tables" value="|uc_sys_organization|uc_sys_position|uc_sys_role|uc_sys_user|"/>
    * <!--逻辑删除数据表-->
    * <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=" ";
    public static final String SAAS_SQL_GOT_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=""; //配置逻辑删除表

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    String userName="";//当前登入会话用户名
    Long tentantId=0l;//当前登入会话租户Id

    //当前会议用户的租户id附值与用户名附值
    // if(ThreadVariable.getSession()!=null&&ThreadVariable.getSession().getTentantId()>0)
    // {
    // userName=ThreadVariable.getSession().getUserName();
    // tentantId=ThreadVariable.getSession().getTentantId();
    // }


    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
    //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(SAAS_SQL_DELEGATE);
    //id为执行的mapper方法的全路径名,如com.uv.dao.UserMapper.insertUser
    String id = mappedStatement.getId();
    //sql语句类型 select、delete、insert、update
    String sqlCommandType = mappedStatement.getSqlCommandType().toString();
    BoundSql boundSql = statementHandler.getBoundSql();

    //获取到原始sql语句
    String sql = boundSql.getSql();
    //得到租户数据隔离后台的Sql
    String newSql=this.getSaasSql(sql,tentantId,userName);

    //通过反射修改sql语句
    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
    }
    else if(sqlCommandType.equals(SAAS_SQL_INSERT))
    {
    //region 新境SQL 租户表与添加人添加时间逻辑, 非查询插入
    if(sql.indexOf(SAAS_SQL_SELECT)==-1) {
    String strTags = "";
    String strValues = "";
    if (sql.indexOf(SAAS_SQL_CREATE_USER) == -1) {
    //默认添加创建人
    strTags += SAAS_SQL_CREATE_USER_DOT;
    strValues += "'" + userName + "',";
    }
    if (sql.indexOf(SAAS_SQL_CREATE_DATE) == -1) {
    //默认添加创建时间
    strTags += SAAS_SQL_CREATE_DATE_DOT;
    strValues += SAAS_SQL_NOW_SIGN_DOT;
    }
    String tableName = arrSql[2]; //当前表名
    if (tentant_filte_tables_indexof(tableName) > -1) {
    //是租户过滤表,当前没有添加租房Id插入则进行添加
    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 + strTags + arrSql[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, "");
    }
    }
    }
    newSql=StringUtils.join(arrSql, SAAS_SQL_SPACE);
    //endregion
    }
    else if(sqlCommandType.equals(SAAS_SQL_UPDATE))
    {
    //region 更新SQL,租户表与更新人、更新时间替换
    String strUpdate=SAAS_SQL_SPACE;
    if(sql.indexOf(SAAS_SQL_UPDATE_USER)==-1)
    {
    if(strUpdate.length()>1)
    strUpdate+=SAAS_SQL_DOT;
    strUpdate+=SAAS_SQL_UPDATE_USER_EQUS_TAG+userName+"'";//默认添加修改人
    }
    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;//默认添加修改时间
    }
    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]; //当前表名
    if(tentant_filte_tables_indexof(tableName)>-1)
    {
    //是租户过滤表,更新条件中没有添加租户ID条件限制,则添加用户ID条件限制
    if(sql.indexOf(SAAS_SQL_TENTANT_ID)==-1)
    {
    newSql+=SAAS_SQL_SPACE_AND_SPACE_TENTANT_ID_EQUS+tentantId;
    break;
    }
    }
    }
    }
    //endregion
    }
    else if(sqlCommandType.equals(SAAS_SQL_DELETE))
    {
    //region 删除SQL 租户表与逻辑删除表逻辑替换;
    for(int i=1;i<arrSql.length;i++)
    {
    if(arrSql[i-1].equals(SAAS_SQL_FROM))
    {
    String tableName=arrSql[i]; //当前表名
    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;
    }


    }
  • 相关阅读:
    GridView“gv_Info”激发了未处理的事件“RowEditing” “RowEditing”
    VS aspx页面在 设计视图 状态时 才可选用 工具 菜单下的 生成本地资源
    愿能与诸位关心的人及时保持互联
    [转]NOD32 與 無法將工作階段狀態要求送至工作階段狀態伺服器 NOD32与asp.net 状态服务
    [转]JavaScript:只能输入数字(IE、FF)
    勿以恶小而为之>致 被烟所包的程序员
    婚姻 一辈子的幸福厮守 请不要多拿彩礼和父母说事
    [文摘20090601]美国和中国老师讲灰姑娘的故事(差距啊~体现得淋漓尽致)
    多语言开发 之 通过基页类及Session 动态响应用户对语言的选择
    javascript的拖放(第1部分)
  • 原文地址:https://www.cnblogs.com/breka/p/11996848.html
Copyright © 2011-2022 走看看