天天看点

mybatis 拦截器+ sqlparser 实现系统数据隔离

前言:  因为系统要服务另外一家公司,那么问题就来了。1,采用分布式系统,推翻原有的系统架构。2,业务是一致的主要是不同的公司,互补影响,那么就要从数据上做隔离,想到了分库。总之,大概思考了一个小时,决定来使用mybatis拦截器尝试来实现。大致是在现有数据库所有的业务表都 加入  companyId 字段,以此字段来作为区分和拦截的由于之前没有使用过mybatis拦截器,那么一切都从0 开始百度+查询资料;网上有很多关于拦截器的资料信息,这里贴上两处本人认为有用的参考资料 A 拦截器原理出处:https://www.jianshu.com/p/7c7b8c2c985d

1  拦截器的四种代理模式, StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里我采用的是其中的一种模式用插件来实现对一级缓存的操作(禁用等等)。

2 拦截器模式定下,那么开始在spring-mybatis.xml 加入拦截器插件;

        <property name="plugins">

            <array>

                <bean class="com.wangzhixuan.commons.intercept.TestInterceptor"></bean> 

            </array>

        </property>

package com.wangzhixuan.commons.intercept;

import java.io.StringReader;

import java.lang.reflect.InvocationTargetException;

import java.sql.Connection;

import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;

import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.SqlCommandType;

import org.apache.ibatis.plugin.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Plugin;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.reflection.SystemMetaObject;

import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

import com.wangzhixuan.commons.sqlparser.SelectVisitorImpl;

import net.sf.jsqlparser.parser.CCJSqlParserManager;

import net.sf.jsqlparser.statement.select.Select;

@Intercepts({

        @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class, Integer.class})

})

public class TestInterceptor implements Interceptor {

    // 控制器本来就是单例,这样似乎更加合理

    protected Logger logger = LogManager.getLogger(getClass());

    private CCJSqlParserManager parserManager = new CCJSqlParserManager();

    @Override

    public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {

        if(invocation.getTarget() instanceof StatementHandler) {

            //逻辑代码区

            StatementHandler statementHandler = (StatementHandler)invocation.getTarget();

            MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);

            // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环

            // 可以分离出最原始的的目标类)

            while (metaStatementHandler.hasGetter("h")) {

                Object object = metaStatementHandler.getValue("h");

                metaStatementHandler = SystemMetaObject.forObject(object);

            }

            // 分离最后一个代理对象的目标类

            while (metaStatementHandler.hasGetter("target")) {

                Object object = metaStatementHandler.getValue("target");

                metaStatementHandler = SystemMetaObject.forObject(object);

            }

MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");

            //获取sql

            BoundSql boundSql = statementHandler.getBoundSql();

            String sql = boundSql.getSql();

            if(sql.contains("NEXTVAL")){ // 去掉子序列函数

            return invocation.proceed();

            }

            //获取方法id

String id = mappedStatement.getId();

if (id.contains("InventoryInoutMapper") || id.contains("selectSaleReportByGmtCreate")

|| id.contains("selectBiShipReportByGmtCreate") || id.contains("selectTransNoinventoryReport")

|| id.contains("queryPurStoDetails") || id.contains("queryTaskDailyCloseIds")) { // 剔除不需要走拦截器的方法

return invocation.proceed();

}

            //获得方法类型

            SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

            if ("SELECT".equals(sqlCommandType.toString())) {  // 拦截主要是针对查询语句的

             //如果是select就将sql转成SELECT对象  

            try {

            Select select = (Select)parserManager.parse(new StringReader(sql));

            //访问各个visitor  

                    select.getSelectBody().accept(new SelectVisitorImpl());  

                    //将增强后的sql放回

                    metaStatementHandler.setValue("delegate.boundSql.sql", select.toString());

                    return invocation.proceed();

            } catch(Throwable e) {

            logger.error("拦截器异常Start");

            logger.error(e);

            logger.error("拦截器异常End");

            }

            }

        }

        return invocation.proceed();

    }

    @Override

    public Object plugin(Object target) {

        //生成代理对象

        return Plugin.wrap(target, this);

    }

    @Override

    public void setProperties(Properties properties) {

    }

}

//下面是sqlpase的具体用法 (这里我采用的是访问者模式对于所涉及的业务表进行隔离)

package com.wangzhixuan.commons.sqlparser;

import java.util.HashSet;

import org.apache.shiro.SecurityUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.google.common.collect.Sets;

import com.wangzhixuan.commons.shiro.ShiroUser;

import net.sf.jsqlparser.expression.Expression;

