飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • AngularJS教程
  • UEditor使用文档
  • ThinkPHP5.0教程

SpringCloud微服务实战——搭建企业级开发框架(二十八):扩展MybatisPlus插件DataPermissionInterceptor实现数据权限控

时间:2021-12-03  作者:FullStackProgrammer  

一套完整的系统权限需要支持功能权限和数据权限,前面介绍了系统通过RBAC的权限模型来实现功能的权限控制,这里我们来介绍,通过扩展Mybatis-Plus的插件DataPermissionInterceptor实现数据权限控制。

简单介绍一下,所谓功能权限,顾名思义是指用户在系统中拥有对哪些功能操作的权限控制,而数据权限是指用户在系统中能够访问哪些数据的权限控制,数据权限又分为行级数据权限和列级数据权限。

数据权限基本概念:

  • 行级数据权限:以表结构为描述对象,一个用户拥有对哪些数据的权限,表示为对数据库某个表整行的数据拥有权限,例如按部门区分,某一行数据属于某个部门,某个用户只对此部门的数据拥有权限,那么该用户拥有此行的数据权限。
  • 列级数据权限:以表结构为描述对象,一个用户可能只对某个表中的部分字段拥有权限,例如表中银行卡、手机号等重要信息只有高级用户能够查询,而一些基本信息,普通用户就可以查询,不同的用户角色拥有的数据权限不一样。

实现方式:

  • 行级数据权限:
      对行级数据权限进行细分,以角色为标识的数据权限,分为:
      1、只能查看本人数据;
      2、只能查看本部门数据;
      3、只能查看本部门及子部门数据;
      4、可以查看所有部门数据;
      以用户为标识的数据权限,分为:
      5、同一功能角色权限拥有不同部门的数据权限;
      6、不同角色权限拥有不同部门的数据权限。
      第1/2/3/4类的实现方式需要在角色列表对角色进行数据权限配置,针对某一接口该角色拥有哪种数据权限。
      第5类的实现方式,需要在用户列表进行配置,给用户分配多个不同部门。
      第6类的实现方式比较复杂,目前有市面上的大多数解决方案是:
        1、在登录时,判断用户是否拥有多个部门,如果存在,那么首先让用户选择其所在的部门,登录后只对选择的部门权限进行操作;
        2、针对不同部门创建不同的用户及角色,登录时,选择对应的账号进行登录。
      个人因秉承复杂的系统简单化,尽量用低耦合的方式实现复杂功能的理念,更倾向于第二种方式,原因是:
1、系统实现方面减少复杂度,越复杂的判断,越容易出问题,不仅仅在开发过程中,还在于后续系统的扩展和更新过程中。
2、对于工作量方面的取舍,一个人拥有多个部门不同权限的方式属于常用功能,但是并不普遍,也就是说在一家企业中,同一个用户即是业务部门经理,又是财务部门经理的情况并不普遍,更多的是专人专职。这里要和第5类做好区分,比如你是业务部门经理可能会管理多个部门,这种属于权限一致,只是拥有多个部门权限,这属于第5类。再比如一个总经理,可能会看到所有的业务、财务数据这属于第4类。
所以这里不会采取用户登录后选择部门的方式来判断数据权限。
  • 列级数据权限:
      列级数据权限的实现主要是针对某个角色能够看到哪些字段,不存在针对某个用户给他特定字段的情况,这种情况单独建立一个角色即可,尽量采用类RBAC的方式来实现,不要使用户直接和数据权限关联。列级数据权限除了要考虑后台取数据的问题,还要考虑到在界面上展示时,如果是一个表格,那么没有权限的列需要根据数据权限来判断是否展示。这里在配置界面就要考虑,在角色配置时,需要分为行级数据权限和列级数据权限进行不同的配置:行级数据权限应该配置需要数据权限控制的接口,数据权限的类型(上面提到的1234);列级数据权限除了需要配置上面提到的之外,还需要配置可以访问的字段或者排除访问的字段。

数据权限

在资源管理配置资源关联接口的数据权限规则(t_sys_data_permission_role),通过RBAC的方式用角色和用户关联,在用户管理配置用户同角色的多个部门数据权限,用户直接和部门关联(t_sys_data_permission_user)。系统数据权限管理功能设计如下所示:

权限管理

