首页 > 其他分享 >数据权限

数据权限

时间:2023-06-16 13:57:43浏览次数:40  
标签:deptId pi admin 权限 数据 id

本文源码地址

Gitee GitHub
后端 https://gitee.com/linjiabin100/pi-admin.git https://github.com/zengpi/pi-admin.git
前端 https://gitee.com/linjiabin100/pi-admin-web.git https://github.com/zengpi/pi-admin-web.git

数据行级权限是指不同的用户只能访问特定条件下的数据行。比如,部门经理可以查看其部门下的所有数据,而普通员工只能查看他自己提交的数据。

要在 pi-admin 中使用行级权限,你需要以下步骤:

  1. 数据库中定义与数据行级权限相关联的列

    数据权限相关的表中需要创建关联的列,比如部门、部门及下级部门、自定义部门等数据权限类型需要用到列 dept_id,本人数据权限类型需要用到 user_id

    `dept_id` bigint unsigned DEFAULT NULL COMMENT '部门 ID',
    `user_id` bigint unsigned DEFAULT NULL COMMENT '用户 ID',
    

    在插入时需要维护这些列的值。

  2. 在 Mapper 接口中指定支持数据权限的方法。

    @DataPermission({
        @DataPermissionItem(type = DataPermissionTypeEnum.SELF, 
                            @DataPermissionColumn(key = "userId", value = "user_id"))
        @DataPermissionItem(type = DataPermissionTypeEnum.DEPT_AND_CHILD, 
                            @DataPermissionColumn(key = "deptId", value = "dept_id"))
    })
    TestVO listTest(@Param("p1") ParamDTO p1);
    

    在以上代码中,listTest 支持两种数据权限类型:DataPermissionTypeEnum.SELFDataPermissionTypeEnum.DEPT_AND_CHILD,分别表示本人和部门及下级部门。

    扫描所有的 mapper 接口的时机是在程序启动时,这极大的提高了程序运行时执行的效率。有关如何在程序启动时扫描 mapper 的相关信息,请参阅数据权限实现原理

  3. 在角色管理中给角色配置角色范围,拥有对应角色的的用户就拥有对应的权限:

以上配置了拥有部门经理的用户执行 listTest 时能查看其部门及下级部门的数据。

对 MyBatis-Plus BaseMapper 中提供的原生方法进行过滤

对于 MyBatis-Plus 提供的原生的方法,需要先重写该方法,再对该方法进行注解:

DataPermissionTypeEnum

me.pi.admin.common.mybatis.enums.DataPermissionTypeEnum 中定义了数据权限类型:

/**
     * 全部
     */
ALL(1),
/**
     * 部门
     */
DEPT(2, "#{#deptId} = #{#data.deptId}", "dept_id = #{#data.deptId}"),
/**
     * 部门及下级部门
     */
DEPT_AND_CHILD(3, "#{#deptId} IN (#{@dps.getDeptAndChildId(#data.deptId)})",
               "dept_id IN (#{@dps.getDeptAndChildId(#data.deptId)})"),
/**
     * 自定义部门
     */
CUSTOM_DEPT(4, "#{#deptId} IN (#{@dps.getDeptIdsByRoleId(#data.roleId)})",
            "dept_id IN (#{@dps.getDeptIdsByRoleId(#data.roleId)})"),
/**
     * 本人
     */
SELF(5, "#{#userId} = #{#data.id}", "user_id = #{#data.userId}");

数据权限模板支持 spel 表达式,以 DEPT_AND_CHILD 为例,3 表示权限类型代码;#{#deptId} IN (#{@dps.getDeptAndChildId(#data.deptId)})表示数据权限 sql 模板;dept_id IN (#{@dps.getDeptAndChildId(#data.deptId)}) 表示默认 sql 模板(不需要指定 deptId 与数据库中列名的对应关系)。

数据范围可以通过 me.pi.admin.common.mybatis.annotation.DataPermissionItem 注解在 Mapper 接口的方法上指定,它接收两个参数:

