java每日精进 5.07【框架之数据权限】

数据权限,实现指定用户可以操作指定范围的数据。

数据权限不支持指定用户只能查看数据的某些字段

权限可以分成三类:功能权限、数据权限、字段权限

目前可以使用数据脱敏实现一定程度的字段权限控制;

1.数据权限实现步骤

1.1插件原理

DataPermissionInterceptor 的工作原理与租户插件类似,它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来。

数据权限插件 | MyBatis-Plus (baomidou.com)

1.2SQL片段组装的核心逻辑代码

JSQLParser 是一个开源的 SQL 解析库,可方便地解析和修改 SQL 语句。它是插件实现权限逻辑的关键工具,MyBatis-Plus 的数据权限依托于 JSQLParser 的解析能力。

以下示例展示如何使用 JSQLParser 来修改 SQL

java 复制代码
// 示例 SQL
String sql = "SELECT * FROM user WHERE status = 'active'";
Expression expression;

try {
    expression = CCJSqlParserUtil.parseCondExpression("status = 'inactive'");
    PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse(sql)).getSelectBody();
    select.setWhere(expression);

    System.out.println(select); // 输出:SELECT * FROM user WHERE status = 'inactive'
} catch (JSQLParserException e) {
    e.printStackTrace();
}

自定义 MultiDataPermissionHandler,实现特定业务逻辑:

java 复制代码
/**
 * 基于 {@link DataPermissionRule} 的数据权限处理器
 *
 * 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>
 * 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来
 */
@RequiredArgsConstructor
public class DataPermissionRuleHandler implements MultiDataPermissionHandler {

    //ruleFactory 是一个"规则工厂",它是一个工具,专门用来提供"权限规则"。
    private final DataPermissionRuleFactory ruleFactory;

    //Table table:表示你要查的表;
    //Expression where:表示你原来的查询条件(比如 WHERE id = 1)
    //String mappedStatementId:一个字符串,表示你正在调用的查询方法
    @Override
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
        // 获得 Mapper 对应的数据权限的规则
        List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
        if (CollUtil.isEmpty(rules)) {
            return null;
        }
        // 生成条件,allExpression,用来存放最终的权限条件
        Expression allExpression = null;
        for (DataPermissionRule rule : rules) {
            // 判断表名是否匹配,规则是否管理当前的表
            String tableName = MyBatisUtils.getTableName(table);
            if (!rule.getTableNames().contains(tableName)) {
                continue;
            }
            // 单条规则的条件,对于这个表(tableName),它的限制条件是什么
            Expression oneExpress = rule.getExpression(tableName, table.getAlias());
            if (oneExpress == null) {
                continue;
            }
            // 拼接到 allExpression 中
            allExpression = allExpression == null ? oneExpress
                    : new AndExpression(allExpression, oneExpress);
        }
        return allExpression;
    }
}

整体流程(用图书馆的例子)

  1. 你想借书(查数据库),比如查 SELECT * FROM user。
  2. 图书管理员(DataPermissionRuleHandler)先拦住你,问:"你用的是哪个借书方法?"(mappedStatementId)。
  3. 管理员去问规则工厂(ruleFactory):"这个借书方法有哪些权限规则?"(rules)。
  4. 如果没有规则,管理员说:"随便借!"(返回 null)。
  5. 如果有规则(比如"只能借 dept_id = 1 的书"),管理员检查你借的书是不是这个部门的(tableName)。
  6. 如果是,管理员生成一个限制条件(u.dept_id = 1),并把所有条件用 AND 连起来。
  7. 最后,管理员把限制条件加到你的借书请求里(SELECT * FROM user WHERE dept_id = 1),确保你只能借到有权限的书。

2.整体代码实现流程

