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="\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="";     //配置逻辑删除表

    @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;    }

}

原文地址:https://www.cnblogs.com/breka/p/11996848.html

时间: 2024-11-19 23:02:38

SAAS多租户数据逻辑隔离的相关文章

saas系统多租户数据隔离的实现(一)数据隔离方案

0. 前言 前几天跟朋友聚会的时候,朋友说他们公司准备自己搞一套saas系统,以实现多个第三方平台的业务接入需求.聊完以后,实在手痒难耐,于是花了两天时间自己实现了两个saas系统多租户数据隔离实现方案.俗话说“独乐乐不如众乐乐”,所以我把我的“研究成果”写出来,让大家乐呵乐呵. 在分享我的研究成果之前,我们先了解一下相关的定义吧.如果对这部分内容熟悉的同学,可以直接略过. 1. 什么是saas系统 引用百度百科上面的描述, “SaaS平台是运营saas软件的平台.SaaS提供商为企业搭建信息化

SaaS多租户模式数据存储方案比较

云计算多租户几乎用于所有软件即服务 (Software as a Service, SaaS) 应用程序,因为计算资源是可伸缩的,而且这些资源的分配由实际使用决定.话虽如此,用户可以通过 Internet 访问多种类型的 SaaS 应用程序,从小的基于 Internet 的小部件到大型企业软件应用程序.根据存储在企业网络之外的软件供应商的基础架构上的数据不同,安全需求也在不断增长.应用程序需要多租户是有许多原因的,其中最明显的原因就是成本:在大多数情况下,为每个客户增加几个服务器和一个数据库是远

SaaS多租户模式数据存储方案

  云计算多租户几乎用于所有软件即服务 (Software as a Service, SaaS) 应用程序,因为计算资源是可伸缩的,而且这些资源的分配由实际使用决定.话虽如此,用户可以通过 Internet 访问多种类型的 SaaS 应用程序,从小的基于 Internet 的小部件到大型企业软件应用程序.根据存储在企业网络之外的软件供应商的基础架构上的数据不同,安全需求也在不断增长.应用程序需要多租户是有许多原因的,其中最明显的原因就是成本:在大多数情况下,为每个客户增加几个服务器和一个数据库

EEPlat PaaS中的多租户数据隔离模式

EEPlat PaaS支持三种租户的数据隔离技术:Sparce Column.tenantId字段隔离.每个租户独立数据库. 1)Sparce Column,和Salesforce Appforce 类似,主要通过一个通用表来存放所有自定义信息,里面有租户字段和很多统一的数据栏位(比如500个).EEPlat PaaS平台通过租户分区的方式进行租户数据的查询优化. 和Appforce 不同的是,EEPlat PaaS平台产品提供了2层的元数据抽象,EEPlat PaaS平台产品中业务元数据和租户

JeeSite 4.x SAAS 多租户技术设计方案

SaaS 是 Software-as-a-Service(软件即服务)的简称,从技术角度上可称之为 "多租户技术或称多重租赁技术".它与 "按需软件.应用服务提供商.托管软件" 所具有相似的含义.它是一种通过互联网提供软件的模式,厂商将应用软件统一部署在自己的服务器上,客户可以根据自己实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得厂商提供的服务.用户不用再购买软件,而改用向提供商租用基于Web的软件,来管

Openflow流表应用测试--逻辑隔离

1.搭建拓扑 搭建了三层的二叉树结构网络,开启SimpleSwitch3.py无法完成pingall联通测试,于是将拓扑结构更改为简单的line,四个交换机(OF13)连成一线,每个交换机下挂两个主机,依次h1--h8 s1 = net.addSwitch('s1', cls=OVSKernelSwitch, protocols='OpenFlow13', mac='00:00:00:00:00:11')    s2 = net.addSwitch('s2', cls=OVSKernelSwit

微软SaaS多租户解决方案

微软SaaS多租户解决方案 docs.microsoft.com/en-us/azure/sql-database/saas-tenancy-app-design-patterns https://docs.microsoft.com/en-us/azure/sql-database/saas-tenancy-app-design-patterns https://github.com/TarsNET https://github.com/ElderJames https://github.co

saas 系统租户个性化域名&amp;&amp;租户绑定自己域名的解决方案

实际的需求就类似github 的自定义page 1. 个性化域名 github 实现原理就是用户个性化域名使用泛域名解析,这个比较简单,大部分域名提供商都可以解决 具体操作不用赘述 使用nginx 的配置比较简单 配置如下: server { listen 8080default; index index.html index.htm index.php; root html; location /{ root html; } location /app { root html; } locat

Oracle数据逻辑组件

概念: oracle的逻辑结构一般可以分成: 表空间(tablespace).段(segment).区(extent).数据块(data block).oracle由表空间组成,表空间由段组成,段由区组成,区由数据块组成. 分类: 表空间: 1.表空间是数据库中最大的逻辑单位,几个表空间组成了数据库. 2.表空间由一个或多个数据文件在物理上构成,一个数据文件只能属于一个表空间,这里通过数据文件位置的不同形成表空间在多个磁盘的分布.3.根据组成的段类型,表空间分成:数据段表空间.索引段表空间.临时