import net.sf.jsqlparser.expression.LongValue;

import net.sf.jsqlparser.expression.operators.conditional.AndExpression;

import net.sf.jsqlparser.expression.operators.relational.Between;

import net.sf.jsqlparser.expression.operators.relational.EqualsTo;

import net.sf.jsqlparser.expression.operators.relational.GreaterThan;

import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;

import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;

import net.sf.jsqlparser.expression.operators.relational.LikeExpression;

import net.sf.jsqlparser.expression.operators.relational.MinorThan;

import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;

import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;

import net.sf.jsqlparser.schema.Column;

import net.sf.jsqlparser.schema.Table;

import net.sf.jsqlparser.statement.select.FromItemVisitor;

import net.sf.jsqlparser.statement.select.LateralSubSelect;

import net.sf.jsqlparser.statement.select.SubJoin;

import net.sf.jsqlparser.statement.select.SubSelect;

import net.sf.jsqlparser.statement.select.TableFunction;

import net.sf.jsqlparser.statement.select.ValuesList;

public class FromItemVisitorImpl implements FromItemVisitor {

    private static Logger logger = LoggerFactory.getLogger(FromItemVisitorImpl.class);

    // 声明增强条件

    private Expression enhancedCondition;

    // 定义需要数据隔离的表

    private static HashSet<String> interceptTableSet = Sets.newHashSet();

    static {

    interceptTableSet.add("ITEM");//商品管理数据隔离; 同时item 表中新增  company_id 字段

        interceptTableSet.add("ITEM_SKU");//SKU管理数据隔离;

    interceptTableSet.add("INVENTORY");//SKU 管理展示库存

    interceptTableSet.add("BRAND");//品牌管理

    interceptTableSet.add("`OUTER_ORDER`");//主订单管理

    interceptTableSet.add("`OUTER_ORDER_DETAIL`");// 主订单明细管理

    interceptTableSet.add("ERP_ORDER");//子订单管理

    interceptTableSet.add("TASK_DAILY");//采购单

    interceptTableSet.add("TASK_DAILY_DETAIL");//采购单明细

    interceptTableSet.add("PURCHASE_STORAGE");//采购入库单

    interceptTableSet.add("PURCHASE_STORAGE_DETAIL");//采购入库单明细

    interceptTableSet.add("INVENTORY_AREA");//库存占用表

    interceptTableSet.add("INVENTORY_OUT");//库存记录库记录表

    interceptTableSet.add("WAREHOUSE");//仓库改造

    interceptTableSet.add("INVENTORY_INOUT");//出入库记录表     {硬编码}

        //interceptTableSet.add("`SHIPPING_ORDER`");//发货管理              {硬编码}

        //interceptTableSet.add("TASK_DAILY_ORDER");//订单生成采购单              {硬编码}

    //interceptTableSet.add("TASK_DAILY_ORDER_HISTORY");// 订单生成采购单历史记录  {硬编码}

    interceptTableSet.add("TASK_DAILY_ORDER_UNCOMPLETE"); //未完成采购表

    interceptTableSet.add("`ERP_RETURN_ORDER`"); //退单表

    interceptTableSet.add("`SELLER_TYPE`");//销售类别表

    interceptTableSet.add("`SELLER`");//销售人员表

    interceptTableSet.add("`WX_PURCHASE_USER`");//买手人员表

    interceptTableSet.add("`GOODS_CASE`");//货源信息反馈表

    interceptTableSet.add("LOGISTIC_COMPANY");//快递公司

    interceptTableSet.add("RECEIPT");//小票

    interceptTableSet.add("RECEIPT_GROUP");//小票另存表

    interceptTableSet.add("TASK_RECEIPT");//小票明细

    interceptTableSet.add("FIND_ITEM");//发现商品

    interceptTableSet.add("FIND_ITEM_SKU");//发现商品sku

    interceptTableSet.add("FILE_MANAGE");//文件管理

    }

    // FROM 表名 <----主要的就是这个,判断用户对这个表有没有权限

    @Override

    public void visit(Table tableName) {

        //判断该表是否是需要操作的表

        if (isActionTable(tableName.getFullyQualifiedName())) {

            // 声明表达式数组

            Expression[] expressions;

            // 其他情况,也就是最常用的情况,比如where   1 = 1

            Column column = new Column(new Table(tableName.getAlias()!=null?tableName.getAlias().getName():tableName.getFullyQualifiedName()), "company_id");

            if(getShiroUser()== null){

            return;

            }else {

            expressions = new Expression[] {column, new LongValue(getShiroUser().getCompanyId())};

            }

            // 根据运算符对原始数据进行拼接

            Expression operator = this.getOperator("=", expressions);

            if (this.enhancedCondition != null) {

                enhancedCondition = new AndExpression(enhancedCondition , operator);

            } else {

                enhancedCondition = operator;

            }

        }

    }