public @interface DataPermissionItem {
    // 1. 数据权限类型
    DataPermissionTypeEnum type();
    // 2. 数据权限模板变量与列名的映射
    DataPermissionColumn column() default @DataPermissionColumn(key = "", value = "");
}

在上面的代码中, type 用于指定数据权限的类型,column 用于指定模板中的 key 对应的数据库表中的列名。

当数据权限配置如下,表示数据权限范围为部门及下级部门,sql 会被填充成 dept_id IN (#{@dps.getDeptAndChildId(#data.deptId)})

@DataPermission({
    @DataPermissionItem(type = DataPermissionTypeEnum.DEPT_AND_CHILD, 
                        @DataPermissionColumn(key = "deptId", value = "dept_id"))
})
TestVO listTest(@Param("p1") ParamDTO p1);

你可以更改生效的列,比如在数据库中表示部门 ID 的列名为 did,则可以通过指定 value 改变它,最终生成的 sql 为 did IN (#{@dps.getDeptAndChildId(#data.deptId)})

@DataPermission({
    @DataPermissionItem(type = DataPermissionTypeEnum.DEPT_AND_CHILD, 
                        @DataPermissionColumn(key = "deptId", value = "did"))
})
TestVO listTest(@Param("p1") ParamDTO p1);

如果没有指定 @DataPermissionColumn(key = "deptId", value = "dept_id"),则会使用默认 sql 模板,也就是 dept_id IN (#{@dps.getDeptAndChildId(#data.deptId)})

@DataPermission({
    @DataPermissionItem(type = DataPermissionTypeEnum.DEPT_AND_CHILD)
})
TestVO listTest(@Param("p1") ParamDTO p1);

#{@dps.getDeptAndChildId(#data.deptId)} 中的 @dps 表示 Spring 中的一个名为 dps 的 bean,它的类型为 me.pi.admin.core.system.service.DataPermissionService

public interface DataPermissionService {
    /**
     * 通过部门 ID 获取当前部门及下级部门的部门 ID 列表,以逗号分隔
     *
     * @param deptId 当前部门 ID
     * @return 当前部门及下级部门的部门 ID 列表,以逗号分隔
     */
    String getDeptAndChildId(Long deptId);

    /**
     * 通过角色 ID 获取部门 ID 列表
     * @param roleId 角色 ID
     * @return 部门 ID 列表
     */
    String getDeptIdsByRoleId(Long roleId);
}

通过 @Service("dps") 指定 bean 的名称。

扩展 DataPermissionData

对于模板中的 #data.deptId 的值则由 me.pi.admin.common.mybatis.handler.DataPermissionData 定义,如果需要扩展,只能修改源代码:

public class DataPermissionData {
    /**
     * 用户 ID
     */
    private Long userId;
    /**
     * 部门 ID
     */
    private Long deptId;
    /**
     * 角色 ID
     */
    private Long roleId;
}

赋值的逻辑在 me.pi.admin.common.mybatis.handler.PiDataPermissionHandler#getSqlConditions 中:

实现原理

实现数据权限的关键是拦截待执行的 SQL,动态添加查询条件,实现过滤数据的目的。要实现这一点,可以借助 MyBatis-Plus 的拦截器。

关键代码

类型 作用
me.pi.admin.common.mybatis.interceptor.PiDataPermissionInterceptor 数据权限拦截器
me.pi.admin.common.mybatis.handler.PiDataPermissionHandler 数据权限处理器
me.pi.admin.common.mybatis.enums.DataPermissionTypeEnum 数据权限类型枚举
me.pi.admin.common.mybatis.annotation.DataPermission 数据权限注解
me.pi.admin.common.mybatis.annotation.DataPermissionItem 配置应用的数据权限各类型
me.pi.admin.common.mybatis.annotation.DataPermissionColumn 指定数据权限模板中的 key 与数据库列的映射

扫描需要支持数据权限的 mappedStatement

程序启动时扫描所有的 mapper 接口,如果 mapper 接口的方法上标注了 me.pi.admin.common.mybatis.annotation.DataPermission 注解,则表明支持数据权限:

如何获取所有 mapper 接口?MyBatis-Plus 在启动时扫描了 mapper 接口,并缓存了起来,可以通过 sqlSessionFactory 获取:

Collection<Class<?>> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();

另外,通过反射可以获取方法上的注解信息,保存在 PiDataPermissionHandlerannotationCaches 中:

/**
 * 数据权限注解扫描器
 *
 * @author ZnPi
 * @date 2023-05-06
 */
@RequiredArgsConstructor
public class DataPermissionScanner implements CommandLineRunner {
    private final SqlSessionFactory sqlSessionFactory;
    private final PiDataPermissionHandler dataPermissionHandler;

    @Override
    public void run(String... args) {
        Collection<Class<?>> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();
        mappers.forEach(mapper -> {
            Map<Method, Annotation> methodsAnnotation = AnnotationScanner.getMethodsAnnotation(mapper, DataPermission.class);
            methodsAnnotation.forEach((method, annotation) -> {
                String key = method.getDeclaringClass().getName() + "." + method.getName();
                // 将解析结果保存在 PiDataPermissionHandler 的 annotationCaches 中
                dataPermissionHandler.getAnnotationCaches().put(key, annotation);
            });
        });
    }
}
/**
  * 获取指定类上的方法上标注了指定注解的方法以及注解
  *
  * @param clazz      指定类的字节码
  * @param annotation 注解
  * @return 定类上的方法上标注了指定注解的方法以及注解
  */
public static Map<Method, Annotation> getMethodsAnnotation(
    Class<?> clazz, Class<? extends Annotation> annotation) {
    Map<Method, Annotation> methodAnnotationMap = new HashMap<>();
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method method : declaredMethods) {
        if (method.isAnnotationPresent(annotation)) {
            methodAnnotationMap.put(method, method.getAnnotation(annotation));
        }
    }
    return methodAnnotationMap;
}

MyBatis-Plus 拦截器

数据权限拦截器可以参考 com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor 的实现:

  • 实现 com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor 接口。

    public interface InnerInterceptor {
       protected void processSelect(Select select, int index, String sql, Object obj) {
            throw new UnsupportedOperationException();
        }
        
        protected void processUpdate(Update update, int index, String sql, Object obj) {
            throw new UnsupportedOperationException();
        }
        
        protected void processUpdate(Update update, int index, String sql, Object obj) {
            throw new UnsupportedOperationException();
        }
    }
    
  • 继承 com.baomidou.mybatisplus.extension.parser.JsqlParserSupport

    /**
     * https://github.com/JSQLParser/JSqlParser
     */
    public abstract class JsqlParserSupport {
        
    }
    

拦截器实现请查看 me.pi.admin.common.mybatis.interceptor.PiDataPermissionInterceptor

获取数据权限范围

数据权限范围的配置方式请参考数据行级权限一节,源码请参考 me.pi.admin.common.mybatis.handler.PiDataPermissionHandler#getSqlConditions

// 获取注解中的数据权限项
List<DataPermissionItem> dataPermissionItems = Arrays.stream(annotation.value())
    .filter(dataPermissionItem ->
            dataPermissionType.getCode().equals(dataPermissionItem.type().getCode()))
    .collect(Collectors.toList());
if (dataPermissionItems.isEmpty()) {
    continue;
}

String sqlTemplate;

DataPermissionItem dataPermissionItem = dataPermissionItems.get(0);
if (!StringUtils.hasText(dataPermissionItem.column().key())) {
    // 未指定列,使用默认值
    sqlTemplate = dataPermissionType.getDefaultSqlTemplate();
} else {
    // 设置的 key 与模板不匹配
    if (!dataPermissionType.getSqlTemplate().contains(dataPermissionItem.column().key())) {
        continue;
    }

    sqlTemplate = dataPermissionType.getSqlTemplate();
    standardEvaluationContext.setVariable(dataPermissionItem.column().key(),
                                          dataPermissionItem.column().value());
}

标签:deptId,pi,admin,权限,数据,id
From: https://www.cnblogs.com/zn-pi/p/17485342.html

相关文章

  • Python数据类型-列表与元组
    #题目1:删除如下列表中的"矮穷丑",写出2种或以上方法:#info=["yuze",18,"男","矮穷丑",["高","富","帅"],True,None,"狼的眼睛是啥样的"]info=["yuze",18,"男","矮穷丑",["......
  • JavaScript 变量和数据类型
    JavaScript变量和数据类型变量在JavaScript中,变量用于存储和操作数据。声明一个变量需要使用关键字var、let或const。1.使用var声明变量varname='John';varage=28;var关键字可以被同一作用域内的其他代码访问到,而不受块级作用域的限制。var声明的变量可......
  • 使用chrome扩展程序爬取地图数据
    偶然,想爬取城市所有的公交和地铁线路。其实通过8684网站就可以爬取到了。但是好像不够完整,就想从高德地图抓取。阿里的产品也太难了。对新手而言,只会简单的post请求显然不足以完成任务。其实不管什么网站,抽象起来就是获取数据,保存,分析而已。对简单的任务,爬虫用什么语言,就用该语......
  • b GaussDB数据库开发设计
    一使用IAM账户登录二创建gaussDB1首先进入控制台-vpc-左手边选择访问限制-点击安全组-选择已创建好的,点击后面配置规则选择【入方向规则】,单击【添加规则】,具体如下:8000端口放开成功。2创建GaussDB数据库进入GaussDB(foropenGauss)服务,点击左侧的【服务列表】,选择其中【数据......
  • 系统架构设计师笔记第16期:数据库基本概念
    数据库技术的发展数据库技术在过去几十年中经历了显著的发展和演变。层次数据库和网状数据库:20世纪60年代和70年代初,层次数据库和网状数据库是主流的数据库模型。层次数据库使用树状结构组织数据,而网状数据库使用复杂的网络结构。这些数据库模型适用于特定的数据组织和查询需求,但缺......
  • solr 模拟数据库like查询(不使用分词)
    IK分词个别拆分的不够完美,另外个别业务逻辑是需要替代数据库的like查询。所以本篇文章是介绍如何在solr中使用类似数据库的like查询。本片文章是介绍如何在solr中使用类似数据库的like操作。首先我们抛弃text_ik。IK分词,因为使用的是like操作,所以这块不能在使用分词了。我们需......
  • 【RS】哨兵系列数据下载(新手教程)
    ​        学遥感的避免不了使用哨兵数据,毕竟10m的分辨率可以满足大部分的定量分析,同时也是最重要的一点,它免费!!!今天以哨兵二号为示例,教大家如何下载哨兵数据。 哨兵-1卫星是全天时、全天候雷达成像任务,用于陆地和海洋观测,首颗哨兵-1A卫星已于2014年4月3日发射。哨兵-2......
  • django项目更换数据库
    背景:在公司写的django项目跑的好好的,但是数据库所在的服务器电脑被搞挂了(也不知道被人安装什么了,服务起不起来了,只能重新安装数据库,django项目关联新的数据库),已有的项目要尽快恢复(原先的数据没办法找回了),只能重新关联一个。这里数据库安装不再赘述(一定一定要装linux系统的数据......
  • 2022年 12 月 Tita 升级|管理员权限全新升级!
    升级快速一览:·【管理员授权】全新的「权限组」授权模式,支持定制更复杂的管理角色·【系统影响】精准定位管理范围与权限,满足企业管理需求点击免费领取绩效考核模版等资料升级详情一、管理员授权页面升级,全新的「权限组」授权模式,支持企业灵活自定的管理角色界面改动......
  • golang之数据验证validator
    https://blog.csdn.net/guyan0319/article/details/105918559/前言在web应用中经常会遇到数据验证问题,普通的验证方法比较繁琐,这里介绍一个使用比较多的包validator。原理将验证规则写在struct对字段tag里,再通过反射(reflect)获取struct的tag,实现数据验证。安装gogetgithub.co......