当前位置: 首页 > news >正文

java每日精进 5.08【框架之数据权限补充】

1.@DataPermission 注解

@DataPermission数据权限注解,可声明在类或者方法上,配置使用的数据权限规则。

① enable 属性:当前类或方法是否开启数据权限,默认是 true 开启状态,可设置 false 禁用状态。

也就是说,数据权限默认是开启的,无需添加 @DataPermission 注解

// UserProfileController.java@GetMapping("/get")
@Operation(summary = "获得登录用户信息")
@DataPermission(enable = false) // 关闭数据权限,避免只查看自己时,查询不到部门。
public CommonResult<UserProfileRespVO> profile() {// .. 省略代码if (user.getDeptId() != null) {DeptDO dept = deptService.getDept(user.getDeptId());resp.setDept(UserConvert.INSTANCE.convert02(dept));}// .. 省略代码
}

② includeRules 属性,配置生效的 DataPermissionRule (opens new window)数据权限规则。例如说,项目里有 10 种 DataPermissionRule 规则,某个方法只想其中的 1 种生效,则可以使用该属性。

③ excludeRules 属性,配置排除的 DataPermissionRule (opens new window)数据权限规则。例如说,项目里有 10 种 DataPermissionRule 规则,某个方法不想其中的 1 种生效,则可以使用该属性。

2.自定义数据权限规则

2.1 DataPermissionRule类

如果想要自定义数据权限规则,只需要实现DataPermissionRule (opens new window)数据权限规则接口,并声明成 Spring Bean 即可。需要实现的只有两个方法:

/*** 数据权限规则接口* 通过实现接口,自定义数据规则。例如说,*/
public interface DataPermissionRule {/*** 返回需要生效的表名数组* 为什么需要该方法?Data Permission 数组基于 SQL 重写,通过 Where 返回只有权限的数据* 如果需要基于实体名获得表名,可调用 {@link TableInfoHelper#getTableInfo(Class)} 获得* @return 表名数组*/Set<String> getTableNames();/*** 根据表名和别名,生成对应的 WHERE / OR 过滤条件* @param tableName 表名* @param tableAlias 别名,可能为空* @return 过滤条件 Expression 表达式*/Expression getExpression(String tableName, Alias tableAlias);}
  • DataPermissionRule 是一个接口,定义了数据权限规则的核心行为。它用于自定义数据权限逻辑,通过在 SQL 查询执行前动态添加 WHERE 或 OR 条件来限制用户访问的数据。实现该接口的类(如 DeptDataPermissionRule)需要指定哪些表需要权限控制,以及如何为这些表生成过滤条件。

Set<String> getTableNames()

  • 作用:返回需要应用数据权限控制的表名集合
  • 返回值:Set<String>,表示受权限控制的表名集合
  • 说明
    • 数据权限基于 SQL 重写,系统需要知道哪些表需要添加权限过滤条件
    • 表名通常通过 MyBatis Plus 的 TableInfoHelper.getTableInfo(Class) 从实体类获取
    • 返回的表名集合用于 DataPermissionRuleHandler 判断当前查询的表是否需要权限控制
    • 示例
      @Override public Set<String> getTableNames() { return Set.of("t_user", "t_dept"); }
      表示权限规则适用于 t_user 和 t_dept 表。

Expression getExpression(String tableName, Alias tableAlias)

  • 作用:根据表名和别名生成 SQL 过滤条件(WHERE 或 OR 表达式)。
  • 入参
    • tableName (String):查询的表名,例如 t_user。
    • tableAlias (Alias):表的别名,可能为空(例如在多表查询中,表可能有别名如 u)。
  • 返回值:Expression,表示 SQL 过滤条件,可能是 InExpression、EqualsTo 等 JSQLParser 表达式对象,或 null(表示无条件)。
  • 说明
    • 该方法是权限规则的核心,负责根据业务逻辑生成限制条件的 SQL 表达式。
    • 返回的 Expression 会被 MyBatis Plus 的 DataPermissionInterceptor 转换为 SQL 片段,追加到查询的 WHERE 子句。
    • 如果返回 null,表示不添加任何权限条件(即允许访问所有数据)。
  • 示例
    @Override
    public Expression getExpression(String tableName, Alias tableAlias) {if ("t_user".equals(tableName)) {String column = tableAlias != null ? tableAlias.getName() + ".dept_id" : "dept_id";return new InExpression(new Column(column), new ExpressionList<>(new LongValue(1), new LongValue(2)));}return null;
    }
    为 t_user 表生成条件 WHERE dept_id IN (1, 2)。

