天天看点

mybatis整合数据权限数据权限写个测试类

数据权限

现在很多企业级应用都需要拦截数据权限, 只有配置了相应数据权限的人才能看到该数据

关于数据权限的实现, 个人想了两种实现方式

第一种是基于AOP, 配置相应的注解, 在切面中将数据权限的参数值强制设置到请求参数中去, 然后dao层利用mybatis的动态sql, 将权限值拼接进去, 该方案有前提条件, 数据权限控制的字段必须放到基类中, 其他的对象要继承该基类, Mapper.xml必须抽取一个公用的, 其他的Mapper需要引用该mapper文件作为权限控制(否则每个类和Mapper.xml都要独自维护一套, 不便于后续扩展和维护)

第二种是基于mybatis的拦截器, 拦截sql后将数据权限控制的sql拼接进去, 基于mybatis拦截器拼接sql的难点在于数据权限sql和原sql的拼接, 网上很多版本都是select * from (原sql) where 数据权限sql, 这种实际上并不可取, 一旦出现分页, 或者最终查出的结果集不包含权限控制字段的话, 就会出现bug

这里使用的是正则表达式去匹配后拼接权限sql, 其他的可以参考网上mybatis拦截器的方案

/**
 * sql解析器
 *
 * @author wang.js on 2019/5/8.
 * @version 1.0
 */
public class SqlParser {

	private SqlParser() {
	}

	private static final String SQL_WHERE = "WHERE";

	private static final String SQL_LEFT_JOIN = "LEFT JOIN ";

	/**
	 * 将数据权限的sql拼进原sql中
	 *
	 * @param originSql    原sql
	 * @param privilegeSql 数据权限sql
	 * @return String
	 */
	public static String handlerSql(String originSql, String privilegeSql) {
		if (originSql.endsWith(";")) {
			originSql = originSql.substring(0, originSql.lastIndexOf(";"));
		}
		originSql = originSql.replace("\t", " ").replace("\n", " ");
		originSql = originSql.replaceAll(" {2,}", " ");
		originSql = originSql.replaceAll(" where ", " " + SQL_WHERE + " ");
		originSql = addWhere(originSql);
		originSql = originSql.replace("left", "LEFT");
		originSql = originSql.replace("join", "JOIN");
		originSql = originSql.replaceAll("LEFT[ ]+JOIN[ ]+", SQL_LEFT_JOIN);

		List<String> matcherList = matcherTableSql(originSql);
		for (String matcherSql : matcherList) {
			if (originSql.contains(SQL_LEFT_JOIN + matcherSql)) {
				continue;
			}
			String newMatcherSql = mergeSql(matcherSql, privilegeSql);
			originSql = originSql.replace(matcherSql, newMatcherSql);
		}
		return originSql;
	}

	/**
	 * 添加where关键字
	 *
	 * @param originSql 原始sql
	 * @return String
	 */
	private static String addWhere(String originSql) {
		if (originSql.contains("WHERE")) {
			return originSql;
		}
		String[] split = originSql.split(" ");
		for (int i = 0; i < split.length; i++) {
			if (split[i].equalsIgnoreCase("GROUP") && (i + 1) < split.length && split[i + 1].equalsIgnoreCase("BY")) {
				return originSql.replaceAll(split[i], "WHERE 1=1 GROUP");
			}
			if (split[i].equalsIgnoreCase("ORDER") && (i + 1) < split.length && split[i + 1].equalsIgnoreCase("BY")) {
				return originSql.replaceAll(split[i], "WHERE 1=1 ORDER");
			}
			if (split[i].equalsIgnoreCase("LIMIT")) {
				return originSql.replaceAll(split[i], "WHERE 1=1 LIMIT");
			}
		}
		return originSql + " WHERE 1=1";
	}

	/**
	 * 合并sql
	 *
	 * @param originSql    原sql
	 * @param privilegeSql 数据权限sql
	 * @return String
	 */
	private static String mergeSql(String originSql, String privilegeSql) {
		return originSql.replace(SQL_WHERE, SQL_WHERE + " " + privilegeSql);
	}

	/**
	 * 配置符合
	 *
	 * @param originSql 原sql
	 * @return List<String>
	 */
	private static List<String> matcherTableSql(String originSql) {
		List<String> matcharList = new ArrayList<>();
		String regex = "\\w[ ,](.*?)WHERE";
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(originSql);
		while (matcher.find()) {
			matcharList.add(matcher.group());
		}
		return matcharList;
	}

}
           

写个测试类

@Test
public void t1() {
	String privilegeSql = " brand_code in (1, 2, 3) and region_code in (2, 3) and";
	String originSql = "select * from (select t1, t2, t3 from tableA where 1=1) t1, (select t1, t2, t3 from tableB s where 1=1) t2";
	//		String originSql = "select t1, t2, t3 from tableA";
	System.out.println(SqlParser.handlerSql(originSql, privilegeSql));
}
           

测试后输出的结果为

select * from (select t1, t2, t3 from tableA WHERE brand_code in (1, 2, 3) and region_code in (2, 3) and 1=1) t1, (select t1, t2, t3 from tableB s WHERE brand_code in (1, 2, 3) and region_code in (2, 3) and 1=1) t2

这里需要强调的是, 必须保证每条需要权限控制的语句都含有where关键字, 否则数据权限sql拼接不进去

总结

第一种基于AOP的方案需要维护基类和Mapper的通用代码, 其他的必须继承或引用这两个文件, 但是好处是不容易出现bug

第二种基于mybatis拦截器的方案需要保证每条需要权限控制的sql都有where关键字, 好处是mapper.xml和基类都没有强制性要求

具体使用哪一种就看实际项目需要了

继续阅读