一、背景
做事之前先问一下为什么。
(废话)首先问一下为什么这样做,当然是需求所致。
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万岁