2.1Spring 应用启动

  • Spring 容器启动时,处理 @AutoConfiguration 和 @Configuration 类。
  • YudaoDataPermissionAutoConfiguration 创建核心 Bean:
    • DataPermissionRuleFactory:管理所有 DataPermissionRule 实例。
    • DataPermissionRuleHandler:通过 DataPermissionInterceptor 集成到 MyBatis Plus 拦截器链。
    • DataPermissionAnnotationAdvisor:支持基于注解的权限控制。
  • YudaoDeptDataPermissionAutoConfiguration(在满足条件时)创建 DeptDataPermissionRule Bean,并通过 DeptDataPermissionRuleCustomizer 配置部门相关规则。
  • 具体每个模块的规则就像DataPermissionConfiguration 定义 DeptDataPermissionRuleCustomizer,为 DeptDataPermissionRule 配置特定表和字段(如 AdminUserDO、DeptDO)。
复制代码
 ```java
 /**
  * 数据权限的自动配置类
  * DataPermissionRuleFactory:管理所有 DataPermissionRule 实例。
  * DataPermissionRuleHandler:通过 DataPermissionInterceptor 集成到 MyBatis Plus 拦截器链
  * DataPermissionAnnotationAdvisor:支持基于注解的权限控制
  */
 @AutoConfiguration
 public class YudaoDataPermissionAutoConfiguration {

     /**
      * 创建数据权限规则的工厂,管理所有数据权限规则的集合
      * @param rules
      * @return
      */
     @Bean
     public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
         return new DataPermissionRuleFactoryImpl(rules);
     }

     /**
      * 处理数据权限规则的核心处理器
      * @param interceptor
      * @param ruleFactory
      * @return
      */
     @Bean
     public DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor,
                                                                DataPermissionRuleFactory ruleFactory) {
         // 创建 DataPermissionInterceptor 拦截器
         DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory);
         DataPermissionInterceptor inner = new DataPermissionInterceptor(handler);
         // 添加到 interceptor 中
         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
         MyBatisUtils.addInterceptor(interceptor, inner, 0);
         return handler;
     }

     /**
      * 提供基于注解的数据权限控制
      * @return
      */
     @Bean
     public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
         return new DataPermissionAnnotationAdvisor();
     }

 }
 ```

 ```java
 /**
  * 基于部门的数据权限 AutoConfiguration
  * YudaoDeptDataPermissionAutoConfiguration(在满足条件时)创建 DeptDataPermissionRule Bean,
  * 并通过 DeptDataPermissionRuleCustomizer 配置部门相关规则。
  */
 @AutoConfiguration
 @ConditionalOnClass(LoginUser.class)
 @ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})
 public class YudaoDeptDataPermissionAutoConfiguration {

     /**
      * 创建一个 DeptDataPermissionRule 对象,并将 permissionApi 作为参数传递给其构造函数。
      * 遍历 customizers 列表,调用每个 DeptDataPermissionRuleCustomizer 的 customize 方法
      * 对 rule 对象进行自定义配置。
      * 返回配置好的 DeptDataPermissionRule 对象。
      * @param permissionApi
      * @param customizers
      * @return
      */
     @Bean
     public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,
                                                          List<DeptDataPermissionRuleCustomizer> customizers) {
         // 创建 DeptDataPermissionRule 对象
         DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
         // 补全表配置
         customizers.forEach(customizer -> customizer.customize(rule));
         return rule;
     }

 }
 ```

 ```java
 /**
  * system 模块的数据权限 Configuration
  */
 @Configuration(proxyBeanMethods = false)
 public class DataPermissionConfiguration {

     @Bean
     public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
         return rule -> {
             // dept
             rule.addDeptColumn(AdminUserDO.class);
             rule.addDeptColumn(DeptDO.class, "id");
             // user
             rule.addUserColumn(AdminUserDO.class, "id");
         };
     }

 }
 ```

2.2发起 MyBatis Plus 查询

  • 通过 MyBatis Mapper 方法(如 selectList)发起数据库查询。
  • 生成查询的 mappedStatementId,例如 com.example.UserMapper.selectList。

2.3MyBatis Plus 拦截器链处理

  • MyBatis Plus 通过拦截器链处理查询。
  • DataPermissionInterceptor(在 YudaoDataPermissionAutoConfiguration 中添加到 MybatisPlusInterceptor)在链的早期(位置 0,优先于分页插件)被调用。
  • DataPermissionInterceptor 委托给 DataPermissionRuleHandler 的 getSqlSegment 方法。