数据权限表设计:
CREATE TABLE `t_sys_data_permission_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'主键\',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT \'租户id\',
  `user_id` bigint(20) NOT NULL COMMENT \'用户id\',
  `organization_id` bigint(20) NOT NULL COMMENT \'机构id\',
  `status` tinyint(2) NULL DEFAULT 1 COMMENT \'状态 0禁用,1 启用,\',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT \'创建时间\',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT \'创建者\',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT \'更新时间\',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT \'更新者\',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT \'1:删除 0:不删除\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_data_permission_role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'主键\',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT \'租户id\',
  `resource_id` bigint(20) NOT NULL DEFAULT 0 COMMENT \'功能权限id\',
  `data_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限名称\',
  `data_mapper_function` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限对应的mapper方法全路径\',
  `data_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'需要做数据权限主表\',
  `data_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'需要做数据权限表的别名\',
  `data_column_exclude` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限需要排除的字段\',
  `data_column_include` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限需要保留的字段\',
  `inner_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限表,默认t_sys_organization\',
  `inner_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'数据权限表的别名,默认organization\',
  `data_permission_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT \'1\' COMMENT \'数据权限类型:1只能查看本人 2只能查看本部门 3只能查看本部门及子部门 4可以查看所有数据\',
  `custom_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'自定义数据权限(增加 where条件)\',
  `status` tinyint(2) NOT NULL DEFAULT 1 COMMENT \'状态 0禁用,1 启用,\',
  `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'备注\',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT \'创建时间\',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT \'创建者\',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT \'更新时间\',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT \'更新者\',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT \'1:删除 0:不删除\',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = \'数据权限配置表\' ROW_FORMAT = DYNAMIC;

数据权限缓存(Redis)设计:
  • Redis Key:
    多租户模式:auth:tenant:data:permission:0(租户):mapper_Mapper全路径_type_数据权限类型
    普通模式:auth:data:permission:mapper_Mapper全路径_type_数据权限类型
  • Redis Value:存放角色分配的DataPermissionEntity配置
      数据权限插件在组装SQL时,首先通过前缀匹配查询mapper的statementId是否在缓存中,如果存在,那么取出当前用户的数据权限类型,组装好带有数据权限类型的DataPermission缓存Key,从缓存中取出数据权限配置。
    在设计角色时,除了需要给角色设置功能权限之外,还要设置数据权限类型,角色的数据权限类型只能单选(1只能查看本人 2只能查看本部门 3只能查看本部门及子部门 4可以查看所有数据5自定义)
代码实现:
  • 因DataPermissionInterceptor默认不支持修改selectItems,导致无法做到列级别的数据权限,所以这里自定义扩展DataPermissionInterceptor,使其支持列级权限扩展
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GitEggDataPermissionInterceptor extends DataPermissionInterceptor {

    private GitEggDataPermissionHandler dataPermissionHandler;

    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (!域名IgnoreDataPermission(域名d())) {
            域名undSql mpBs = 域名undSql(boundSql);
            域名(域名erSingle(域名(), 域名d()));
        }
    }

    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = 域名electBody();
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect)selectBody;
            域名essDataPermission(plainSelect, (String)obj);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOperationList = (SetOperationList)selectBody;
            List<selectbody> selectBodyList = 域名elects();
            域名ach((s) -> {
                PlainSelect plainSelect = (PlainSelect)s;
                域名essDataPermission(plainSelect, (String)obj);
            });
        }

    }

    protected void processDataPermission(PlainSelect plainSelect, String whereSegment) {
        域名essDataPermission(plainSelect, whereSegment);
    }

}
  • 自定义实现DataPermissionHandler数据权限控制
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class GitEggDataPermissionHandler implements DataPermissionHandler {

    @Value(("${域名le}"))
    private Boolean enable;

    /**
     * 注解方式默认关闭,这里只是说明一种实现方式,实际使用时,使用配置的方式即可
     */
    @Value(("${data-域名tation-enable}"))
    private Boolean annotationEnable = false;

    private final RedisTemplate redisTemplate;

    public void processDataPermission(PlainSelect plainSelect, String mappedStatementId) {
        try {
            GitEggUser loginUser = 域名urrentUser();
            // 1 当有数据权限配置时才去判断用户是否有数据权限控制
            if (域名tEmpty(loginUser) && 域名tEmpty(域名ataPermissionTypeList())) {
                // 1 根据系统配置的数据权限拼装sql
                StringBuffer statementSb = new StringBuffer();
                if (enable)
                {
                    域名nd(域名NT_DATA_PERMISSION_KEY).append(域名enantId());
                }
                else
                {
                    域名nd(域名_PERMISSION_KEY);
                }
                String dataPermissionKey = 域名ring();
                StringBuffer statementSbt = new StringBuffer(域名_PERMISSION_KEY_MAPPER);
                域名nd(mappedStatementId).append(域名_PERMISSION_KEY_TYPE);
                String mappedStatementIdKey = 域名ring();
                DataPermissionEntity dataPermissionEntity = null;
                for (String dataPermissionType: 域名ataPermissionTypeList())
                {
                    String dataPermissionUserKey = mappedStatementIdKey + dataPermissionType;
                    dataPermissionEntity = (DataPermissionEntity) 域名dHashOps(dataPermissionKey).get(dataPermissionUserKey);
                    if (域名tEmpty(dataPermissionEntity)) {
                        break;
                    }
                }
                // mappedStatementId是否有配置数据权限
                if (域名tEmpty(dataPermissionEntity))
                {
                    dataPermissionFilter(loginUser, dataPermissionEntity, plainSelect);
                }
                //默认不开启注解,因每次查询都遍历注解,影响性能,直接选择使用配置的方式实现数据权限即可
                else if(annotationEnable)
                {
                    // 2 根据注解的数据权限拼装sql
                    Class<!--?--> clazz = 域名ame(域名tring(域名, 域名IndexOf(域名)));
                    String methodName = 域名tring(域名IndexOf(域名) + 域名);
                    Method[] methods = 域名eclaredMethods();
                    for (Method method : methods) {
                        //当有多个时,这个方法可以获取到
                        DataPermission[] annotations = 域名nnotationsByType(域名s);
                        if (域名tEmpty(annotations) && 域名ame().equals(methodName)) {
                            for (DataPermission dataPermission : annotations) {
                                String dataPermissionType = 域名PermissionType();
                                for (String dataPermissionUser : 域名ataPermissionTypeList()) {
                                    if (域名tEmpty(dataPermission) && 域名tEmpty(dataPermissionType)
                                            && 域名ls(dataPermissionType)) {
                                        DataPermissionEntity dataPermissionEntityAnnotation = annotationToEntity(dataPermission);
                                        dataPermissionFilter(loginUser, dataPermissionEntityAnnotation, plainSelect);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

        } catch (ClassNotFoundException e) {
            域名tStackTrace();
        }
    }

    /**
     * 构建过滤条件
     *
     * @param user 当前登录用户
     * @param plainSelect plainSelect
     * @return 构建后查询条件
     */
    public static void dataPermissionFilter(GitEggUser user, DataPermissionEntity dataPermissionEntity, PlainSelect plainSelect) {
        Expression expression = 域名here();
        String dataPermissionType = 域名ataPermissionType();
        String dataTableName = 域名ataTableName();
        String dataTableAlias = 域名ataTableAlias();

        String innerTableName = 域名tEmpty(域名nnerTableName()) ? 域名nnerTableName(): 域名_PERMISSION_TABLE_NAME;
        String innerTableAlias = 域名tEmpty(域名nnerTableAlias()) ? 域名nnerTableAlias() : 域名_PERMISSION_TABLE_ALIAS_NAME;

        List<string> organizationIdList = 域名rganizationIdList();

        // 列级数据权限
        String dataColumnExclude = 域名ataColumnExclude();
        String dataColumnInclude = 域名ataColumnInclude();
        List<string> includeColumns = new ArrayList<>();
        List<string> excludeColumns = new ArrayList<>();
        // 只包含这几个字段,也就是不是这几个字段的,直接删除
        if (域名tEmpty(dataColumnInclude))
        {
            includeColumns = 域名st(域名t(域名A));
        }

        // 需要排除这几个字段
        if (域名tEmpty(dataColumnExclude))
        {
            excludeColumns = 域名st(域名t(域名A));
        }
        List<selectitem> selectItems = 域名electItems();
        List<selectitem> removeItems = new ArrayList<>();
        if (域名tEmpty(selectItems)
                && (域名tEmpty(includeColumns) || 域名tEmpty(excludeColumns))) {
            for (SelectItem selectItem : selectItems) {
                // 暂不处理其他类型的selectItem
                if (selectItem instanceof SelectExpressionItem) {
                    SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
                    Alias alias = 域名lias();
                    if ((域名tEmpty(includeColumns) && !域名ains(域名ame()))
                            || (!域名pty(excludeColumns) && 域名ains(域名ame())))
                    {
                        域名(selectItem);
                    }
                } else if (selectItem instanceof AllTableColumns) {
                    域名(selectItem);
                }
            }
            if (域名tEmpty(removeItems))
            {
                域名veAll(removeItems);
                域名electItems(selectItems);
            }
        }

        // 行级数据权限
        // 查询用户机构和子机构的数据,这里是使用where条件添加子查询的方式来实现的,这样的实现方式好处是不需要判断Update,Insert还是Select,都是通用的,缺点是性能问题。
        if (域名evel().equals(dataPermissionType)) {
            // 如果是table的话,那么直接加inner,如果不是,那么直接在where条件里加子查询
            if (域名romItem() instanceof Table)
            {
                Table fromTable = (Table)域名romItem();
                //数据主表
                Table dataTable = null;
                //inner数据权限表
                Table innerTable = null;
                if (域名ame().equalsIgnoreCase(dataTableName))
                {
                    dataTable = (Table)域名romItem();
                }

                // 如果是查询,这里使用inner join关联过滤,不使用子查询,因为join不需要建立临时表,因此速度比子查询快。
                List<join> joins = 域名oins();
                boolean hasPermissionTable = false;
                if (域名tEmpty(joins)) {
                    Iterator joinsIterator = 域名ator();
                    while(域名ext()) {
                        Join join = (Join)域名();
                        // 判断join里面是否存在t_sys_organization表,如果存在,那么直接使用,如果不存在则新增
                        FromItem rightItem = 域名ightItem();
                        if (rightItem instanceof Table) {
                            Table table = (Table)rightItem;
                            // 判断需要inner的主表是否存在
                            if (null == dataTable && 域名ame().equalsIgnoreCase(dataTableName))
                            {
                                dataTable = table;
                            }

                            // 判断需要inner的表是否存在
                            if (域名ame().equalsIgnoreCase(innerTableName))
                            {
                                hasPermissionTable = true;
                                innerTable = table;
                            }
                        }
                    }
                }

                //如果没有找到数据主表,那么直接抛出异常
                if (null == dataTable)
                {
                    throw new BusinessException("在SQL语句中没有找到数据权限配置的主表,数据权限过滤失败。");
                }

                //如果不存在这个table,那么新增一个innerjoin
                if (!hasPermissionTable)
                {
                    innerTable = new Table(innerTableName).withAlias(new Alias(innerTableAlias, false));
                    Join join = new Join();
                    域名RightItem(innerTable);
                    EqualsTo equalsTo = new EqualsTo();
                    域名eftExpression(new Column(dataTable, 域名_PERMISSION_ORGANIZATION_ID));
                    域名ightExpression(new Column(innerTable, 域名_PERMISSION_ID));
                    域名OnExpression(equalsTo);
                    域名oins(join);
                }

                EqualsTo equalsToWhere = new EqualsTo();
                域名eftExpression(new Column(innerTable, 域名_PERMISSION_ID));
                域名ightExpression(new LongValue(域名rganizationId()));
                Function function = new Function();
                域名ame(域名_PERMISSION_FIND_IN_SET);
                域名arameters(new ExpressionList(new LongValue(域名rganizationId()) , new Column(innerTable, 域名_PERMISSION_ANCESTORS)));
                OrExpression orExpression = new OrExpression(equalsToWhere, function);
                //判断是否有数据权限,如果有数据权限配置,那么添加数据权限的机构列表
                if(域名tEmpty(organizationIdList))
                {
                    for (String organizationId : organizationIdList)
                    {
                        EqualsTo equalsToPermission = new EqualsTo();
                        域名eftExpression(new Column(innerTable, 域名_PERMISSION_ID));
                        域名ightExpression(new LongValue(organizationId));
                        orExpression = new OrExpression(orExpression, equalsToPermission);
                        Function functionPermission = new Function();
                        域名ame(域名_PERMISSION_FIND_IN_SET);
                        域名arameters(new ExpressionList(new LongValue(organizationId) , new Column(innerTable,域名_PERMISSION_ANCESTORS)));
                        orExpression = new OrExpression(orExpression, functionPermission);
                    }
                }
                expression = 域名tEmpty(expression) ? new AndExpression(expression, new Parenthesis(orExpression)) : orExpression;
                域名here(expression);
            }
            else
            {
                InExpression inExpression = new InExpression();
                域名eftExpression(buildColumn(dataTableAlias, 域名_PERMISSION_ORGANIZATION_ID));
                SubSelect subSelect = new SubSelect();
                PlainSelect select = new PlainSelect();
                域名electItems(域名letonList(new SelectExpressionItem(new Column(域名_PERMISSION_ID))));
                域名romItem(new Table(域名_PERMISSION_TABLE_NAME));
                EqualsTo equalsTo = new EqualsTo();
                域名eftExpression(new Column(域名_PERMISSION_ID));
                域名ightExpression(new LongValue(域名rganizationId()));
                Function function = new Function();
                域名ame(域名_PERMISSION_FIND_IN_SET);
                域名arameters(new ExpressionList(new LongValue(域名rganizationId()) , new Column(域名_PERMISSION_ANCESTORS)));
                OrExpression orExpression = new OrExpression(equalsTo, function);

                //判断是否有数据权限,如果有数据权限配置,那么添加数据权限的机构列表
                if(域名tEmpty(organizationIdList))
                {
                    for (String organizationId : organizationIdList)
                    {
                        EqualsTo equalsToPermission = new EqualsTo();
                        域名eftExpression(new Column(域名_PERMISSION_ID));
                        域名ightExpression(new LongValue(organizationId));
                        orExpression = new OrExpression(orExpression, equalsToPermission);
                        Function functionPermission = new Function();
                        域名ame(域名_PERMISSION_FIND_IN_SET);
                        域名arameters(new ExpressionList(new LongValue(organizationId) , new Column(域名_PERMISSION_ANCESTORS)));
                        orExpression = new OrExpression(orExpression, functionPermission);
                    }
                }
                域名here(orExpression);
                域名electBody(select);
                域名ightExpression(subSelect);
                expression = 域名tEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;
                域名here(expression);
            }
        }
        // 只查询用户拥有机构的数据,不包含子机构
        else if (域名evel().equals(dataPermissionType)) {
            InExpression inExpression = new InExpression();
            域名eftExpression(buildColumn(dataTableAlias, 域名_PERMISSION_ORGANIZATION_ID));
            ExpressionList expressionList = new ExpressionList();
            List<expression> expressions = new ArrayList<>();
            域名(new LongValue(域名rganizationId()));
            if(域名tEmpty(organizationIdList))
            {
                for (String organizationId : organizationIdList)
                {
                    域名(new LongValue(organizationId));
                }
            }
            域名xpressions(expressions);
            域名ightItemsList(expressionList);
            expression = 域名tEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;
            域名here(expression);

        }
        // 只能查询个人数据
        else if (域名evel().equals(dataPermissionType)) {
            EqualsTo equalsTo = new EqualsTo();
            域名eftExpression(buildColumn(dataTableAlias, 域名_PERMISSION_SELF));
            域名ightExpression(new StringValue(域名eOf(域名d())));
            expression = 域名tEmpty(expression) ? new AndExpression(expression, new Parenthesis(equalsTo)) : equalsTo;
            域名here(expression);
        }
        //当类型为查看所有数据时,不处理
//        if (域名ype().equals(dataPermissionType)) {
//
//        }
        // 自定义过滤语句
        else if (域名evel().equals(dataPermissionType)) {
            String customExpression = 域名ustomExpression();
            if (域名pty(customExpression))
            {
                throw new BusinessException("没有配置自定义表达式");
            }
            try {
                Expression expressionCustom = 域名eCondExpression(customExpression);
                expression = 域名tEmpty(expression) ? new AndExpression(expression, new Parenthesis(expressionCustom)) : expressionCustom;
                域名here(expression);
            } catch (JSQLParserException e) {
                throw new BusinessException("自定义表达式配置错误");
            }
        }
    }

    /**
     * 构建Column
     *
     * @param dataTableAlias 表别名
     * @param columnName 字段名称
     * @return 带表别名字段
     */
    public static Column buildColumn(String dataTableAlias, String columnName) {
        if (域名tEmpty(dataTableAlias)) {
            columnName = dataTableAlias + 域名 + columnName;
        }
        return new Column(columnName);
    }


    /**
     * 注解转为实体类
     * @param annotation 注解
     * @return 实体类
     */
    public static DataPermissionEntity annotationToEntity(DataPermission annotation) {
        DataPermissionEntity dataPermissionEntity = new DataPermissionEntity();
        域名ataPermissionType(域名PermissionType());
        域名ataColumnExclude(域名ColumnExclude());
        域名ataColumnInclude(域名ColumnInclude());
        域名ataTableName(域名TableName());
        域名ataTableAlias(域名TableAlias());
        域名nnerTableName(域名rTableName());
        域名nnerTableAlias(域名rTableAlias());
        域名ustomExpression(域名omExpression());
        return dataPermissionEntity;
    }

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        return null;
    }
  • 系统启动时初始化数据权限配置到Redis
    @Override
    public void initDataRolePermissions() {
        List<datapermissionroledto> dataPermissionRoleList = 域名yDataPermissionRoleListAll();
        // 判断是否开启了租户模式,如果开启了,那么角色权限需要按租户进行分类存储
        if (enable) {
            Map<long, list<datapermissionroledto="">> dataPermissionRoleListMap =
                    域名am().collect(域名pingBy(DataPermissionRoleDTO::getTenantId));
            域名ach((key, value) -> {
                // auth:tenant:data:permission:0
                String redisKey = 域名NT_DATA_PERMISSION_KEY + key;
                域名te(redisKey);
                addDataRolePermissions(redisKey, value);
            });
        } else {
            域名te(域名_PERMISSION_KEY);
            // auth:data:permission
            addDataRolePermissions(域名_PERMISSION_KEY, dataPermissionRoleList);
        }
    }

    private void addDataRolePermissions(String key, List<datapermissionroledto> dataPermissionRoleList) {
        Map<string, datapermissionentity=""> dataPermissionMap = new TreeMap<>();
        域名llable(dataPermissionRoleList).orElse(new ArrayList<>()).forEach(dataPermissionRole -> {
            String dataRolePermissionCache = new StringBuffer(域名_PERMISSION_KEY_MAPPER)
                    .append(域名ataMapperFunction()).append(域名_PERMISSION_KEY_TYPE)
                    .append(域名ataPermissionType()).toString();
            DataPermissionEntity dataPermissionEntity = 域名ByClass(dataPermissionRole, 域名s);
            域名(dataRolePermissionCache, dataPermissionEntity);
        });
        域名dHashOps(key).putAll(dataPermissionMap);
    }
数据权限配置指南:

域名

  • 数据权限名称:自定义一个名称,方便查找和区分
  • Mapper全路径: Mapper路径配置到具体方法名称,例:域名域名域名ctUserList
  • 数据权限类型:
    只能查看本人(实现原理是在查询条件添加数据表的creator条件)
    只能查看本部门 (实现原理是在查询条件添加数据表的部门条件)
    只能查看本部门及子部门 (实现原理是在查询条件添加数据表的部门条件)
    可以查看所有数据(不处理)
    自定义(添加where子条件)
注解配置数据权限配置指南:
    /**
     * 查询用户列表
     * @param page
     * @param user
     * @return
     */
    @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "3", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
    @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "2", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
    @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "1", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission")
    Page<userinfo> selectUserList(Page<userinfo> page, @Param("user") QueryUserDTO user);
行级数据权限配置:

数据主表:主数据表,用于数据操作时的主表,例如SQL语句时的主表
数据主表别名:主数据表的别名,用于和数据权限表进行inner join操作
数据权限表:用于inner join的数据权限表,主要用于使用ancestors字段查询所有子组织机构
数据权限表别名:用于和主数据表进行inner join

列级数据权限配置:

排除的字段:配置没有权限查看的字段,需要排除这些字段
保留的字段:配置有权限查看的字段,只保留这些字段

备注:
  • 此数据权限设计较灵活,也较复杂,有些简单应用场景的系统可能根本用不到,只需配置行级数据权限即可。
  • Mybatis-Plus的插件DataPermissionInterceptor使用说明 https://域名/baomidou/mybatis-plus/issues/I37I90
  • update,insert逻辑说明:inner时只支持正常查询,及inner查询,不支持子查询,update,insert,子查询等直接使用添加子查询的方式实现数据权限
  • 还有在这里说明一下,在我们实际业务开发过程中,只能查看本人数据的数据权限,一般不会通过系统来配置,而是在业务代码编写过程中就 会实现,比如查询个人订单接口,那么个人用户id肯定是接口的入参,在接口被请求的时候,只需要通过我们自定义的方法获取到当前登录用户,然后作为参数传入即可。这种对于个人数据的数据权限,通过业务代码来实现会更加方便和安全,且没有太多的工作量,方便理解也容易扩展。
源码地址: 

Gitee: https://域名/wmz1930/GitEgg

GitHub: https://域名/wmz1930/GitEgg

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。