新增:新增角色数据权限功能(基于 MyBatis Plus DataPermissionInterceptor 插件实现)

1.基于 MyBatis Plus DataPermissionInterceptor 插件实现的数据权限功能
2.通过在指定 Mapper 接口层方法添加 @DataPermission 注解实现数据权限
This commit is contained in:
2023-03-07 23:55:24 +08:00
parent 5f4a9abec6
commit fb0effed9a
17 changed files with 400 additions and 18 deletions

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限注解
*
* @author Charles7c
* @since 2023/3/6 23:34
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
/**
* 表别名
*/
String value() default "";
}

View File

@@ -47,6 +47,17 @@ public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.Base
return Db.saveBatch(entityList);
}
/**
* 批量更新记录
*
* @param entityList
* 实体列表
* @return 是否成功
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
}
/**
* 链式查询
*

View File

@@ -0,0 +1,187 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.config.mybatis;
import java.lang.reflect.Method;
import java.util.Collections;
import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import top.charles7c.cnadmin.common.annotation.DataPermission;
import top.charles7c.cnadmin.common.constant.StringConsts;
import top.charles7c.cnadmin.common.enums.DataScopeEnum;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.model.dto.RoleDTO;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SubSelect;
/**
* 数据权限处理器实现类
* <p>
* 来源:<a href="https://gitee.com/baomidou/mybatis-plus/issues/I37I90">DataPermissionInterceptor 如何使用?</a>
* </p>
*
* @author Charles7c
* @since 2023/3/6 23:19
*/
@Slf4j
public class DataPermissionHandlerImpl implements DataPermissionHandler {
/** ID */
private static final String ID = "id";
/** 部门 ID */
private static final String DEPT_ID = "dept_id";
/** 创建人 */
private static final String CREATE_USER = "create_user";
/** 部门表 */
private static final String DEPT_TABLE = "sys_dept";
/** 角色和部门关联表 */
private static final String ROLE_DEPT_TABLE = "sys_role_dept";
/** 角色和部门关联表:角色 ID */
private static final String ROLE_ID = "role_id";
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
Class<?> clazz =
Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringConsts.DOT)));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringConsts.DOT) + 1);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
DataPermission annotation = method.getAnnotation(DataPermission.class);
if (ObjectUtils.isNotEmpty(annotation)
&& (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
// 获取当前登录用户
LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtils.isNotEmpty(loginUser) && !loginUser.isAdmin()) {
return buildDataScopeFilter(loginUser, annotation.value(), where);
}
}
}
} catch (ClassNotFoundException e) {
log.error("Data permission handler build data scope filter occurred an error: {}.", e.getMessage(), e);
}
return where;
}
/**
* 构建数据范围过滤条件
*
* @param user
* 当前登录用户
* @param tableAlias
* 表别名
* @param where
* 当前查询条件
* @return 构建后查询条件
*/
private static Expression buildDataScopeFilter(LoginUser user, String tableAlias, Expression where) {
Expression expression = null;
for (RoleDTO role : user.getRoleSet()) {
DataScopeEnum dataScope = role.getDataScope();
if (DataScopeEnum.ALL.equals(dataScope)) {
return where;
}
if (DataScopeEnum.DEPT_AND_CHILD.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` in (select `id` from `sys_dept` where `id` = xxx or
// find_in_set(xxx, `ancestors`));
// 构建子查询
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(ID))));
select.setFromItem(new Table(DEPT_TABLE));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(ID));
equalsTo.setRightExpression(new LongValue(user.getDeptId()));
Function function = new Function();
function.setName("find_in_set");
function.setParameters(new ExpressionList(new LongValue(user.getDeptId()), new Column("ancestors")));
select.setWhere(new OrExpression(equalsTo, function));
subSelect.setSelectBody(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(buildColumn(tableAlias, DEPT_ID));
inExpression.setRightExpression(subSelect);
expression =
ObjectUtils.isNotEmpty(expression) ? new OrExpression(expression, inExpression) : inExpression;
} else if (DataScopeEnum.DEPT.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` = xxx;
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(buildColumn(tableAlias, DEPT_ID));
equalsTo.setRightExpression(new LongValue(user.getDeptId()));
expression = ObjectUtils.isNotEmpty(expression) ? new OrExpression(expression, equalsTo) : equalsTo;
} else if (DataScopeEnum.SELF.equals(dataScope)) {
// select t1.* from table as t1 where t1.`create_user` = xxx;
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(buildColumn(tableAlias, CREATE_USER));
equalsTo.setRightExpression(new LongValue(user.getId()));
expression = ObjectUtils.isNotEmpty(expression) ? new OrExpression(expression, equalsTo) : equalsTo;
} else if (DataScopeEnum.CUSTOM.equals(dataScope)) {
// select t1.* from table as t1 where t1.`dept_id` in (select `dept_id` from `sys_role_dept` where
// `role_id` = xxx);
// 构建子查询
SubSelect subSelect = new SubSelect();
PlainSelect select = new PlainSelect();
select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(DEPT_ID))));
select.setFromItem(new Table(ROLE_DEPT_TABLE));
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(new Column(ROLE_ID));
equalsTo.setRightExpression(new LongValue(role.getId()));
select.setWhere(equalsTo);
subSelect.setSelectBody(select);
// 构建父查询
InExpression inExpression = new InExpression();
inExpression.setLeftExpression(buildColumn(tableAlias, DEPT_ID));
inExpression.setRightExpression(subSelect);
expression =
ObjectUtils.isNotEmpty(expression) ? new OrExpression(expression, inExpression) : inExpression;
}
}
return ObjectUtils.isNotEmpty(where) ? new AndExpression(where, new Parenthesis(expression)) : expression;
}
/**
* 构建 Column
*
* @param tableAlias
* 表别名
* @param columnName
* 字段名称
* @return 带表别名字段
*/
private static Column buildColumn(String tableAlias, String columnName) {
if (StringUtils.isNotEmpty(tableAlias)) {
columnName = String.format("%s.%s", tableAlias, columnName);
}
return new Column(columnName);
}
}