复制代码
 ```java
 /**
  * 基于 {@link DataPermissionRule} 的数据权限处理器
  *
  * 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>
  * 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来
  */
 @RequiredArgsConstructor
 public class DataPermissionRuleHandler implements MultiDataPermissionHandler {

     //ruleFactory 是一个"规则工厂",它是一个工具,专门用来提供"权限规则"。
     private final DataPermissionRuleFactory ruleFactory;

     //Table table:表示你要查的表;
     //Expression where:表示你原来的查询条件(比如 WHERE id = 1)
     //String mappedStatementId:一个字符串,表示你正在调用的查询方法
     @Override
     public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
         // 获得 Mapper 对应的数据权限的规则
         List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
         if (CollUtil.isEmpty(rules)) {
             return null;
         }
         // 生成条件,allExpression,用来存放最终的权限条件
         Expression allExpression = null;
         for (DataPermissionRule rule : rules) {
             // 判断表名是否匹配,规则是否管理当前的表
             String tableName = MyBatisUtils.getTableName(table);
             if (!rule.getTableNames().contains(tableName)) {
                 continue;
             }
             // 单条规则的条件,对于这个表(tableName),它的限制条件是什么
             Expression oneExpress = rule.getExpression(tableName, table.getAlias());
             if (oneExpress == null) {
                 continue;
             }
             // 拼接到 allExpression 中
             allExpression = allExpression == null ? oneExpress
                     : new AndExpression(allExpression, oneExpress);
         }
         return allExpression;
     }
 }
 ```

2.4DataPermissionRuleHandler 处理

  • DataPermissionRuleHandler 从 DataPermissionRuleFactory 获取适用于当前查询的 DataPermissionRule 列表(通过 getDataPermissionRule(mappedStatementId))。
  • DataPermissionRuleFactoryImpl 根据 DataPermissionContextHolder 过滤规则:
    • 无上下文:返回所有规则。
    • 有上下文:根据 enable()、includeRules 或 excludeRules 过滤。
  • 对每个适用规则(如 DeptDataPermissionRule),检查是否适用于查询的表(tableName)。
  • 如果适用,调用规则的 getExpression 方法生成 SQL 条件(Expression)。
复制代码
 ```java
 /**
  * 默认的 DataPermissionRuleFactoryImpl 实现类
  * 支持通过 {@link DataPermissionContextHolder} 过滤数据权限
  */
 @RequiredArgsConstructor
 public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {

     /**
      * 数据权限规则数组
      */
     private final List<DataPermissionRule> rules;

     @Override
     public List<DataPermissionRule> getDataPermissionRules() {
         return rules;
     }

     @Override // mappedStatementId 参数,暂时没有用。以后,可以基于 mappedStatementId + DataPermission 进行缓存
     public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {
         // 1. 无数据权限
         if (CollUtil.isEmpty(rules)) {
             return Collections.emptyList();
         }
         // 2. 未配置,则默认开启
         DataPermission dataPermission = DataPermissionContextHolder.get();
         if (dataPermission == null) {
             return rules;
         }
         // 3. 已配置,但禁用
         if (!dataPermission.enable()) {
             return Collections.emptyList();
         }

         // 4. 已配置,只选择部分规则
         if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) {
             return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass()))
                     .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询
         }
         // 5. 已配置,只排除部分规则
         if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) {
             return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass()))
                     .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询
         }
         // 6. 已配置,全部规则
         return rules;
     }

 }
 ```

2.5DeptDataPermissionRule 处理

  • DeptDataPermissionRule 检查是否存在登录用户(LoginUser)并验证用户是否为管理员(UserTypeEnum.ADMIN)。
  • 从用户上下文或通过 PermissionApi.getDeptDataPermission 获取部门数据权限(DeptDataPermissionRespDTO)。
  • 根据权限设置(all、deptIds、self)构建 SQL 条件:
    • buildDeptExpression:若 deptIds 不为空且表配置了 dept_id 字段,生成 WHERE dept_id IN (...)。
    • buildUserExpression:若 self 为 true 且表配置了 user_id 字段,生成 WHERE user_id = ?。
    • 使用 OR 组合条件或返回单一条件。
  • 将生成的 Expression 转换为 SQL 片段(如 dept_id IN (1, 2) OR user_id = 100)。