2.2 DeptDataPermissionRule 类

/*** 基于部门的 {@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<>();@Overridepublic Set<String> getTableNames() {return TABLE_NAMES;}@Overridepublic 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);}}

DeptDataPermissionRule 是 DataPermissionRule 接口的一个具体实现,专注于基于部门的权限控制。它通过表的 dept_id 和 user_id 字段生成过滤条件,支持以下权限逻辑:

  • 用户可以查看指定部门(deptIds)的数据。
  • 用户可以查看自己的数据(self)。
  • 用户可以查看所有数据(all)。 该类还支持动态配置表和字段映射,解决部门变更时的权限问题(如是否修改历史数据的 dept_id)。
  1. Set<String> getTableNames()
    • 作用:返回受权限控制的表名集合。
    • 入参:无。
    • 返回值:Set<String>,包含 TABLE_NAMES 中的表名。
    • 说明:实现 DataPermissionRule 接口方法,返回 deptColumns 和 userColumns 中配置的表名。
    • 示例
      deptColumns.put("t_user", "dept_id");
      userColumns.put("t_user", "user_id");
      // getTableNames() 返回 Set.of("t_user")
  2. Expression getExpression(String tableName, Alias tableAlias)
    • 作用:为指定表生成权限过滤条件。
    • 入参
      • tableName (String):表名,如 t_user。
      • tableAlias (Alias):表别名,可能为 null。
    • 返回值:Expression,SQL 过滤条件,或 null(无条件)。
    • 逻辑
      1. 检查登录用户是否存在(SecurityFrameworkUtils.getLoginUser),若无,返回 null。
      2. 检查用户是否为管理员(UserTypeEnum.ADMIN),若否,返回 null。
      3. 从 LoginUser 上下文获取权限(DeptDataPermissionRespDTO),若无则通过 permissionApi.getDeptDataPermission 获取并缓存。
      4. 根据权限处理:
        • 若 all = true,返回 null(无条件,允许访问所有数据)。
        • 若 deptIds 为空且 self = false,返回 EqualsTo(null, null)(确保无数据返回)。
        • 否则,调用 buildDeptExpression 和 buildUserExpression 生成部门和用户条件。
      5. 组合条件:
        • 若两者均为空,返回 EXPRESSION_NULL。
        • 若仅一个非空,返回该条件。
        • 若两者非空,使用 OR 组合(如 (dept_id IN (1, 2) OR user_id = 100))。
    • 示例
      // 假设:tableName = "t_user", tableAlias = null
      // 用户:ID = 100, 权限:deptIds = [1, 2], self = true
      // deptColumns: {"t_user": "dept_id"}
      // userColumns: {"t_user": "user_id"}
      Expression expr = getExpression("t_user", null);
      // 返回:(dept_id IN (1, 2) OR user_id = 100)
  3. Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds)
    • 作用:生成基于部门的过滤条件。
    • 入参
      • tableName (String):表名。
      • tableAlias (Alias):表别名。
      • deptIds (Set<Long>):允许访问的部门 ID 集合。
    • 返回值:Expression,部门过滤条件(如 dept_id IN (1, 2)),或 null(无条件)。
    • 逻辑
      1. 从 deptColumns 获取表对应的部门字段名,若无,返回 null。
      2. 若 deptIds 为空,返回 null。
      3. 构建 InExpression,列为 MyBatisUtils.buildColumn(tableName, tableAlias, columnName),值为 deptIds 转换成的 LongValue 列表。
    • 示例
      // tableName = "t_user", tableAlias = null, deptIds = [1, 2]
      // deptColumns: {"t_user": "dept_id"}
      Expression expr = buildDeptExpression("t_user", null, Set.of(1L, 2L));
      // 返回:dept_id IN (1, 2)
  4. Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId)
    • 作用:生成基于用户的过滤条件。
    • 入参
      • tableName (String):表名。
      • tableAlias (Alias):表别名。
      • self (Boolean):是否允许查看自己的数据。
      • userId (Long):当前用户 ID。
    • 返回值:Expression,用户过滤条件(如 user_id = 100),或 null(无条件)。
    • 逻辑
      1. 若 self = false,返回 null。
      2. 从 userColumns 获取表对应的用户字段名,若无,返回 null。
      3. 构建 EqualsTo,列为 MyBatisUtils.buildColumn(tableName, tableAlias, columnName),值为 LongValue(userId)。
    • 示例
      // tableName = "t_user", tableAlias = null, self = true, userId = 100
      // userColumns: {"t_user": "user_id"}
      Expression expr = buildUserExpression("t_user", null, true, 100L);
      // 返回:user_id = 100
  5. void addDeptColumn(Class<? extends BaseDO> entityClass)
    • 作用:为实体类添加默认部门字段(dept_id)配置。
    • 入参
      • entityClass (Class<? extends BaseDO>):实体类。
    • 返回值:无。
    • 说明:调用 addDeptColumn(entityClass, DEPT_COLUMN_NAME)。
  6. void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName)
    • 作用:为实体类添加自定义部门字段配置。
    • 入参
      • entityClass (Class<? extends BaseDO>):实体类。
      • columnName (String):部门字段名。
    • 返回值:无。
    • 说明:通过 TableInfoHelper.getTableInfo 获取表名,调用 addDeptColumn(tableName, columnName)。
  7. void addDeptColumn(String tableName, String columnName)
    • 作用:为指定表添加部门字段配置。
    • 入参
      • tableName (String):表名。
      • columnName (String):部门字段名。
    • 返回值:无。
    • 说明:将映射添加到 deptColumns,并将 tableName 添加到 TABLE_NAMES。
    • 示例
      addDeptColumn("t_user", "dept_id");
      // deptColumns: {"t_user": "dept_id"}
      // TABLE_NAMES: ["t_user"]
  8. void addUserColumn(Class<? extends BaseDO> entityClass)
    • 作用:为实体类添加默认用户字段(user_id)配置。
    • 入参
      • entityClass (Class<? extends BaseDO>):实体类。 “

相关文章:

  • Linux系统(OpenEuler22.03-LTS)部署FastGPT
  • MySql集群架构
  • [逆向工程]什么是DLL注入(二十二)
  • 1.3.1 Linux音频框架alsa详细介绍
  • Inno Setup专业打包指南:从基础到高级应用
  • 前端面经 作用域和作用域链
  • 信息系统项目管理工程师备考计算类真题讲解十五
  • 数值分析——条件数
  • 云展厅的制作方式
  • Web 架构之前后端分离
  • docker镜像误删恢复
  • 如何在 Logback 日志框架中加入链路 ID
  • 【基础知识】李雅普诺夫方程与李雅普诺夫函数
  • mybatis-plus-join-boot-starter依赖解决 Join 联表查询
  • SQLite3介绍与常用语句汇总
  • 121页最佳实践PPT | XX集团SAP ERP业务蓝图规划方案erp规划方案
  • lumpy:基因组结构变异SV的检测
  • 简单说明.nii.gz文件数据结构
  • 缓冲区溢出分析
  • C++ 模板方法模式详解与实例
  • 看展览|2025影像上海艺博会:市场与当代媒介中的摄影
  • 前4个月我国货物贸易进出口同比增长2.4%,增速较一季度加快1.1个百分点
  • 国家主席习近平同普京总统出席签字和合作文本交换仪式
  • 我驻苏丹使馆建议在苏中国公民尽快撤离
  • 泉州一家婚介机构广告牌越南新娘七天闪婚领证?市监部门介入
  • 现场|万米云端,遇见上博