目录

承前

概念概述

权限概述

数据权限要素

操作控制

规则定义

方案选型

方案一:子查询:

方案二:手动添加Sql控制

方案三:借助视图

方案四:Sql解析器

比对选型

条件视图实现

Mybatis访问

添加配置

DB侧

性能问题

Sql解析器实现

支持场景

资源定义

操作控制

DruidSql解析器逻辑

Join

Union

Where

DruidSqlParser

Mybatis拦截器代码

总结


承前

        如果一个表根据tenantid区分不同公司的数据,那么就需要在写Sql脚本的时候添加一个where tenantid = ** 的条件,用来区分。如下面表:

tenantid

id

name

sex

age

1001

1

张三1


18

1001

2

张三2


19

1001

3

张三3


20

1001

4

张三4


21

1001

5

张三5


22

1002

6

张三6


23

1002

7

张三7


24

1002

8

张三8


25

1002

9

张三9


26

1002

10

张三10


27

概念概述

权限概述

权限就是某个人对某资源是否可以进行某种操作。

权限分为功能权限和数据权限。

功能权限:表象就是:角色,权限,菜单,登录,认证

数据权限:指对系统用户进行数据资源可见性的控制,比如:某部门的HR只能看到某部门的员工

数据权限要素

从当前登录用户的角度来说,数据权限的定义可以解释为:`当前登录的用户只能看到该用户权限范围内的数据资源`。由此可以分析出数据权限控制中几个关键要素:

1. 主体,即当前登录用户。领导、角色等概念可翻译为当前登录用户是否是领导,是否拥有某角色。
2. 数据资源。即受管控系统数据。
3. 条件规则。即当前登录用户对于某特定的数据资源适用的条件。


主体:主体是人,实际使用呢,中间会添加一个角色。 当然也可以直接关联到人。
资源:数据资源包括DB中的数据,也包括File。如果File已经在DB中映射记录,那么全部可以理解为DB中的数据。
资源的定义:DB中的表很多,需要从业务的角度分析,控制的资源的类型,以及从哪些维度控制,权限值是啥。比如:按照部门控制员工的访问。员工就是资源类型,部门就是控制的一个维度,可以理解为表中的一个字段,这个字段的取值需要是固定的,或者是相对固定的,这个固定的字段值,就是权限值。
条件规则:即用户(角色) 可以访问的 资源+维度+权限值 的映射关系

操作控制

按说操作是功能权限,但为了防止访问的横向扩展。

比如:我是A部门的HR,具有A部分的员工CRUD权限。 URI是可以通过的。 insert的时候 我传的 部门ID 是B。

为了控制这种情况,需要对insert,update,delete 的条件和数值进行校验,融合select进行的控制,可以分为以下三种情况:

1. 只控制条件:数据查询和删除权限 Select、Delete
 - 将具有的权限添加到where条件中

2. 只控制数值:数据新增权限 insert
 - 控制values中对应的字段,比如部门字段值是否是A

3. 上述两种的融合,即控制条件也控制值,数据更新权限 update
 - 控制update的条件,是为了防止更新了其他部门的数据
 - 控制values中的值,是为了防止将A部门更新成B部门

规则定义

1. 对逻辑关系的扩展:上述只能and场景,也可以添加一些or场景
2. 对权限值的扩展:比如我可以访问性李的员工, 权限值: 李*
当然 规则定义完成后,需要在实现的时候兼容即可。

方案选型

数据权限 的控制 就是从Sql的角度来控制,控制执行的Sql中是权限内的。

方案一:子查询:

有控制的就替换为子查询
Mybatis拦截器,拦截访问的Sql
框架角度解决,可以解决手写Sql不足
不足:子查询会创建零时表,在数据量大时DB会直接挂掉
优点:不会对业务代码进行入侵;规则可以灵活;动态增加资源类型和维度而不需要修改代码。

方案二:手动添加Sql控制

实现简单
改动点多,数据权限扩展 影响很大

方案三:借助视图

1.动态视图,动态创建视图
2.条件视图,实现创建的视图条件值是一个函数,视图使用MERGE模式(默认算法,不创建中间表),不足:权限值只能有一个,如果新增条件,需要更新视图;好处是:可以从DB权限进行控制,只给视图的权限。
    A:视图使用函数传值,函数通过存储过程传值
    B:存储过程传值可以放在Mybatis的拦截器中做