复制代码
 ```java
 /**
  * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
  *
  * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
  *
  * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
  * 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【moyun-server 采用该方案】
  * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
  *  1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
  *      最终过滤条件是 WHERE dept_id = ?
  *  2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;
  *      最终过滤条件是 WHERE user_id IN (?, ?, ? ...)
  *  3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;
  *      最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)
  */
 @AllArgsConstructor
 @Slf4j
 public class DeptDataPermissionRule implements DataPermissionRule {

     /**
      * LoginUser 的 Context 缓存 Key
      */
     protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();

     private static final String DEPT_COLUMN_NAME = "dept_id";
     private static final String USER_COLUMN_NAME = "user_id";

     static final Expression EXPRESSION_NULL = new NullValue();

     private final PermissionApi permissionApi;

     /**
      * 基于部门的表字段配置
      * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义
      *
      * key:表名
      * value:字段名
      */
     private final Map<String, String> deptColumns = new HashMap<>();
     /**
      * 基于用户的表字段配置
      * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
      *
      * key:表名
      * value:字段名
      */
     private final Map<String, String> userColumns = new HashMap<>();
     /**
      * 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
      */
     private final Set<String> TABLE_NAMES = new HashSet<>();

     @Override
     public Set<String> getTableNames() {
         return TABLE_NAMES;
     }

     @Override
     public Expression getExpression(String tableName, Alias tableAlias) {
         // 只有有登陆用户的情况下,才进行数据权限的处理
         LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
         if (loginUser == null) {
             return null;
         }
         // 只有管理员类型的用户,才进行数据权限的处理
         if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
             return null;
         }

         // 获得数据权限
         DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
         // 从上下文中拿不到,则调用逻辑进行获取
         if (deptDataPermission == null) {
             deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());
             if (deptDataPermission == null) {
                 log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
                 throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
                         loginUser.getId(), tableName, tableAlias.getName()));
             }
             // 添加到上下文中,避免重复计算
             loginUser.setContext(CONTEXT_KEY, deptDataPermission);
         }

         // 情况一,如果是 ALL 可查看全部,则无需拼接条件
         if (deptDataPermission.getAll()) {
             return null;
         }

         // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
         if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
             && Boolean.FALSE.equals(deptDataPermission.getSelf())) {
             return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
         }

         // 情况三,拼接 Dept 和 User 的条件,最后组合
         Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
         Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
         if (deptExpression == null && userExpression == null) {
             // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
             log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
                     JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
 //            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
 //                    loginUser.getId(), tableName, tableAlias.getName()));
             return EXPRESSION_NULL;
         }
         if (deptExpression == null) {
             return userExpression;
         }
         if (userExpression == null) {
             return deptExpression;
         }
         // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
         return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));
     }

     private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
         // 如果不存在配置,则无需作为条件
         String columnName = deptColumns.get(tableName);
         if (StrUtil.isEmpty(columnName)) {
             return null;
         }
         // 如果为空,则无条件
         if (CollUtil.isEmpty(deptIds)) {
             return null;
         }
         // 拼接条件
         return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
                 // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
                 new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new))));
     }

     private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
         // 如果不查看自己,则无需作为条件
         if (Boolean.FALSE.equals(self)) {
             return null;
         }
         String columnName = userColumns.get(tableName);
         if (StrUtil.isEmpty(columnName)) {
             return null;
         }
         // 拼接条件
         return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
     }

     // ==================== 添加配置 ====================

     public void addDeptColumn(Class<? extends BaseDO> entityClass) {
         addDeptColumn(entityClass, DEPT_COLUMN_NAME);
     }

     public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
        addDeptColumn(tableName, columnName);
     }

     public void addDeptColumn(String tableName, String columnName) {
         deptColumns.put(tableName, columnName);
         TABLE_NAMES.add(tableName);
     }

     public void addUserColumn(Class<? extends BaseDO> entityClass) {
         addUserColumn(entityClass, USER_COLUMN_NAME);
     }

     public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
         addUserColumn(tableName, columnName);
     }

     public void addUserColumn(String tableName, String columnName) {
         userColumns.put(tableName, columnName);
         TABLE_NAMES.add(tableName);
     }

 }
 ```