View File

@@ -26,6 +26,7 @@ import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import cn.hutool.core.net.NetUtil;
@@ -48,6 +49,8 @@ public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataPermissionHandlerImpl()));
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 防全表更新与删除插件

View File

@@ -29,20 +29,20 @@ import lombok.NoArgsConstructor;
public class SysConsts {
/**
* 超级管理员角色编码
* 管理员角色编码
*/
public static final String SUPER_ADMIN = "admin";
/**
* 全部权限标识
*/
public static final String ALL_PERMISSION = "*";
public static final String ADMIN_ROLE_CODE = "admin";
/**
* 顶级父 ID
*/
public static final Long SUPER_PARENT_ID = 0L;
/**
* 全部权限标识
*/
public static final String ALL_PERMISSION = "*";
/**
* 默认密码
*/

View File

@@ -22,6 +22,9 @@ import java.util.Set;
import lombok.Data;
import cn.hutool.core.collection.CollUtil;
import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.enums.GenderEnum;
/**
@@ -129,4 +132,21 @@ public class LoginUser implements Serializable {
* 角色编码集合
*/
private Set<String> roles;
/**
* 角色集合
*/
private Set<RoleDTO> roleSet;
/**
* 是否为管理员
*
* @return truefalse
*/
public boolean isAdmin() {
if (CollUtil.isEmpty(roles)) {
return false;
}
return roles.contains(SysConsts.ADMIN_ROLE_CODE);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.model.dto;
import java.io.Serializable;
import lombok.Data;
import top.charles7c.cnadmin.common.enums.DataScopeEnum;
/**
* 角色信息
*
* @author Charles7c
* @since 2023/3/7 22:08
*/
@Data
public class RoleDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 角色编码
*/
private String code;
/**
* 数据权限1全部数据权限2本部门及以下数据权限3本部门数据权限4仅本人数据权限5自定义数据权限
*/
private DataScopeEnum dataScope;
}