当前位置: 首页 > 图灵资讯 > 技术篇> SpringCloud微服务实战——搭建企业级开发框架(二十八):扩展MybatisPlus插件DataPermissionInterceptor实现数据权限控制

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

来源:图灵教育
时间:2023-10-20 17:53:22

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

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

数据权限的基本概念:

  • 行级数据权限:以表结构为描述对象,用户有哪些数据权限,表示数据库数据权限,如根据部门,行数据属于部门,用户只有部门数据权限,所以用户有行数据权限。
  • 列数据权限:以表结构为描述对象,用户可能只有一部分字段,如银行卡、手机号码等重要信息只有高级用户可以查询,一些基本信息,普通用户可以查询,不同的用户角色有不同的数据权限。

实现方式:

  • 行级数据权限:  对行级数据权限进行细分,以角色为标识的数据权限分为:  1、只能查看自己的数据;  2、只能查看本部门的数据;  3、只能查看本部门和子部门的数据;  4、可查看所有部门的数据;  以用户为标识的数据权限,分为:  5、具有相同功能的角色权限具有不同部门的数据权限;  6、不同的角色权限有不同部门的数据权限。

  第1/2/3/4类的实现模式需要在角色列表中配置角色的数据权限,以及角色在某个接口中拥有哪些数据权限。  第五种实现方式需要在用户列表中配置,并将多个不同的部门分配给用户。  第六类的实现方法比较复杂。目前市场上的解决方案大多是:    1、在登录时,判断用户是否有多个部门。如果存在,请先让用户选择自己的部门,登录后只操作所选部门的权限;    2、为不同部门创建不同的用户和角色,登录时,选择相应的帐户进行登录。  个人更倾向于第二种方式,因为他们坚持复杂的系统简化,试图以低耦合的方式实现复杂功能的概念,因为:

  1、降低系统实现的复杂性,判断越复杂,越容易出现问题,不仅在开发过程中,而且在后续系统的扩展和更新过程中。  2、对于工作量的选择,一个人拥有多个部门不同权限的方式是一种常见的功能,但并不常见,也就是说,在一个企业中,同一用户是业务部门经理和财务部门经理并不常见,更多的是全职的。这里要区分第五类。比如你是业务部门经理,可能会管理多个部门,属于权限一致,只有多个部门权限,属于第五类。另一个例子是总经理,他可能会看到所有的业务和财务数据都属于第四类。  因此,用户登录后不会选择部门来判断数据权限。
  • 等级数据权限:  列级数据权限的实现主要是针对角色可以看到的字段,没有用户给他特定的字段。在这种情况下,可以单独建立一个角色,并尝试使用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只能查看自己 只能查看本部门 33只能检查本部门和子部门 所有数据都可以查看,  `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:Datapermisionentity配置存储角色分配  组装SQL时,数据权限插件首先通过前缀匹配查询maper的statementid是否在缓存中。如果存在,取出当前用户的数据权限类型,组装带有数据权限类型的datapermision缓存Key,数据权限配置从缓存中取出。在设计角色时,除了为角色设置功能权限外,还需要设置数据权限类型。角色的数据权限类型只能单独选择(1) 只能查看本部门 33只能检查本部门和子部门 4可查看所有数据5的自定义。
代码实现:
  • 由于Datapermisioninterceptor默认不支持修改selectitems,因此无法实现列级数据权限,因此Datapermisioninterceptor自定义扩展,使其支持列级权限扩展
@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 (!InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {            PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);            mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));        }    }    protected void processSelect(Select select, int index, String sql, Object obj) {        SelectBody selectBody = select.getSelectBody();        if (selectBody instanceof PlainSelect) {            PlainSelect plainSelect = (PlainSelect)selectBody;            this.processDataPermission(plainSelect, (String)obj);        } else if (selectBody instanceof SetOperationList) {            SetOperationList setOperationList = (SetOperationList)selectBody;            List<SelectBody> selectBodyList = setOperationList.getSelects();            selectBodyList.forEach((s) -> {                PlainSelect plainSelect = (PlainSelect)s;                this.processDataPermission(plainSelect, (String)obj);            });        }    }    protected void processDataPermission(PlainSelect plainSelect, String whereSegment) {        this.dataPermissionHandler.processDataPermission(plainSelect, whereSegment);    }}
  • DatapermisionHandler数据权限控制自定义
@Component@RequiredArgsConstructor(onConstructor_ = @Autowired)public class GitEggDataPermissionHandler implements DataPermissionHandler {    @Value(("${tenant.enable}"))    private Boolean enable;    /**     * 默认关闭注解方法,这里只说明一种实现方法,实际使用时,使用配置的方式     */    @Value(("${data-permission.annotation-enable}"))    private Boolean annotationEnable = false;    private final RedisTemplate redisTemplate;    public void processDataPermission(PlainSelect plainSelect, String mappedStatementId) {        try {            GitEggUser loginUser = GitEggAuthUtils.getCurrentUser();            // 1 当有数据权限配置时,判断用户是否有数据权限控制            if (ObjectUtils.isNotEmpty(loginUser) && CollectionUtils.isNotEmpty(loginUser.getDataPermissionTypeList())) {                // 1 sql根据系统配置的数据权限组装                StringBuffer statementSb = new StringBuffer();                if (enable)                {                    statementSb.append(DataPermissionConstant.TENANT_DATA_PERMISSION_KEY).append(loginUser.getTenantId());                }                else                {                    statementSb.append(DataPermissionConstant.DATA_PERMISSION_KEY);                }                String dataPermissionKey = statementSb.toString();                StringBuffer statementSbt = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER);                statementSbt.append(mappedStatementId).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE);                String mappedStatementIdKey = statementSbt.toString();                DataPermissionEntity dataPermissionEntity = null;                for (String dataPermissionType: loginUser.getDataPermissionTypeList())                {                    String dataPermissionUserKey = mappedStatementIdKey + dataPermissionType;                    dataPermissionEntity = (DataPermissionEntity) redisTemplate.boundHashOps(dataPermissionKey).get(dataPermissionUserKey);                    if (ObjectUtils.isNotEmpty(dataPermissionEntity)) {                        break;                    }                }                // mappedstatementid是否配置数据权限                if (ObjectUtils.isNotEmpty(dataPermissionEntity))                {                    dataPermissionFilter(loginUser, dataPermissionEntity, plainSelect);                }                ///默认不打开注释,因为每次查询都会影响性能,直接选择使用配置来实现数据权限                else if(annotationEnable)                {                    // 2 sql根据注释的数据权限组装                    Class<?> clazz = Class.forName(mappedStatementId.substring(GitEggConstant.Number.ZERO, mappedStatementId.lastIndexOf(StringPool.DOT)));                    String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + GitEggConstant.Number.ONE);                    Method[] methods = clazz.getDeclaredMethods();                    for (Method method : methods) {                        //当有多种方法时,可以获得此方法                        DataPermission[] annotations = method.getAnnotationsByType(DataPermission.class);                        if (ObjectUtils.isNotEmpty(annotations) && method.getName().equals(methodName)) {                            for (DataPermission dataPermission : annotations) {                                String dataPermissionType = dataPermission.dataPermissionType();                                for (String dataPermissionUser : loginUser.getDataPermissionTypeList()) {                                    if (ObjectUtils.isNotEmpty(dataPermission) && StringUtils.isNotEmpty(dataPermissionType)                                            && dataPermissionUser.equals(dataPermissionType)) {                                        DataPermissionEntity dataPermissionEntityAnnotation = annotationToEntity(dataPermission);                                        dataPermissionFilter(loginUser, dataPermissionEntityAnnotation, plainSelect);                                        break;                                    }                                }                            }                        }                    }                }            }        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }    /**     * 构建过滤条件     *     * @param user 目前登录用户     * @param plainSelect plainSelect     * @return 施工后查询条件     */    public static void dataPermissionFilter(GitEggUser user, DataPermissionEntity dataPermissionEntity, PlainSelect plainSelect) {        Expression expression = plainSelect.getWhere();        String dataPermissionType = dataPermissionEntity.getDataPermissionType();        String dataTableName = dataPermissionEntity.getDataTableName();        String dataTableAlias = dataPermissionEntity.getDataTableAlias();        String innerTableName = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableName()) ? dataPermissionEntity.getInnerTableName(): DataPermissionConstant.DATA_PERMISSION_TABLE_NAME;        String innerTableAlias = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableAlias()) ? dataPermissionEntity.getInnerTableAlias() : DataPermissionConstant.DATA_PERMISSION_TABLE_ALIAS_NAME;        List<String> organizationIdList = user.getOrganizationIdList();        // 列级数据权限        String dataColumnExclude = dataPermissionEntity.getDataColumnExclude();        String dataColumnInclude = dataPermissionEntity.getDataColumnInclude();        List<String> includeColumns = new ArrayList<>();        List<String> excludeColumns = new ArrayList<>();        // 只包含这些字段,即不是这些字段,直接删除        if (StringUtils.isNotEmpty(dataColumnInclude))        {            includeColumns = Arrays.asList(dataColumnInclude.split(StringPool.COMMA));        }        // 这些字段需要排除        if (StringUtils.isNotEmpty(dataColumnExclude))        {            excludeColumns = Arrays.asList(dataColumnExclude.split(StringPool.COMMA));        }        List<SelectItem> selectItems = plainSelect.getSelectItems();        List<SelectItem> removeItems = new ArrayList<>();        if (CollectionUtils.isNotEmpty(selectItems)                && (CollectionUtils.isNotEmpty(includeColumns) 
|| CollectionUtils.isNotEmpty(excludeColumns))) {            for (SelectItem selectItem : selectItems) {                // 其他类型的selectitem暂时不处理                if (selectItem instanceof SelectExpressionItem) {                    SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;                    Alias alias = selectExpressionItem.getAlias();                    if ((CollectionUtils.isNotEmpty(includeColumns) && !includeColumns.contains(alias.getName()))                            || (!CollectionUtils.isEmpty(excludeColumns) && excludeColumns.contains(alias.getName())))                    {                        removeItems.add(selectItem);                    }                } else if (selectItem instanceof AllTableColumns) {                    removeItems.add(selectItem);                }            }            if (CollectionUtils.isNotEmpty(removeItems))            {                selectItems.removeAll(removeItems);                plainSelect.setSelectItems(selectItems);            }        }        // 行级数据权限        // 查询用户机构和子机构的数据,这是通过查询where条件添加子来实现的。这种实现方式的优点是不需要判断Update、Insert还是Select,这些都是通用的,缺点是性能问题。        if (DataPermissionTypeEnum.DATA_PERMISSION_ORG_AND_CHILD.getLevel().equals(dataPermissionType)) {            // 如果是table,那么直接加inner,如果不是,然后直接在where条件下加子查询            if (plainSelect.getFromItem() instanceof Table)            {                Table fromTable = (Table)plainSelect.getFromItem();                ///数据主表                Table dataTable = null;                /////                Table innerTable = null;                if (fromTable.getName().equalsIgnoreCase(dataTableName))                {                    dataTable = (Table)plainSelect.getFromItem();                }                // 如果是查询,在这里使用inner join关联过滤,不使用子查询,因为join不需要建立临时表,所以速度比子查询快。                List<Join> joins = plainSelect.getJoins();                boolean hasPermissionTable = false;                if (CollectionUtils.isNotEmpty(joins)) {                    Iterator joinsIterator = joins.iterator();                    while(joinsIterator.hasNext()) {                        Join join = (Join)joinsIterator.next();                        // 判断join中是否存在t_sys_organization表,若存在,则直接使用,若不存在,则新增                        FromItem rightItem = join.getRightItem();                        if (rightItem instanceof Table) {                            Table table = (Table)rightItem;                            // 判断inner的主表是否存在                            if (null == dataTable && table.getName().equalsIgnoreCase(dataTableName))                            {                                dataTable = table;                            }                            // 判断inner是否存在需要inner的表                            if (table.getName().equalsIgnoreCase(innerTableName))                            {                                hasPermissionTable = true;                                innerTable = table;                            }                        }                    }                }                //如果没有找到数据主表,然后直接抛出异常                if (null == dataTable)                {                    throw new BusinessException("数据权限配置的主表未在SQL语句中找到,数据权限过滤失败。");                }                ///如果没有这个table,则添加一个新的innerjoinin                if (!hasPermissionTable)                {                    innerTable = new Table(innerTableName).withAlias(new Alias(innerTableAlias, false));                    Join join = new Join();                    join.withRightItem(innerTable);                    EqualsTo equalsTo = new EqualsTo();                    equalsTo.setLeftExpression(new Column(dataTable, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));                    equalsTo.setRightExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));                    join.withOnExpression(equalsTo);                    plainSelect.addJoins(join);                }                EqualsTo equalsToWhere = new EqualsTo();                equalsToWhere.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));                equalsToWhere.setRightExpression(new LongValue(user.getOrganizationId()));                Function function = new Function();                function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);                function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));                OrExpression orExpression = new OrExpression(equalsToWhere, function);                如果有数据权限配置,//判断是否有数据权限,所以添加数据权限的机构列表                if(CollectionUtils.isNotEmpty(organizationIdList))                {                    for (String organizationId : organizationIdList)                    {                        EqualsTo equalsToPermission = new EqualsTo();                        equalsToPermission.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID));                        equalsToPermission.setRightExpression(new LongValue(organizationId));                        orExpression = new OrExpression(orExpression, equalsToPermission);                        Function functionPermission = new Function();                        functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);                        functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(innerTable,DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));                        orExpression = new OrExpression(orExpression, functionPermission);                    }                }                expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(orExpression)) : orExpression;                plainSelect.setWhere(expression);            }            else            {                InExpression inExpression = new InExpression();                inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));                SubSelect subSelect = new SubSelect();                PlainSelect select = new PlainSelect();                select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(DataPermissionConstant.DATA_PERMISSION_ID))));                select.setFromItem(new Table(DataPermissionConstant.DATA_PERMISSION_TABLE_NAME));                EqualsTo equalsTo = new EqualsTo();                equalsTo.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID));                equalsTo.setRightExpression(new LongValue(user.getOrganizationId()));                Function function = new Function();                function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);                function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));                OrExpression orExpression = new OrExpression(equalsTo, function);                如果有数据权限配置,//判断是否有数据权限,所以添加数据权限的机构列表                if(CollectionUtils.isNotEmpty(organizationIdList))                {                    for (String organizationId : organizationIdList)                    {                        EqualsTo equalsToPermission = new EqualsTo();                        equalsToPermission.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID));                        equalsToPermission.setRightExpression(new LongValue(organizationId));                        orExpression = new OrExpression(orExpression, equalsToPermission);                        Function functionPermission = new Function();                        functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET);                        functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS)));                        orExpression = new OrExpression(orExpression, functionPermission);                    }                }                select.setWhere(orExpression);                subSelect.setSelectBody(select);                inExpression.setRightExpression(subSelect);                expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;                plainSelect.setWhere(expression);            }        }        // 只查询用户拥有机构的数据,不包括子机构        else if (DataPermissionTypeEnum.DATA_PERMISSION_ORG.getLevel().equals(dataPermissionType)) {            InExpression inExpression = new InExpression();            inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID));            ExpressionList expressionList = new ExpressionList();            List<Expression> expressions = new ArrayList<>();            expressions.add(new LongValue(user.getOrganizationId()));            if(CollectionUtils.isNotEmpty(organizationIdList))            {                for (String organizationId : organizationIdList)                {                    expressions.add(new LongValue(organizationId));                }            }            expressionList.setExpressions(expressions);            inExpression.setRightItemsList(expressionList);            expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression;            plainSelect.setWhere(expression);        }        // 个人数据只能查询        else if (DataPermissionTypeEnum.DATA_PERMISSION_SELF.getLevel().equals(dataPermissionType)) {            EqualsTo equalsTo = new EqualsTo();            equalsTo.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_SELF));            equalsTo.setRightExpression(new StringValue(String.valueOf(user.getId())));            expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(equalsTo)) : equalsTo;            plainSelect.setWhere(expression);        }        //当类型是查看所有数据时,不处理//        if (DataPermissionTypeEnum.DATA_PERMISSION_ALL.getType().equals(dataPermissionType)) {////        }        // 定制过滤语句        else if (DataPermissionTypeEnum.DATA_PERMISSION_CUSTOM.getLevel().equals(dataPermissionType)) {            String customExpression = dataPermissionEntity.getCustomExpression();            if (StringUtils.isEmpty(customExpression))            {                throw new BusinessException("没有配置自定义表达式表达式");            }            try {                Expression expressionCustom = CCJSqlParserUtil.parseCondExpression(customExpression);                expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(expressionCustom)) : expressionCustom;                plainSelect.setWhere(expression);            } catch (JSQLParserException e) {                throw new BusinessException("自定义表达式配置错误");            }        }    }    /**     * 构建Column     *     * @param dataTableAlias 表别名     * @param columnName 字段名称     * @return 带表别名字段     */    public static Column buildColumn(String dataTableAlias, String columnName) {        if (StringUtils.isNotEmpty(dataTableAlias)) {            columnName = dataTableAlias + StringPool.DOT + columnName;        }        return new Column(columnName);    }    /**     * 注释转化为实体类     * @param annotation 注解     * @return 实体类     */    public static DataPermissionEntity annotationToEntity(DataPermission annotation) {        DataPermissionEntity dataPermissionEntity = new DataPermissionEntity();        dataPermissionEntity.setDataPermissionType(annotation.dataPermissionType());        dataPermissionEntity.setDataColumnExclude(annotation.dataColumnExclude());        dataPermissionEntity.setDataColumnInclude(annotation.dataColumnInclude());        dataPermissionEntity.setDataTableName(annotation.dataTableName());        dataPermissionEntity.setDataTableAlias(annotation.dataTableAlias());        dataPermissionEntity.setInnerTableName(annotation.innerTableName());        dataPermissionEntity.setInnerTableAlias(annotation.innerTableAlias());        dataPermissionEntity.setCustomExpression(annotation.customExpression());        return dataPermissionEntity;    }    @Override    public Expression getSqlSegment(Expression where, String mappedStatementId) {        return null;    }
  • 当系统启动时,将初始化数据权限配置到Rediss
    @Override    public void initDataRolePermissions() {        List<DataPermissionRoleDTO> dataPermissionRoleList = dataPermissionRoleMapper.queryDataPermissionRoleListAll();        // 判断租户模式是否开启,如果打开,角色权限需要根据租户进行分类存储        if (enable) {            Map<Long, List<DataPermissionRoleDTO>> dataPermissionRoleListMap =                    dataPermissionRoleList.stream().collect(Collectors.groupingBy(DataPermissionRoleDTO::getTenantId));            dataPermissionRoleListMap.forEach((key, value) -> {                // auth:tenant:data:permission:0                String redisKey = DataPermissionConstant.TENANT_DATA_PERMISSION_KEY + key;                redisTemplate.delete(redisKey);                addDataRolePermissions(redisKey, value);            });        } else {            redisTemplate.delete(DataPermissionConstant.DATA_PERMISSION_KEY);            // auth:data:permission            addDataRolePermissions(DataPermissionConstant.DATA_PERMISSION_KEY, dataPermissionRoleList);        }    }    private void addDataRolePermissions(String key, List<DataPermissionRoleDTO> dataPermissionRoleList) {        Map<String, DataPermissionEntity> dataPermissionMap = new TreeMap<>();        Optional.ofNullable(dataPermissionRoleList).orElse(new ArrayList<>()).forEach(dataPermissionRole -> {            String dataRolePermissionCache = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER)                    .append(dataPermissionRole.getDataMapperFunction()).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE)                    .append(dataPermissionRole.getDataPermissionType()).toString();            DataPermissionEntity dataPermissionEntity = BeanCopierUtils.copyByClass(dataPermissionRole, DataPermissionEntity.class);            dataPermissionMap.put(dataRolePermissionCache, dataPermissionEntity);        });        redisTemplate.boundHashOps(key).putAll(dataPermissionMap);    }
数据权限配置指南:

image.png

  • 数据权限名称:自定义名称,便于搜索和区分
  • Mapper全路径: 将Mapper路径配置到具体的方法名称,例如:com.gitegg.service.system.mapper.UserMapper.selectUserList
  • 数据权限类型:只能查看本人(实现原则是在查询条件下添加数据表的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字段查询所有子组织机构的数据权限表别名:与主数据表一起使用iner join

列级数据权限配置:

排除的字段:配置无权查看的字段,排除这些字段保留的字段:配置有权查看的字段,只保留这些字段

备注:
  • 这种数据权限设计灵活复杂,一些简单的应用场景系统可能根本不需要,只需要配置行级数据权限即可。
  • Mybatis-DatapermisioninterceptorPlus插件使用说明 https://gitee.com/baomidou/mybatis-plus/issues/I37I90
  • update,insert逻辑说明:inner只支持正常查询,inner查询,不支持子查询,update,insert,直接使用添加子查询实现子查询的数据权限
  • 此外,在我们的实际业务开发过程中,我们只能查看我们的数据权限。一般来说,我们不会通过系统进行配置,而是在编写业务代码的过程中 例如,如果您查询个人订单接口,则个人用户ID必须是接口的参与。当接口被要求时,您只需要通过我们自定义的方法获得当前的登录用户,然后将其作为参数进行输入。通过业务代码实现个人数据的这种数据权限将更加方便和安全,工作量不大,易于理解和扩展。