2.6SQL 修改与执行

  • DataPermissionInterceptor 将 SQL 片段追加到原始查询的 WHERE 子句。
  • MyBatis Plus 继续通过其他拦截器(如分页)处理修改后的 SQL。
  • 最终 SQL 执行,数据库返回仅限于用户有权限的数据。

2.7示例场景

Dart 复制代码
假设用户(ID: 100,管理员类型)查询用户列表(AdminUserDO),配置如下:

查询通过 UserMapper.selectList(new QueryWrapper<>()) 执行。

用户权限:deptIds = [1, 2],self = true。

AdminUserDO 在 DataPermissionConfiguration 中配置了 dept_id 和 user_id 字段。
分步执行:

1. 查询发起:
Mapper 方法:com.example.UserMapper.selectList。
原始 SQL:SELECT * FROM admin_user。
2. 拦截器调用:
DataPermissionInterceptor 拦截查询,调用 DataPermissionRuleHandler.getSqlSegment。
3.规则获取:
DataPermissionRuleHandler 调用 DataPermissionRuleFactoryImpl.getDataPermissionRule("com.example.UserMapper.selectList")。
返回 [DeptDataPermissionRule](假设无上下文过滤)。
4. 表匹配:
表名:admin_user。
DeptDataPermissionRule.getTableNames() 包含 admin_user,规则适用。
5. 条件生成:
调用 DeptDataPermissionRule.getExpression("admin_user", alias)。
用户已登录(ID: 100,管理员)。
获取 DeptDataPermissionRespDTO:{ all: false, deptIds: [1, 2], self: true }。
buildDeptExpression:deptColumns 映射 admin_user 到 dept_id,生成 dept_id IN (1, 2)。
buildUserExpression:userColumns 映射 admin_user 到 user_id,self = true,生成 user_id = 100。
组合:(dept_id IN (1, 2) OR user_id = 100)。
6. SQL 修改:
修改后 SQL:SELECT * FROM admin_user WHERE (dept_id IN (1, 2) OR user_id = 100)。
7. 执行:
数据库执行修改后的 SQL,仅返回部门 1 或 2 的用户,或 user_id = 100 的用户。

每个类功能详解

以下是每个类的功能描述以及它们之间的联系。

3.解释类

3.1类详解

  1. YudaoDataPermissionAutoConfiguration
    • 目的:配置数据权限核心组件。
    • 功能
      • 创建 DataPermissionRuleFactory,管理所有 DataPermissionRule。
      • 创建 DataPermissionRuleHandler,通过 DataPermissionInterceptor 集成到 MyBatis Plus。
      • 提供 DataPermissionAnnotationAdvisor,支持注解驱动的权限控制。
    • 关键 Bean
      • dataPermissionRuleFactory:返回 DataPermissionRuleFactoryImpl。
      • dataPermissionRuleHandler:配置 MyBatis Plus 拦截器链。
      • dataPermissionAnnotationAdvisor:启用注解权限。
  2. YudaoDeptDataPermissionAutoConfiguration
    • 目的:配置基于部门的数据权限。
    • 功能
      • 在 LoginUser、PermissionApi 和 DeptDataPermissionRuleCustomizer 存在时激活。
      • 创建 DeptDataPermissionRule,通过 DeptDataPermissionRuleCustomizer 进行定制。
    • 关键 Bean
      • deptDataPermissionRule:初始化并配置部门规则。
  3. DataPermissionRuleHandler
    • 目的:应用数据权限规则的核心处理器。
    • 功能
      • 从 DataPermissionRuleFactory 获取适用规则。
      • 遍历规则,检查表适用性,生成 SQL 条件。
      • 使用 AND 组合多条规则的条件。
    • 关键方法
      • getSqlSegment:为给定表和查询生成 SQL 片段。
  4. DataPermissionRuleFactoryImpl
    • 目的:管理和过滤数据权限规则。
    • 功能
      • 存储 DataPermissionRule 列表。
      • 根据 DataPermissionContextHolder 过滤规则(enable、includeRules、excludeRules)。
    • 关键方法
      • getDataPermissionRules:返回所有规则。
      • getDataPermissionRule:为特定查询过滤规则。
  5. DeptDataPermissionRule
    • 目的:实现基于部门的数据权限逻辑。
    • 功能
      • 配置表的 dept_id 和 user_id 字段。
      • 根据用户权限(deptIds、self)生成 SQL 条件。
      • 支持动态字段映射(deptColumns、userColumns)。
    • 关键方法
      • getTableNames:返回受控表。
      • getExpression:为表构建 SQL 条件。
      • buildDeptExpression / buildUserExpression:生成部门/用户条件。
      • 配置方法(如 addDeptColumn、addUserColumn)。
  6. DeptDataPermissionRuleCustomizer
    • 目的:为 DeptDataPermissionRule 提供定制接口。
    • 功能
      • 允许外部配置表-字段映射。
      • 在 DataPermissionConfiguration 中实现,为特定表(如 AdminUserDO)设置规则。
  7. DataPermissionConfiguration
    • 目的:为系统模块提供特定配置。
    • 功能
      • 定义 DeptDataPermissionRuleCustomizer,为 DeptDataPermissionRule 配置 AdminUserDO 和 DeptDO 的字段映射。

