Mabatis-Plus BaseMapper前置SQL增强

一、背景

做事之前先问一下为什么。

(废话)首先问一下为什么这样做,当然是需求所致。

1、需求背景

在各式各样的系统中,对权限的要求都是必须的;

权限控制大概分为两大类:一种是功能上的权限控制;另一种是数据上的权限控制。

本文介绍到的方法是为了解决数据权限控制而写的(此方法不仅仅能进行数据权限控制)。

思路:需求要求对用户进行数据权限控制(部门能看部门,企业能看本企业,总部能看所有企业)——>对数据按照创建部门(create_dept)进行分隔——>(SQL实现)select * from [表名]  where create_dept in (createDeptIdList);

非常简单就能实现。但是作为一个CV高手,简单的CRUD直接通过Mabatis-Plus实现,总不能每一个都要去写sql然后再加上该条件吧!虽然是CV但是难免会出错。

二、直入正题

为了让代码简洁明了,不易出错(懒);为Mybatis-Plus做前置监听,也就是在SQL执行之前将咱们需要的SQL拼到将要执行的SQL里面去。

1、拦截器配置

首先呢,我们先配置一下拦截器,废话不多说上代码

/**
 * 拦截器配置
 */
@Configuration
public class MybatisHandlerConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //1.添加数据权限插件
        MyDataPermissionInterceptor permissionInterceptor = new MybatisPermissionInterceptor ();
        //2.添加自定义的数据权限处理器
        permissionInterceptor.setPermissionHandler(new MybatisPermissionHandler ());
        interceptor.addInnerInterceptor(permissionInterceptor );
        //3.分页方言配置
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

}

2、自定义拦截器

继续不废话上代码

/**
 * 1.仅针对baseMapper原生的方法进行前置增加,不支持对自定义sql的增强
 * 1.1增强方法:selectList、selectPage、selectMaps、selectMapsPage、selectObjs、selectCount、selectOne
 * <p>
 * 2.@UserDataPermission注解默认为true,如果不需要进行数据权限过滤,可以设置为false,不加注解默认true
 * 3.加上注解后,会自动在sql后面增加and create_dept in (createDepts)的条件,createDepts为当前用户所属的公司id及下级公司id
 * 4.如果sql中已存在create_dept的条件,则不会再进行增加
 * 5.用户数据权限(permission_type)优先级高于角色数据权限(permission_type)
 * 6.如果用户&&角色数据权限(permission_type)为空,或实体不存在createDept字段,则不进行增强
 * 7.仅针对单表查询,不支持多表查询
 * 8.请保证BaseMapper的泛型对象中包含createDept字段,否则无法进行增强
 */
@Slf4j
public class MybatisPermissionHandler implements InnerInterceptor {

	private static final List<String> methodList = Arrays.asList("selectList", "selectPage", "selectMaps", "selectMapsPage", "selectObjs", "selectCount", "selectOne");

	/**
	 * 获取数据权限 SQL 片段
	 *
	 * @param plainSelect  查询对象
	 * @param whereSegment 查询条件片段
	 */
	@SneakyThrows(Exception.class)
	public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
		//1.获取where条件
		Expression where = plainSelect.getWhere();
		if (Objects.isNull(where)) {
			where = new HexValue(" 1 = 1 ");
		}
		StringBuilder whereSql = new StringBuilder(where.toString());
		try {
			if (whereSql.indexOf("create_dept") != -1) {
				log.info("当前sql已存在create_dept条件,不进行增强");
				return new HexValue(whereSql.toString());
			}
		} catch (Exception e) {
			throw new Exception("当前sql已存在create_dept条件,不进行增强");
		}

		String className = null;
		String methodName = null;
		try {
			//2.获取mapper类名
			log.info("开始进行权限过滤判断,where: {},mappedStatementId: {}", where, whereSegment);
			className = whereSegment.substring(0, whereSegment.lastIndexOf("."));

			//3.获取执行方法名
			methodName = whereSegment.substring(whereSegment.lastIndexOf(".") + 1);
			if (!methodList.contains(methodName)) {
				log.info("当前方法不能进行权限过滤,methodName: {}", methodName);
				return where;
			}
		} catch (Exception e) {
			throw new Exception("获取方法异常,methodName: " + whereSegment.substring(whereSegment.lastIndexOf(".") + 1));
		}


		//4.获取当前mapper
		Class<?> mapperClass = Class.forName(className);

		//5.通过反射获取当前mapper继承BaseMapper的泛型实体+字段
		try {
			Class<?> entityClass = Class.forName(((ParameterizedTypeImpl) mapperClass.getGenericInterfaces()[0]).getActualTypeArguments()[0].getTypeName());
			List<String> fieldList = Arrays.stream(entityClass.getDeclaredFields()).map(java.lang.reflect.Field::getName).collect(Collectors.toList());
			if (!fieldList.contains("createDept")) {
				//获取父类
				Class<?> superclass = entityClass.getSuperclass();
				if (superclass != null) {
					List<String> superFieldList = Arrays.stream(superclass.getDeclaredFields()).map(java.lang.reflect.Field::getName).collect(Collectors.toList());
					if (superFieldList.contains("createDept")) {
						fieldList.addAll(superFieldList);
					}
				}
				if (!fieldList.contains("createDept")) {
					log.info("当前实体类中不存在create_dept字段,不进行增强");
					return where;
				}
			}
		} catch (Exception e) {
			throw new Exception("获取实体类失败,className: " + className);
		}

		//6.mapper上所有注解
		UserDataPermission annotation = null;
		try {
			annotation = mapperClass.getAnnotation(UserDataPermission.class);
		} catch (NullPointerException e) {
			log.error("mapper上没有加注解,className: {},methodName: {}", className, methodName);
		}
		if (null == annotation || annotation.isOpen()) {//没加注解||开启过滤,走过滤
			where = appendDataPermission(whereSql);
		}
		return where;
	}

	/**
	 * where条件增强,追加数据权限
	 */
	private Expression appendDataPermission(StringBuilder whereSql) {
		// 1.获取当前用户所属公司及下级公司id
		List<String> companyByPermission = AuthCompanyUtil.getCompanyByPermission();
		if (companyByPermission.isEmpty()) {
			log.info("当前用户没有数据权限,不进行增强");
			return new HexValue(whereSql.toString());
		}

		StringBuilder createDeptsString = new StringBuilder();
		createDeptsString.append(Func.join(companyByPermission, ","));

		String enhancedSql = String.format("%s and create_dept in (%s)", whereSql, createDeptsString);
		log.info("增强后where条件: {}", enhancedSql);
		return new HexValue(enhancedSql);
	}


}

3、权限处理

继续上代码

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class MybatisPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {

    /**
     * 数据权限处理器
     */
    private MybatisPermissionHandler permissionHandler;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            this.setWhere((PlainSelect) selectBody, (String) obj);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOperationList = (SetOperationList) selectBody;
            List<SelectBody> selectBodyList = setOperationList.getSelects();
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }

    /**
     * 设置 where 条件
     *
     * @param plainSelect  查询对象
     * @param whereSegment 查询条件片段
     */
    private void setWhere(PlainSelect plainSelect, String whereSegment) {
        Expression sqlSegment = this.permissionHandler.getSqlSegment(plainSelect, whereSegment);
        if (null != sqlSegment) {
            plainSelect.setWhere(sqlSegment);
        }
    }
}

三、写在最后

方法有针对性,可根据需求进行更改,如有其他好的方法也可以分享在评论中。

有什么奇葩的需求也可以写在留言中,咱们一起探讨,然后进行封装简化(懒)

最后的最后CV万岁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值