    // FROM 子查询

    @Override

    public void visit(SubSelect subSelect) {

        // 如果是子查询的话返回到select接口实现类

        subSelect.getSelectBody().accept(new SelectVisitorImpl());

    }

    // FROM subjoin

    @Override

    public void visit(SubJoin subjoin) {

        subjoin.getLeft().accept(new FromItemVisitorImpl());

        subjoin.getJoin().getRightItem().accept(new FromItemVisitorImpl());

    }

    // FROM 横向子查询

    @Override

    public void visit(LateralSubSelect lateralSubSelect) {

        lateralSubSelect.getSubSelect().getSelectBody()

                .accept(new SelectVisitorImpl());

    }

    // FROM value列表

    @Override

    public void visit(ValuesList valuesList) {

    }

    // FROM tableFunction

    @Override

    public void visit(TableFunction tableFunction) {

    }

    // 将字符串类型的运算符转换成数据库运算语句

    private Expression getOperator(String op, Expression[] exp) {

        if ("=".equals(op)) {

            EqualsTo eq = new EqualsTo();

            eq.setLeftExpression(exp[0]);

            eq.setRightExpression(exp[1]);

            return eq;

        } else if (">".equals(op)) {

            GreaterThan gt = new GreaterThan();

            gt.setLeftExpression(exp[0]);

            gt.setRightExpression(exp[1]);

            return gt;

        } else if (">=".equals(op)) {

            GreaterThanEquals geq = new GreaterThanEquals();

            geq.setLeftExpression(exp[0]);

            geq.setRightExpression(exp[1]);

            return geq;

        } else if ("<".equals(op)) {

            MinorThan mt = new MinorThan();

            mt.setLeftExpression(exp[0]);

            mt.setRightExpression(exp[1]);

            return mt;

        } else if ("<=".equals(op)) {

            MinorThanEquals leq = new MinorThanEquals();

            leq.setLeftExpression(exp[0]);

            leq.setRightExpression(exp[1]);

            return leq;

        } else if ("<>".equals(op)) {

            NotEqualsTo neq = new NotEqualsTo();

            neq.setLeftExpression(exp[0]);

            neq.setRightExpression(exp[1]);

            return neq;

        } else if ("is null".equalsIgnoreCase(op)) {

            IsNullExpression isNull = new IsNullExpression();

            isNull.setNot(false);

            isNull.setLeftExpression(exp[0]);

            return isNull;

        } else if ("is not null".equalsIgnoreCase(op)) {

            IsNullExpression isNull = new IsNullExpression();

            isNull.setNot(true);

            isNull.setLeftExpression(exp[0]);

            return isNull;

        } else if ("like".equalsIgnoreCase(op)) {

            LikeExpression like = new LikeExpression();

            like.setNot(false);

            like.setLeftExpression(exp[0]);

            like.setRightExpression(exp[1]);

            return like;

        } else if ("not like".equalsIgnoreCase(op)) {

            LikeExpression nlike = new LikeExpression();

            nlike.setNot(true);

            nlike.setLeftExpression(exp[0]);

            nlike.setRightExpression(exp[1]);

            return nlike;

        } else if ("between".equalsIgnoreCase(op)) {

            Between bt = new Between();

            bt.setNot(false);

            bt.setLeftExpression(exp[0]);

            bt.setBetweenExpressionStart(exp[1]);

            bt.setBetweenExpressionEnd(exp[2]);

            return bt;

        } else if ("not between".equalsIgnoreCase(op)) {

            Between bt = new Between();

            bt.setNot(true);

            bt.setLeftExpression(exp[0]);

            bt.setBetweenExpressionStart(exp[1]);

            bt.setBetweenExpressionEnd(exp[2]);

            return bt;

        } else {

            // 如果没有该运算符对应的语句

            return null;

        }

    }

    public Expression getEnhancedCondition() {

        return enhancedCondition;

    }

    // 判断传入的table是否是要进行操作的table

    public boolean isActionTable(String tableName) {

        boolean flag = false;

        if (interceptTableSet.contains(tableName.toUpperCase())) {

            flag = true;

        }

        return flag;

    }

    public ShiroUser getShiroUser() {

    ShiroUser one = (ShiroUser) SecurityUtils.getSubject().getPrincipal();

        return one;

    }

}

继续阅读