• 数据权限技术调研


    什么是数据权限

    数据权限无非就是某人只能看到某些数据,这些数据是可能是属于自己直接操作的,也可能是间接操作的。

        场景1、 业务员A在业务员B某个订单上查看了某某客户对某某产品的销售单价,在某某搜搜框中搜出其他业务员的订单信息,可以随意的查看其他人的订单信息,其他人的业务员客户信息,其他人负责的产品信息,这无疑不是对系统健全的挑战。
       场景2、 某业务主管看到公司某产品的平均毛利率,某客户的平均毛利率,重则带着下面一群小弟出去创业,称为公司的竞争对手,这无疑会对公司造成损失。

    某业务员A想知道自己有哪些客户,于是打开了客户管理界面,看到的客户全是自己相关的,假如没有数据权限的设定 ,会看到有部分不属于自己负责客户。如何统计出某业务主管自己部门销售量多的销售呢? ​

    有哪些数据权限

    数据权限可以为后期操作做出等级的分级。CRM系统中数据权限有五种:

    一、全部,当选择全部时,该员工会看到所有的数据信息,多用于超级管理员或总裁级别;

    二、本部门及下属部门,该员工会看到本部门的及所有子部门的数据信息,多用于总监级别;

    三、仅本部门,该员工只能看到本部门的数据信息,多用于部门经理级别;

    四、自己发布或参与的,该员工只能看到和自己有关的数据信息,不会看到别人的数据信息,多用于普通员工;

    五、自定义部门,后期该员工可以看到自定义的部门有关的数据信息。

    如何实现数据权限

    1.数据权限是和角色进行绑定的

    对应的页面是:系统管理->权限管理->角色管理

    2.后端使用AOP+自定义注解实现数据权限

    2.1 利用sql实现数据权限

    2.1.1 举例:假如我要查询线索表里的数据

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    ORDER BY
        clue.create_time DESC

    2.1.2 全部数据权限的SQL

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    ORDER BY
        clue.create_time DESC

    在全部数据权限下,什么都不用修改

    2.1.3仅本人数据权限的SQL

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    AND (r.user_id = 1014)
    ORDER BY
        clue.create_time DESC

    可见,对于本人数据权限的sql来说多了 AND (r.user_id = 1014)

    2.1.4具有本部门下所有数据权限:

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    AND (r.dept_id = 213)
    ORDER BY
        clue.create_time DESC

    可见,对于本部门数据权限的sql来说多了 AND (r.dept_id = 213)

    2.1.5具有本部门及以下数据权限SQL

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    AND (
        r.dept_id IN (
            SELECT
                dept_id
            FROM
                sys_dept
            WHERE
                dept_id = 213
            OR find_in_set(213, ancestors)
        )
    )
    ORDER BY
        clue.create_time DESC

    由于查询的是部门及以下,需要关联到部门表

    相比与全部数据权限多了

    AND (
        r.dept_id IN (
            SELECT
                dept_id
            FROM
                sys_dept
            WHERE
                dept_id = 213
            OR find_in_set(213, ancestors)
        )
    )

    2.1.6具有自定义数据权限SQL

    SELECT
        clue.*,
        r.create_by AS assign_by,
        r.user_name AS OWNER,
        r.create_time AS owner_time
    FROM
        tb_clue clue
    LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
    WHERE
        r.latest = '1'
    AND r.type = '0'
    AND clue. STATUS IN ('1', '2')
    AND (
        r.dept_id IN (
            SELECT
                dept_id
            FROM
                sys_role_dept
            WHERE
                role_id = 113
        )
    )
    ORDER BY
        clue.create_time DESC

    相比与全部数据权限多了

    AND (
        r.dept_id IN (
            SELECT
                dept_id
            FROM
                sys_role_dept
            WHERE
                role_id = 113
        )
    )

    2.1.7 总结

    可以看出,数据权限就是在原有的sql上拼接关于数据权限的sql

    2.2自定义数据权限注解@DataScope

    package com.huike.common.annotation;

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
     * 数据权限过滤注解
     * 
     * 
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataScope
    {
        /**
         * 部门表的别名
         */
        public String deptAlias() default "";

        /**
         * 用户表的别名
         */
        public String userAlias() default "";
    }

    2.3利用aop实现该注解

    2.3.1 确定切入点

        // 配置织入点
        @Pointcut("@annotation(com.huike.common.annotation.DataScope)")
        public void dataScopePointCut(){
        }

    2.3.2 利用通知类型拼接sql

    在方法执行前拼接sql,由于是方法执行前,使用@before

    @Before("dataScopePointCut()")
        public void doBefore(JoinPoint point) throws Throwable
        {
            handleDataScope(point);
        }

        protected void handleDataScope(final JoinPoint joinPoint)
        {
            // 获得注解
            DataScope controllerDataScope = getAnnotationLog(joinPoint);
            if (controllerDataScope == null)
            {
                return;
            }
            // 获取当前的用户
            LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
            if (StringUtils.isNotNull(loginUser))
            {
                SysUser currentUser = loginUser.getUser();
                // 如果是超级管理员,则不过滤数据
                if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
                {
                    dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                            controllerDataScope.userAlias());
                }
            }
        }

        /**
         * 数据范围过滤
         *
         * @param joinPoint 切点
         * @param user 用户
         * @param userAlias 别名
         */
        public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
        {
            System.out.println("过滤数据---------------");
            StringBuilder sqlString = new StringBuilder();

            for (SysRole role : user.getRoles())
            {
                String dataScope = role.getDataScope();
                if (DATA_SCOPE_ALL.equals(dataScope))
                {
                    sqlString = new StringBuilder();
                    break;
                }
                else if (DATA_SCOPE_CUSTOM.equals(dataScope))
                {
                    sqlString.append(StringUtils.format(
                            " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                            role.getRoleId()));
                }
                else if (DATA_SCOPE_DEPT.equals(dataScope))
                {
                    sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
                }
                else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
                {
                    sqlString.append(StringUtils.format(
                            " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                            deptAlias, user.getDeptId(), user.getDeptId()));
                }
                else if (DATA_SCOPE_SELF.equals(dataScope))
                {
                    if (StringUtils.isNotBlank(userAlias))
                    {
                        sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                    }
                    else
                    {
                        // 数据权限为仅本人且没有userAlias别名不查询任何数据
                        sqlString.append(" OR 1=0 ");
                    }
                }
            }

            if (StringUtils.isNotBlank(sqlString.toString()))
            {
                Object params = joinPoint.getArgs()[0];
                if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
                {
                    BaseEntity baseEntity = (BaseEntity) params;
                    baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
                }
            }
        }

        /**
         * 是否存在注解,如果存在就获取
         */
        private DataScope getAnnotationLog(JoinPoint joinPoint)
        {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();

            if (method != null)
            {
                return method.getAnnotation(DataScope.class);
            }
            return null;
        } 

    2.4 业务代码例子

    业务层方法,加上@DataScope

    /**
        * 查询线索管理列表
        * 
        * @param tbClue 线索管理
        * @return 线索管理
    */
    @Override
    @DataScope(deptAlias = "r", userAlias = "r")
    public List selectTbClueList(TbClue tbClue) {
        return tbClueMapper.selectTbClueList(tbClue);
    }

    数据访问层XML文件例子

  • 相关阅读:
    深入浅出学Verilog--数据类型
    基于 jetpack compose,使用MVI架构+自定义布局实现的康威生命游戏
    k8s部署gateway、nacos 、app通过网关访问app出现404
    mysql大数据量 分页查询优化
    【华为OD机试真题 python】 5键键盘【2022 Q4 | 100分】
    Cookie简介
    小林coding网站---mysql基础-MySQL索引的数据结构和算法
    C语言 做一个学生信息管理系统
    Centos安装gitlabce
    【一篇让你学会】Web接口测试工具--Jmeter
  • 原文地址:https://blog.csdn.net/weixin_69413377/article/details/126583843