3.2类之间的关系

  • YudaoDataPermissionAutoConfiguration 是入口,搭建核心框架,创建 DataPermissionRuleFactory、DataPermissionRuleHandler 和注解支持。
  • YudaoDeptDataPermissionAutoConfiguration 扩展框架,添加部门特定规则,依赖 DataPermissionRuleFactory 注册 DeptDataPermissionRule。
  • DataPermissionRuleHandler 是运行时处理器,连接 DataPermissionRuleFactory(提供规则)和 MyBatis Plus(通过 DataPermissionInterceptor)。
  • DataPermissionRuleFactoryImpl 作为规则仓库,管理包括 DeptDataPermissionRule 在内的规则,支持动态过滤。
  • DeptDataPermissionRule 是具体规则实现,由 DeptDataPermissionRuleCustomizer 配置,被 DataPermissionRuleHandler 使用。
  • DeptDataPermissionRuleCustomizer(通过 DataPermissionConfiguration)为 DeptDataPermissionRule 提供表特定配置。
  • PermissionApi 和 LoginUser 是外部依赖,提供用户权限数据和上下文。

3.3类形成分层架构:

  • 配置层:YudaoDataPermissionAutoConfiguration、YudaoDeptDataPermissionAutoConfiguration、DataPermissionConfiguration。
  • 核心逻辑层:DataPermissionRuleHandler、DataPermissionRuleFactoryImpl。
  • 规则层:DeptDataPermissionRule。
  • 定制层:DeptDataPermissionRuleCustomizer。
相关推荐
rit843249914 分钟前
Java中的分布式缓存与Memcached集成实战
java·分布式·缓存
LSL666_19 分钟前
Java——包装类
java·开发语言·包装类
caihuayuan520 分钟前
Vue生命周期&脚手架工程&Element-UI
java·大数据·spring boot·后端·课程设计
yasuniko20 分钟前
C++线程库
开发语言·c++
@老蝴29 分钟前
C语言—指针2
c语言·开发语言
故事很腻i31 分钟前
RabbitMQ 消息不重复消费和顺序性
java·rabbitmq
明月看潮生37 分钟前
青少年编程与数学 02-019 Rust 编程基础 01课题、环境准备
开发语言·青少年编程·rust·编程与数学
VBA633744 分钟前
VBA高级应用30例应用4:利用屏蔽事件来阻止自动运行事件
开发语言
Pop–1 小时前
Vue3 el-tree:全选时只返回父节点,半选只返回勾选中的节点(省-市区-县-镇-乡-村-街道)
开发语言·javascript·vue.js
钢铁男儿1 小时前
C# 方法(值参数和引用参数)
java·前端·c#