3.视图&中间表,函数只可以返回一个值,没法做到in场景,可以创建一个中间表,根视图进行join 进行控制

方案四:Sql解析器

Mybatis拦截器,拦截访问的Sql
使用Sql解析器解析Sql语句,通过规则的比对,添加On,where条件,或者 校验insert 和update 的值的合法性
不足:多了一次解析Sql的逻辑;编码复杂;
优点:不会对业务代码进行入侵;规则可以灵活;动态增加资源类型和维度而不需要修改代码。

比对选型

1. 如果可以接受中间表,可以选方案1

2. 如果团队技术能力一般,业务也比较简单,控制的地方不多,也没变更的需求,方案2

3. 如果业务固定,数据库控制严格,比如说要给第三方直接看数据库,这种方案也可以考虑,建议用条件视图;动态创建视图权限太大是个问题;借助中间表,高并发是问题。

4. 如果并发量不高,业务复杂,且需要灵活配置,适应需求的频繁变更,团队技术能力还可以,方案4

条件视图实现

Mybatis访问

<mapper namespace="com.glodon.gcms.admin.dao.BusinessUDMapper">

<select id="list1" resultType="com.glodon.gcms.admin.dbsupportor.entity.Table1"

parameterType="int">

call test_sp(#{tenant});

SELECT * from test_view;

</select>

</mapper>

添加配置

jdbc:mysql://192.168.118.162:3306/test_wq?museUnicode=true&characterEncoding=utf-8&allowMultiQueries=true



必须:在mybatis同一个方法中执行多个SQL语句实现方法:

1、修改数据库连接参数加上allowMultiQueries=true,如:
hikariConfig.security.jdbcUrl=jdbc:mysql://xx.xx.xx:3306/xxxxx?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true

2、直接写多条语句,用“;”隔开即可

<delete id="delete" parameterType="upc.cbs.HtxxlrEntity">
  delete from PC_CBS_CONTRACT where contract_id = #{contract_id};
  delete from PC_CBS_UPLOAD_FILES where  contract_id = #{contract_id} and  filetype='合同附件';
  delete from PC_CBS_CONTRACT_TEAM where contract_id = #{contract_id};
</delete>

mysql需要修改数据连接添加allowMultiQueries=true属性;每个sql语句用“;”分开

DB侧

-- 创建存储过程
-- 作用:给变量赋值
drop procedure if exists test_sp;
create procedure test_sp(
  in tenantcode int(11)
)

BEGIN
set @tenantcode = tenantcode;
-- SELECT * from test_view3;
END;

-- 创建方法
-- 作为视图的变量
create FUNCTION test_f()
returns int(11)
BEGIN
return @tenantcode;
END;

-- 创建视图
-- 使用函数作为变量
create view test_view
AS
SELECT * from table1 where tenant = test_f();

-- 调用过程
set autocommit=0;
START TRANSACTION;
call test_sp(22);
SELECT * from test_view;
COMMIT;

-- 调用过程
call test_sp(22);
SELECT * from test_view;

性能问题

MySQL在处理视图时有两种算法,分别称为MERGE和TEMPTABLE。

在执行"CREATE VIEW"语句时可以指定使用哪种算法。不显现指定的话,Mysql默认使用Merge算法。

MERGE,将视图sql合并到主查询sql中,重新构成新sql进行查询。网上有个大侠说的挺好“有点类似于C语言中的宏展开”

TEMPTABLE, 就是将视图单作临时表来处理。

Sql解析器实现

支持场景

支持场景:
1. ([销售地区] [等于] [上海]) or ([销售地区] [等于] [北京])
2. 不支持 ([销售地区] [等于] [上海]) or ([货物类型] [等于] [3C]) -- 看看这种场景是否需要 支持

资源定义

数据权限包括:资源定义 + 操作控制
数据定义:
数据结构:<资源类型,<维度,取值>>
示例:<table1,<name,11>>  
含义:只允许操作 table1中name=11的字段

操作控制

操作控制:
1. 查询权限: 如果查table1,只能查name=11的数据;
- 涉及Select和Delete
- 会添加where条件,如果select有join,会添加On条件
2. 添加权限:如果给table1添加数据,name值只能是11
- 涉及:insert
- 操作:会检查value中对应的值,如果name不是11,则抛出异常;
3. 更新权限: 如果要更新table1表,只能更新name=11的数据,且values中name对应的值只能是11
- 涉及:update
- 相当于是select+insert并集,即检查values中数据的合法性;又添加where条件。

DruidSql解析器逻辑

Join

给jsonsql语句添加ON 条件
示例:
FROM
    table1 t1
LEFT JOIN table2 t2 ON t1.tenant = t2.tenant
LEFT JOIN table3 t3 ON  t2.tenant = t3.tenant
数据结构:
                             SQLJoinTableSource
                              /               \
                   SQLJoinTableSource               SQLExprTableSource(table3)
                       /          \
 SQLExprTableSource(table3)     SQLExprTableSource(table2)

Union

如果是union查询曾需要处理左右两棵树,每棵树都是一个SQLSelectQuery节点
叶子节点是简单的SQLSelectQueryBlock查询节点
处理完成后,需要处理where条件,同样where条件 也是一棵树,也可能包含子查询,子查询又是一个SQLSelectQuery

Where

数据结构:
处理对象:SQLBinaryOpExpr,SQLInSubQueryExpr
处理方式:
1. SQLBinaryOpExpr,左右子树遍历进去处理,如果有SQLInSubQueryExpr则处理,其他的类型不做处理,即:type=11 或者 添加上去的 tenant in (11,22) 也不做处理
2. 如果类型是SQLInSubQueryExpr,则是继续处理Select语句一样处理。

DruidSqlParser

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement.ValuesClause;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.alibaba.druid.sql.parser.SQLParserUtils;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.druid.util.JdbcUtils;

/**
 * 阿里巴巴的Druid Sql 解析器
 * 用来控制数据权限
 * 数据权限包括:资源定义 + 操作控制
 * 数据定义:
 * 	数据结构:<资源类型,<维度,取值>>
 *  示例:<table1,<name,11>>  
 *  含义:只允许操作 table1中name=11的字段
 * 操作控制:
 * 1. 查询权限: 如果查table1,只能查name=11的数据;
 * - 涉及Select和Delete
 * - 会添加where条件,如果select有join,会添加On条件
 * 2. 添加权限:如果给table1添加数据,name值只能是11
 * - 涉及:insert
 * - 操作:会检查value中对应的值,如果name不是11,则抛出异常;
 * 3. 更新权限: 如果要更新table1表,只能更新name=11的数据,且values中name对应的值只能是11
 * - 涉及:update
 * - 相当于是select+insert并集,即检查values中数据的合法性;又添加where条件。
 * 
 * 案例:
	-- ----------------1.update----------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[222, 33]] --
	--     维度[type] 权限值[33] --
	--     维度[tenant] 权限值[11] --
	
	-- 原始脚本:
	update `table1` SET `remark` = 222 where id = 1;
	
	-- 结果脚本:
	UPDATE `table1`
	SET `remark` = 222
	WHERE id = 1
		AND (remark IN ('222', 33)
			AND type = '33'
			AND tenant = '11');
	-- --------------------------------------------------------------------
	
	
	-- --------2.单表查询------------------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[222, 33]] --
	--     维度[type] 权限值[33] --
	--     维度[tenant] 权限值[11] --
	
	-- 原始脚本:
	SELECT * from table1 where type = 111;
	
	-- 结果脚本:
	SELECT *
	FROM table1
	WHERE type = 111
		AND (remark IN ('222', 33)
			AND type = '33'
			AND tenant = '11');
	-- --------------------------------------------------------------------

	-- --------3.多表查询------------------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[222, 33]] --
	--     维度[type] 权限值[33] --
	--     维度[tenant] 权限值[11] --
	-- 原始脚本:
	SELECT
		t1.`name`,
		t2.tenant,
		t3.type
	FROM
		table1 t1
	LEFT JOIN table2 t2 ON t1.tenant = t2.tenant
	LEFT JOIN table3 t3 ON t2.tenant = t3.tenant
	WHERE
		t1.tenant = 11
	ORDER BY
		t1.type
	LIMIT 0,
	 1;
	
	-- 结果脚本:
	SELECT
		t1.`name`,
		t2.tenant,
		t3.type
	FROM
		table1 t1
	LEFT JOIN table2 t2 ON t1.tenant = t2.tenant
	AND (
		t1.remark IN ('222', 33)
		AND t1.type = '33'
		AND t1.tenant = '11'
	)
	LEFT JOIN table3 t3 ON t2.tenant = t3.tenant
	AND t3.id = '33'
	WHERE
		t1.tenant = 11
	ORDER BY
		t1.type
	LIMIT 0,
	 1;
	
	-- --------------------------------------------------------------------
	
	-- --------4.删除------------------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[222, 33]] --
	--     维度[type] 权限值[33] --
	--     维度[tenant] 权限值[11] --
	
	-- 原始脚本:
	DELETE from table1 where tenant =1;
	
	-- 结果脚本:
	DELETE FROM table1
	WHERE tenant = 1
		AND (remark IN ('222', 33)
			AND type = '33'
			AND tenant = '11');
	-- --------------------------------------------------------------------
	
	
	-- -------5.有错误的insert-------------------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[222, 33]] --
	--     维度[type] 权限值[33] --
	--     维度[tenant] 权限值[11] --
	
	-- 原始脚本:
	INSERT INTO `table1` (	`name`,	`remark`,	`tenant`,	`type`)VALUES ('55', '55', '55', '555'),('66', '66', '66', '666')	;
	
	-- 结果脚本:
	INSERT INTO `table1` (`name`, `remark`, `tenant`, `type`)
	VALUES ('55', '55', '55', '555'),
		('66', '66', '66', '666');
	-- 错误信息:
	
	第 [0] 行, 列:remark,值只允许是:[222, 33],实际值是:55
	第 [1] 行, 列:remark,值只允许是:[222, 33],实际值是:66
	第 [0] 行, 列:tenant,值只允许是:11,实际值是:55
	第 [1] 行, 列:tenant,值只允许是:11,实际值是:66
	第 [0] 行, 列:type,值只允许是:33,实际值是:555
	第 [1] 行, 列:type,值只允许是:33,实际值是:666
	-- --------------------------------------------------------------------
	
	-- --------6.部分报错Insert------------------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[55, 44]] --
	
	-- 原始脚本:
	INSERT INTO `table1` (	`name`,	`remark`,	`tenant`,	`type`)VALUES ('55', '55', '55', '555'),('66', '66', '66', '666')	;
	
	-- 结果脚本:
	INSERT INTO `table1` (`name`, `remark`, `tenant`, `type`)
	VALUES ('55', '55', '55', '555'),
		('66', '66', '66', '666');
	-- 错误信息:
	
	第 [1] 行, 列:remark,值只允许是:[55, 44],实际值是:66
	-- --------------------------------------------------------------------
	
	-- --------7.无报错insert------------------------------------------------
	-- 权限设置
	-- 资源[table3] --
	--     维度[id] 权限值[33] --
	-- 资源[table1] --
	--     维度[remark] 权限值[[55, 66]] --
	
	-- 原始脚本:
	INSERT INTO `table1` (	`name`,	`remark`,	`tenant`,	`type`)VALUES ('55', '55', '55', '555'),('66', '66', '66', '666')	;
	
	-- 结果脚本:
	INSERT INTO `table1` (`name`, `remark`, `tenant`, `type`)
	VALUES ('55', '55', '55', '555'),
		('66', '66', '66', '666');
	-- 错误信息:
	
	-- --------------------------------------------------------------------
	
 * 
 * 
 */
public class DruidSqlParser {

	public static void main(String[] args) throws Exception {
						String sql = "SELECT * from table1 where type = 111";
//						String sql = "SELECT t1.`name`,t2.tenant from table1 t1 LEFT JOIN table2 t2 On t1.tenant = t2.tenant where t1.tenant = 11 order by t1.type limit 0,1";
//						String sql = "SELECT t1.`name`,t2.tenant,t3.type from table1 t1 LEFT JOIN table2 t2 On t1.tenant = t2.tenant LEFT JOIN table3 t3 On t2.tenant = t3.tenant where t1.tenant = 11 order by t1.type limit 0,1 ";
//				String sql = "DELETE from table1 where tenant =1";
//				String sql = "INSERT INTO `table1` (	`name`,	`remark`,	`tenant`,	`type`)VALUES ('55', '55', '55', '555'),('66', '66', '66', '666')	";
//		String sql = "update `table1` SET `remark` = 222 where id = 1";

		/**
		 * 数据结构:<资源类型,<维度,取值>>
		 * 取值类型:int,string,list
		 */
		Map<String, Map<String, Object>> privateMap = new HashMap<String, Map<String, Object>>();
		Map<String, Object> columnMap1 = new HashMap<String, Object>();
//		columnMap1.put("tenant", "11");
//		columnMap1.put("type", "33");
		//		columnMap1.put("tenant", 11);
		columnMap1.put("remark", Arrays.asList("55","66"));
		privateMap.put("table1", columnMap1);

		/*Map<String, Object> columnMap2 = new HashMap<String, Object>();
		columnMap2.put("type", "22");
		privateMap.put("table2", columnMap2);*/

		Map<String, Object> columnMap3 = new HashMap<String, Object>();
		columnMap3.put("id", "33");
		privateMap.put("table3", columnMap3);
		
		String temp = null;

		// 编译Sql,类似是Mysql
		SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, JdbcUtils.MYSQL);
		List<SQLStatement> stmtList = parser.parseStatementList();
		SQLStatement stmt = stmtList.get(0);

		// 是update语句
		if (stmt instanceof MySqlUpdateStatement) {
			MySqlUpdateStatement updateStatement = (MySqlUpdateStatement) stmt;
			
			SQLExprTableSource tableSource = (SQLExprTableSource) updateStatement.getTableSource();
			SQLExpr where = updateStatement.getWhere();
			List<SQLUpdateSetItem> items = updateStatement.getItems();
			String alias = tableSource.getAlias();
			SQLIdentifierExpr expr = (SQLIdentifierExpr) tableSource.getExpr();
			
			// 获取表名
			String tableName = cleanSqlName(expr.getName());
			// 判断该表是否控制
			if(privateMap.containsKey(tableName)){
				//获取该资源的各维度数据
				Map<String, Object> columnMap = privateMap.get(tableName);
				// 缓存错误信息
				StringBuffer sb = new StringBuffer();
				// 遍历 set 的各个键值对,处理
				for(SQLUpdateSetItem item : items){
					// 获取列名
					String columnName = cleanSqlName(item.getColumn().toString());
					// 判断是否控制该列
					if(columnMap.containsKey(columnName)){
						// 获取值,如果是null,则不进行控制
						SQLExpr valueExpr = item.getValue();
						if(valueExpr == null){
							continue;
						}
						// 获取该列允许的权限值
						Object privateValue = columnMap.get(columnName);
						String value = valueExpr.toString();
						// 如果权限值是 数字或者 字符,则直接== 判断
						if(privateValue instanceof Number || privateValue instanceof String){
							if(!(privateValue+"").equals(value)){
								sb.append("\n").append(String.format("列:%s,值只允许是:%s,实际值是:%s", columnName,privateValue,value));
							}
						}else if (privateValue instanceof List){
							//如果 权限值 是一个范围,就需要遍历这个List,看是否允许
							@SuppressWarnings("unchecked")
							List<Object> list =  (List<Object>) privateValue;
							// 如果将要插入的值不是 允许的值,则添加报错信息
							if(!list.contains(value)){
								// 如果不包含报错
								sb.append("\n").append(String.format("列:%s,值只允许是:%s,实际值是:%s", columnName,list,value));
							}
						}
					}
				}
				
				// 如果报错信息不为空,则直接抛出异常
//				if(sb.toString().length() > 0){
//					throw new Exception(sb.toString());
//				}
				
				temp = sb.toString();
			}

			// 构建where条件
			where = volidatePrivate(privateMap, where, alias, tableName);
			// 设置where
			updateStatement.setWhere(where);
		}

		/**
		 * insert 语句
		 * 示例脚本:INSERT INTO `table1` (	`name`,	`remark`,	`tenant`,	`type`)
		 * VALUES ('55', '55', '55', '555'),('66', '66', '66', '666')
		 */
		if (stmt instanceof MySqlInsertStatement) {
			MySqlInsertStatement insertStatement = (MySqlInsertStatement) stmt;

			SQLExprTableSource tableSource = (SQLExprTableSource) insertStatement.getTableSource();
			SQLIdentifierExpr expr = (SQLIdentifierExpr) tableSource.getExpr();
			
			// `table1`,看看 是不是要清理
			String tableName = cleanSqlName(expr.getName());

			// 判断是否控制资源
			if(privateMap.containsKey(tableName)){
				// 获取各维度值
				Map<String, Object> columnMap = privateMap.get(tableName);
				// 获取列名字
				List<SQLExpr> columns = insertStatement.getColumns();
				// 获取值列表,存在一次insert多条的场景
				List<ValuesClause> valuesList = insertStatement.getValuesList();
				// 错误信息缓存
				StringBuffer sb = new StringBuffer();
				// 遍历 (	`name`,	`remark`,	`tenant`,	`type`)
				for (int i = 0 ; i < columns.size() ;i++) {
					// 获取列,名称 	`name`,也需要清理吧
					String columnName = cleanSqlName(((SQLIdentifierExpr) columns.get(i)).getName());
					// 判断是否对改列有 控制
					if(columnMap.containsKey(columnName)){
						Object privateValue = columnMap.get(columnName);
						// 遍历 列值,看是否是允许的值
						for (int j = 0 ; j < valuesList.size() ;j++) {
							// insert中的一行  ('55', '55', '55', '555')
							List<SQLExpr> values = valuesList.get(j).getValues();
							// 根据列的下标,获取对应的值
							String value = ((SQLCharExpr) values.get(i)).getText();
							// 如果是number和string直接判断相等与否;如果是列表,择需要看是否包含
							if(privateValue instanceof Number || privateValue instanceof String){
								// 如果跟设置的不相同
								if(!(privateValue+"").equals(value)){
									sb.append("\n").append(String.format("第 [%s] 行, 列:%s,值只允许是:%s,实际值是:%s", j,columnName,privateValue,value));
								}
							}else if (privateValue instanceof List){
								@SuppressWarnings("unchecked")
								List<Object> list =  (List<Object>) privateValue;
								// 如果不包含报错
								if(!list.contains(value)){
									sb.append("\n").append(String.format("第 [%s] 行, 列:%s,值只允许是:%s,实际值是:%s", j,columnName,list,value));
								}
							}
						}
					}
				}

				// 如果错误信息不为空,直接抛出异常
//				if(sb.toString().length() > 0){
//					throw new Exception(sb.toString());
//				}
				
				temp = sb.toString();
			}


		}

		// 删除语句
		if (stmt instanceof MySqlDeleteStatement) {
			MySqlDeleteStatement delStatement = (MySqlDeleteStatement) stmt;
			SQLExpr where = delStatement.getWhere();
			SQLExprTableSource tableSource = (SQLExprTableSource) delStatement.getTableSource();
			String alias = tableSource.getAlias();
			SQLIdentifierExpr expr = (SQLIdentifierExpr) tableSource.getExpr();
			// 获取表名,并清理`
			String tableName = cleanSqlName(expr.getName());
			// 构建where条件
			where = volidatePrivate(privateMap, where, alias, tableName);
			// 设置where
			delStatement.setWhere(where);
		}

		// 查询语句
		if (stmt instanceof SQLSelectStatement) {
			SQLSelectStatement selectStmt = (SQLSelectStatement) stmt;
			SQLSelect sqlselect = selectStmt.getSelect();
			SQLSelectQueryBlock query = (SQLSelectQueryBlock) sqlselect.getQuery();
			SQLTableSource from = query.getFrom();

			// json语句,这是是棵树
			if(from instanceof SQLJoinTableSource){
				// 获取sql原始数据
				SQLJoinTableSource sqlTableSource = (SQLJoinTableSource) from;
				List<String> sqlList = new ArrayList<String>();
				// 递归添加ON条件
				addJoinSqlCondition(privateMap,sqlList,sqlTableSource);

			}

			// 简单Sql
			if(from instanceof SQLExprTableSource){
				// 获取sql原始数据
				SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) from;
				SQLExpr expr = sqlExprTableSource.getExpr();
				SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) expr;
				String tableName = cleanSqlName(sqlIdentifierExpr.getName());
				String alias = sqlExprTableSource.getAlias();
				SQLExpr where = query.getWhere();
				// 构建where条件
				where = volidatePrivate(privateMap, where, alias, tableName);
				// 设置where条件
				query.setWhere(where);
			}

		}

		// 打印查询语句
		System.out.println("-- --------------------------------------------------------------------");
		System.out.println("-- 权限设置");
		Set<String> keySet = privateMap.keySet();
		for(String key : keySet){
			System.out.println(String.format("-- 资源[%s] --", key));
			Map<String, Object> map = privateMap.get(key);
			Set<String> keySet2 = map.keySet();
			for(String key2 : keySet2){
				Object object = map.get(key2);
				System.out.println(String.format("--     维度[%s] 权限值[%s] --",key2, object.toString()));
			}
		}
		System.out.println("");
		System.out.println("-- 原始脚本:");
		System.out.println(sql+";");
		System.out.println("");
		System.out.println("-- 结果脚本:");
		System.out.println(stmt.toString()+";");
		System.out.println("-- 错误信息:");
		System.out.println(temp);
		System.out.println("-- --------------------------------------------------------------------");
		System.out.println("");

	}
	
	private static String cleanSqlName(String val){
		if(val == null){
			return val;
		}
		return val.replace("`", "");
	}


	private static SQLExpr volidatePrivate(Map<String, Map<String, Object>> privateMap,SQLExpr where, String alias,String tableName) {
		if(privateMap.containsKey(tableName)){
			Map<String, Object> columnMap = privateMap.get(tableName);

			if(columnMap != null && columnMap.size() > 0){
				Set<String> keys = columnMap.keySet();
				List<String> sqlList = new ArrayList<String>();
				for(String key:keys){
					key = cleanSqlName(key);
					// 如果别名存在就用别名
					String key1 = alias !=null && alias.length() > 0 ? alias.concat(".").concat(key) : key;
					// 拼接条件对象
					sqlList.add(getSqlByClass1(key1,columnMap.get(key)));
				}
				// 添加条件
				String sql1 = listToSql(sqlList);
				// 清空list
				sqlList.clear();
				// 构建条件对象
				SQLExpr sqlExpr = SQLParserUtils.createExprParser(sql1, JdbcUtils.MYSQL).expr();
				// 修改where表达式
				if (where == null) {
					where = sqlExpr;
				} else {
					where = new SQLBinaryOpExpr(where, SQLBinaryOperator.BooleanAnd,sqlExpr );
				}
			}

		}

		return where;
	}


	/**
	 * 给jsonsql语句添加ON 条件
	 * 示例:
	 * FROM
	 * 	table1 t1
	 * LEFT JOIN table2 t2 ON t1.tenant = t2.tenant
	 * LEFT JOIN table3 t3 ON  t2.tenant = t3.tenant
	 * 数据结构:
	 *     		     						SQLJoinTableSource
	 *     								    /               \
	 * 	  					SQLJoinTableSource         	  SQLExprTableSource(table3)
	 *                        /          \
	 *  SQLExprTableSource(table3)     SQLExprTableSource(table2)
	 * 
	 * @param privateMap 数据权限Map
	 * @param sqlList 添加的Sql条件列表
	 * @param ts 数据源对象
	 */
	private static void addJoinSqlCondition(Map<String, Map<String, Object>> privateMap,List<String> sqlList,SQLTableSource ts) {
		if(ts == null){
			return ;
		}

		// 判断是否我 符合节点
		if(ts instanceof SQLJoinTableSource){
			// 转换类型
			SQLJoinTableSource sqlTableSource = (SQLJoinTableSource) ts;
			// 检查左边
			addJoinSqlCondition(privateMap,sqlList,sqlTableSource.getLeft());
			// 检查右边
			addJoinSqlCondition(privateMap,sqlList,sqlTableSource.getRight());
			// 添加条件
			String sql = listToSql(sqlList);
			// 清空list
			sqlList.clear();

			if(sql != null && sql.length() > 0){
				SQLExpr condition = sqlTableSource.getCondition();
				SQLExpr constraintsExpr = SQLParserUtils.createExprParser(sql, JdbcUtils.MYSQL).expr();
				condition = new SQLBinaryOpExpr(condition, SQLBinaryOperator.BooleanAnd, constraintsExpr);
				sqlTableSource.setCondition(condition);
			}
		}

		// 判断是否是 叶子节点
		if(ts instanceof SQLExprTableSource){
			// 获取别名
			String leftTableAlias = ts.getAlias();
			SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) ts;
			SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) sqlExprTableSource.getExpr();
			// 获取表名
			String tableName = cleanSqlName(sqlIdentifierExpr.getName());
			// 判断是否控制该资源
			if(privateMap.containsKey(tableName)){
				// 获取维度数据
				Map<String, Object> columnMap = privateMap.get(tableName);
				if(columnMap != null && columnMap.size() > 0){
					// 遍历维度数据
					Set<String> keys = columnMap.keySet();
					for(String key : keys){
						// 如果别名存在就用别名
						String key1 = leftTableAlias !=null && leftTableAlias.length() > 0 ? leftTableAlias.concat(".").concat(key) : key;
						// 拼接条件对象
						sqlList.add(getSqlByClass1(key1,columnMap.get(key)));
					}
				}
			}
		}

	}

	/**
	 * 将条件的list转换为Sql
	 * @param sqlList
	 * @return
	 */
	private static String listToSql(List<String> sqlList) {
		StringBuffer sb = new StringBuffer();
		if(sqlList.size() > 0){
			String sql1 = sqlList.get(0);
			sb.append(sql1);

			for(int i = 1; i < sqlList.size() ; i++){
				sb.append(" and").append(sqlList.get(i));
			}
		}
		return sb.toString();
	}

	/**
	 * 根据类型拼接sql 条件
	 * @param key1
	 * @param value
	 * @return
	 */
	private static String getSqlByClass1(String key1, Object value) {
		StringBuffer sb = new StringBuffer();
		sb.append(" ").append(key1).append(" ");
		if(value instanceof Number){
			sb.append(" = ").append(value);
		}else if(value instanceof String){
			sb.append(" = '").append(value+"'");
		}else if (value instanceof List){
			sb.append(" in (");

			@SuppressWarnings("unchecked")
			List<Object> list =  (List<Object>) value;
			Iterator<Object> it = list.iterator();

			while(it.hasNext()){
				Object next = it.next();
				if(next instanceof Number){
					sb.append(next);
				}else if(next instanceof String){
					sb.append("'").append(next).append("'");
				}

				if(it.hasNext()){
					sb.append(",");
				}
			}
			sb.append(")");
		}

		return  sb.toString();

	}

	


}

Mybatis拦截器代码

import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.springframework.stereotype.Component;


/**
 * mybatis分表拦截器 -- 水平切分
 */
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
})
@Component
public class TableShardInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    	System.out.println("ThreadID:"+Thread.currentThread().getId());
    	System.out.println("Cache:"+BusinessServiceController.Cache.get());
        if (invocation.getTarget() instanceof RoutingStatementHandler) {
            try {
                RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
                // MetaObject是mybatis里面提供的一个工具类,类似反射的效果
                MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
                BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");//获取sql语句
                String originSql = boundSql.getSql();
                
                System.out.println("originSql1:"+originSql);
                if(originSql.contains("table1")){
                	
                	originSql = originSql.replaceAll("table1","test_view");
                	
                	originSql = "call test_sp("+BusinessServiceController.Cache.get()+");" + originSql;
                }
                System.out.println("originSql2:"+originSql);
                
                // originSql = originSql.replaceAll("test_view", "table1");
                
                // 把新语句设置回去
                metaStatementHandler.setValue("delegate.boundSql.sql", originSql);
            } catch (Exception e) {
                // ignore 任何一个地方有异常都去执行原始操作 -- invocation.proceed()
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
        return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

}

总结

……