芋道数据权限使用文档
数据权限是一种细粒度的权限控制机制,它控制用户能够查看和操作哪些数据。与功能权限(控制用户能访问哪些功能)不同,数据权限关注的是数据行级别的访问控制。方案适用场景Bean名称优点缺点各模块独立配置大中型项目每个模块不同解耦、灵活需要多个配置类公共+业务配置有共享表的项目公共+业务层次清晰稍复杂单一配置小型项目单一简单直接耦合度高✅ 使用方案一(各模块独立配置)✅ 给每个 Bean 起不同的名称,避
1. 概述
1.1 什么是数据权限?
数据权限是一种细粒度的权限控制机制,它控制用户能够查看和操作哪些数据。与功能权限(控制用户能访问哪些功能)不同,数据权限关注的是数据行级别的访问控制。
1.2 应用场景
-
部门隔离:销售人员只能查看自己部门的客户数据
-
层级管理:部门经理可以查看本部门及下属部门的数据
-
个人数据:员工只能查看自己创建的数据
-
自定义范围:管理员可以指定特定部门的数据访问权限
1.3 系统特点
✅ 透明化:基于 MyBatis Plus 插件,自动在 SQL 中添加权限条件,业务代码无感知 ✅ 灵活性:支持注解控制,可以启用/禁用特定规则 ✅ 高性能:使用上下文缓存,避免重复计算 ✅ 可扩展:支持自定义数据权限规则
2. 核心原理
2.1 工作流程
┌─────────────┐ │ 用户请求 │ └──────┬──────┘ │ ▼ ┌─────────────────────────┐ │ 1. 解析 @DataPermission │ │ 注解(AOP 拦截) │ └──────┬──────────────────┘ │ ▼ ┌─────────────────────────┐ │ 2. 执行 Service 方法 │ │ 调用 Mapper 查询 │ └──────┬──────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 3. MyBatis Plus 拦截器拦截 SQL │ │ (DataPermissionInterceptor) │ └──────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 4. 获取当前用户的数据权限信息 │ │ (从 LoginUser Context) │ └──────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 5. 根据权限规则动态生成 SQL 条件│ │ (DeptDataPermissionRule) │ └──────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 6. 将权限条件拼接到原始 SQL │ │ WHERE ... AND (dept_id IN ?)│ └──────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 7. 执行最终 SQL,返回结果 │ └─────────────────────────────────┘
2.2 SQL 改写示例
原始 SQL:
SELECT * FROM auction_info WHERE status = 1
添加数据权限后的 SQL:
-- 场景1:仅本人数据 SELECT * FROM auction_info WHERE status = 1 AND user_id = 100 -- 场景2:本部门数据 SELECT * FROM auction_info WHERE status = 1 AND dept_id IN (10, 11, 12) -- 场景3:本部门 + 本人数据 SELECT * FROM auction_info WHERE status = 1 AND (dept_id IN (10, 11, 12) OR user_id = 100) -- 场景4:全部数据(不添加任何条件) SELECT * FROM auction_info WHERE status = 1
2.3 核心组件
| 组件 | 说明 |
|---|---|
| @DataPermission | 注解,用于控制数据权限的启用/禁用 |
| DataPermissionRule | 数据权限规则接口,定义如何生成 SQL 条件 |
| DeptDataPermissionRule | 基于部门的数据权限规则实现 |
| DataPermissionRuleHandler | 权限规则处理器,负责调用规则生成 SQL |
| DataPermissionInterceptor | MyBatis 拦截器,拦截 SQL 并添加权限条件 |
| DeptDataPermissionRespDTO | 数据权限响应对象,包含用户的权限信息 |
3. 架构设计
3.1 类图关系
┌──────────────────────────────┐ │ DataPermissionConfiguration │ 配置类 │ - rentDeptDataPermissionRuleCustomizer() └────────────┬─────────────────┘ │ 配置 ▼ ┌──────────────────────────────┐ │ DeptDataPermissionRule │ 权限规则 │ - getTableNames() │ │ - getExpression() │ │ - addDeptColumn() │ │ - addUserColumn() │ └────────────┬─────────────────┘ │ 实现 ▼ ┌──────────────────────────────┐ │ DataPermissionRule │ 规则接口 │ + getTableNames() │ │ + getExpression() │ └──────────────────────────────┘ ▲ │ 被调用 │ ┌──────────────────────────────┐ │ DataPermissionRuleHandler │ 规则处理器 │ - getSqlSegment() │ └────────────┬─────────────────┘ │ 注入到 ▼ ┌──────────────────────────────┐ │ DataPermissionInterceptor │ MyBatis 拦截器 │ (MyBatis Plus Plugin) │ └──────────────────────────────┘
3.2 执行流程详解
步骤1:配置阶段(启动时)
在 DataPermissionConfiguration 中配置需要数据权限控制的表:
@Bean
public DeptDataPermissionRuleCustomizer rentDeptDataPermissionRuleCustomizer() {
return rule -> {
// 为竞拍信息表配置部门字段
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
// 为用户表配置部门字段和用户字段
rule.addDeptColumn(SUserDO.class, "dept_id");
rule.addUserColumn(SUserDO.class, "id");
};
}
步骤2:查询阶段(运行时)
当执行数据库查询时:
-
MyBatis Plus 拦截器捕获 SQL
// DataPermissionRuleHandler.getSqlSegment() public Expression getSqlSegment(Table table, Expression where, String mappedStatementId)
-
获取适用的权限规则
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
-
生成权限条件表达式
Expression allExpression = rule.getExpression(tableName, table.getAlias());
-
拼接到原始 SQL
allExpression = new AndExpression(allExpression, oneExpress);
步骤3:权限计算
DeptDataPermissionRule.getExpression() 方法的执行逻辑:
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// 1. 获取登录用户
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// 2. 获取用户的数据权限信息(带缓存)
DeptDataPermissionRespDTO deptDataPermission =
loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
if (deptDataPermission == null) {
deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
}
// 3. 根据权限信息生成 SQL 条件
if (deptDataPermission.getAll()) {
return null; // 全部数据,不添加条件
}
// 4. 构建部门条件:WHERE dept_id IN (...)
Expression deptExpression = buildDeptExpression(...);
// 5. 构建用户条件:WHERE user_id = ?
Expression userExpression = buildUserExpression(...);
// 6. 组合条件:WHERE (dept_id IN (...) OR user_id = ?)
return new OrExpression(deptExpression, userExpression);
}
4. 数据权限范围
4.1 数据范围枚举
系统定义了5种数据权限范围(DataScopeEnum):
| 枚举值 | 代码 | 说明 | SQL 条件示例 |
|---|---|---|---|
| ALL | 1 | 全部数据权限 | 无条件 |
| DEPT_CUSTOM | 2 | 指定部门数据权限 | dept_id IN (1, 2, 3) |
| DEPT_ONLY | 3 | 仅本部门数据 | dept_id = 10 |
| DEPT_AND_CHILD | 4 | 本部门及子部门数据 | dept_id IN (10, 11, 12, 13) |
| SELF | 5 | 仅本人数据 | user_id = 100 |
4.2 权限计算逻辑
权限计算在 PermissionServiceImpl.getDeptDataPermission() 方法中实现:
@Override
@DataPermission(enable = false) // 防止递归
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
// 1. 获得用户的所有角色
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
// 2. 初始化结果对象
DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
// 3. 如果没有角色,只能查看自己的数据
if (CollUtil.isEmpty(roles)) {
result.setSelf(true);
return result;
}
// 4. 遍历角色,累加权限(取并集)
for (RoleDO role : roles) {
switch (role.getDataScope()) {
case 1: // ALL - 全部数据
result.setAll(true);
break;
case 2: // DEPT_CUSTOM - 指定部门
result.getDeptIds().addAll(role.getDataScopeDeptIds());
result.getDeptIds().add(userDeptId.get()); // 加上自己的部门
break;
case 3: // DEPT_ONLY - 本部门
result.getDeptIds().add(userDeptId.get());
break;
case 4: // DEPT_AND_CHILD - 本部门及子部门
result.getDeptIds().addAll(
deptService.getChildDeptIdListFromCache(userDeptId.get())
);
result.getDeptIds().add(userDeptId.get());
break;
case 5: // SELF - 仅本人
result.setSelf(true);
break;
}
}
return result;
}
4.3 权限合并规则
当用户拥有多个角色时,权限采用并集(OR)策略:
-
如果任一角色拥有
ALL权限 → 可查看全部数据 -
如果角色A可查看部门1,角色B可查看部门2 → 可查看部门1和部门2
-
如果任一角色设置了
SELF→ 可查看自己的数据
示例:
用户张三同时拥有两个角色:
-
角色A:本部门数据(部门10)
-
角色B:指定部门数据(部门20、30)
最终权限:
result.getDeptIds() = [10, 20, 30]
生成的 SQL:
WHERE dept_id IN (10, 20, 30)
5. 配置使用
5.1 快速开始
步骤1:创建配置类
在你的模块中创建 DataPermissionConfiguration 类:
package com.sunrise.system.module.rent.framework.web.config.core;
import com.sunrise.system.module.rent.dal.dataobject.auctioninfo.AuctionInfoDO;
import com.sunrise.system.module.rent.dal.dataobject.tradeinfo.TradeinfoDO;
import com.sunrise.system.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* rent 模块的数据权限配置
*/
@Configuration("rentDataPermissionConfiguration")
public class DataPermissionConfiguration {
@Bean("rentDeptDataPermissionRuleCustomizer")
public DeptDataPermissionRuleCustomizer rentDeptDataPermissionRuleCustomizer() {
return rule -> {
// 配置需要数据权限的表
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
rule.addDeptColumn(TradeinfoDO.class, "dept_id");
};
}
}
步骤2:确保数据库表有相应字段
需要数据权限的表必须包含以下字段之一:
-
dept_id:部门编号字段(用于部门权限) -
user_id或creator:用户编号字段(用于个人权限)
CREATE TABLE auction_info (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
dept_id BIGINT, -- 部门字段
creator BIGINT, -- 创建者字段
-- 其他字段...
);
步骤3:验证配置
启动项目后,执行查询操作,查看日志中的 SQL:
-- 自动添加了数据权限条件 SELECT * FROM auction_info WHERE status = 1 AND dept_id IN (10, 20)
5.2 配置方法详解
addDeptColumn() - 配置部门字段
// 方法1:使用默认字段名 "dept_id"
rule.addDeptColumn(AuctionInfoDO.class);
// 方法2:指定自定义字段名
rule.addDeptColumn(AuctionInfoDO.class, "department_id");
// 方法3:直接使用表名
rule.addDeptColumn("t_auction_info", "dept_id");
生成的 SQL 条件:
WHERE dept_id IN (10, 20, 30)
addUserColumn() - 配置用户字段
// 方法1:使用默认字段名 "user_id"
rule.addUserColumn(SUserDO.class);
// 方法2:指定自定义字段名(如 creator、create_by 等)
rule.addUserColumn(SUserDO.class, "creator");
// 方法3:直接使用表名
rule.addUserColumn("system_user", "id");
生成的 SQL 条件:
WHERE user_id = 100
同时配置部门和用户字段
rule.addDeptColumn(SUserDO.class, "dept_id"); rule.addUserColumn(SUserDO.class, "id");
生成的 SQL 条件(OR 关系):
WHERE (dept_id IN (10, 20) OR id = 100)
5.3 多模块配置
不同模块可以有各自的数据权限配置,使用唯一的 Bean 名称避免冲突:
System 模块:
@Configuration
public class DataPermissionConfiguration {
@Bean("systemDeptDataPermissionRuleCustomizer")
public DeptDataPermissionRuleCustomizer systemCustomizer() {
return rule -> {
rule.addDeptColumn(AdminUserDO.class);
rule.addDeptColumn(DeptDO.class, "id");
rule.addUserColumn(AdminUserDO.class, "id");
};
}
}
Rent 模块:
@Configuration("rentDataPermissionConfiguration")
public class DataPermissionConfiguration {
@Bean("rentDeptDataPermissionRuleCustomizer")
public DeptDataPermissionRuleCustomizer rentCustomizer() {
return rule -> {
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
rule.addDeptColumn(TradeinfoDO.class, "dept_id");
};
}
}
5.4 多模块共享配置(重要⭐)
核心原理
关键发现: Spring Boot 的自动配置会自动收集所有模块的 DeptDataPermissionRuleCustomizer Bean,然后统一应用!
查看 RsDeptDataPermissionAutoConfiguration 的源码:
@Bean
public DeptDataPermissionRule deptDataPermissionRule(
PermissionCommonApi permissionApi,
List<DeptDataPermissionRuleCustomizer> customizers) { // 👈 注意这里是 List
DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
// 遍历所有模块的 customizer,统一应用
customizers.forEach(customizer -> customizer.customize(rule));
return rule;
}
这意味着: 你不需要手动合并配置!每个模块定义自己的 DeptDataPermissionRuleCustomizer,框架会自动合并它们。
方案一:各模块独立配置(推荐)
适用场景: 各模块相对独立,每个模块管理自己的表
项目结构: ├── rs-module-system │ └── DataPermissionConfiguration (配置 AdminUserDO, DeptDO) ├── rs-module-rent │ └── DataPermissionConfiguration (配置 AuctionInfoDO, TradeinfoDO) └── rs-module-crm └── DataPermissionConfiguration (配置 CrmCustomerDO, CrmContractDO)
System 模块:
@Configuration
public class DataPermissionConfiguration {
@Bean
public DeptDataPermissionRuleCustomizer systemCustomizer() {
return rule -> {
rule.addDeptColumn(AdminUserDO.class);
rule.addDeptColumn(DeptDO.class, "id");
};
}
}
Rent 模块:
@Configuration("rentDataPermissionConfiguration")
public class DataPermissionConfiguration {
@Bean("rentCustomizer")
public DeptDataPermissionRuleCustomizer rentCustomizer() {
return rule -> {
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
rule.addDeptColumn(TradeinfoDO.class, "dept_id");
};
}
}
CRM 模块:
@Configuration("crmDataPermissionConfiguration")
public class DataPermissionConfiguration {
@Bean("crmCustomizer")
public DeptDataPermissionRuleCustomizer crmCustomizer() {
return rule -> {
rule.addDeptColumn(CrmCustomerDO.class, "dept_id");
rule.addDeptColumn(CrmContractDO.class, "dept_id");
};
}
}
最终效果: 所有配置自动合并,生效的表包括:
-
admin_user,dept(System模块) -
auction_info,trade_info(Rent模块) -
crm_customer,crm_contract(CRM模块)
✅ 优点:
-
各模块配置独立,互不干扰
-
模块可以单独启用/禁用
-
配置代码就近管理
方案二:公共模块统一配置
适用场景: 多个模块使用相同的表,或者希望集中管理配置
步骤1:创建公共配置模块
rs-module-common └── src/main/java/.../common/config/CommonDataPermissionConfiguration.java
步骤2:在公共模块中配置
package com.sunrise.system.module.common.config;
import com.sunrise.system.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 公共数据权限配置
* 适用于多个模块共享的表
*/
@Configuration
public class CommonDataPermissionConfiguration {
@Bean("commonDeptDataPermissionRuleCustomizer")
public DeptDataPermissionRuleCustomizer commonCustomizer() {
return rule -> {
// 配置所有模块都需要的通用表
rule.addDeptColumn(AdminUserDO.class);
rule.addDeptColumn(DeptDO.class, "id");
rule.addUserColumn(AdminUserDO.class, "id");
// 配置业务模块共享的表
rule.addDeptColumn(CommonBusinessDO.class, "dept_id");
};
}
}
步骤3:业务模块仍可添加自己的配置
// Rent 模块仍然可以配置自己特有的表
@Configuration("rentDataPermissionConfiguration")
public class DataPermissionConfiguration {
@Bean("rentCustomizer")
public DeptDataPermissionRuleCustomizer rentCustomizer() {
return rule -> {
// 只配置 Rent 模块特有的表
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
rule.addDeptColumn(TradeinfoDO.class, "dept_id");
};
}
}
✅ 优点:
-
公共表集中管理,避免重复配置
-
业务模块可以额外添加自己的配置
-
配置层次清晰
方案三:单一配置类(不推荐)
适用场景: 小型项目,表数量少
@Configuration
public class GlobalDataPermissionConfiguration {
@Bean
public DeptDataPermissionRuleCustomizer globalCustomizer() {
return rule -> {
// System 模块的表
rule.addDeptColumn(AdminUserDO.class);
rule.addDeptColumn(DeptDO.class, "id");
// Rent 模块的表
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
rule.addDeptColumn(TradeinfoDO.class, "dept_id");
// CRM 模块的表
rule.addDeptColumn(CrmCustomerDO.class, "dept_id");
// ... 所有表都配置在这里
};
}
}
❌ 缺点:
-
所有模块耦合在一起
-
无法单独启用/禁用模块
-
配置文件过于庞大
实际运行示例
假设项目有以下配置:
// System 模块
@Bean
public DeptDataPermissionRuleCustomizer systemCustomizer() {
return rule -> {
rule.addDeptColumn(AdminUserDO.class); // admin_user 表
};
}
// Rent 模块
@Bean
public DeptDataPermissionRuleCustomizer rentCustomizer() {
return rule -> {
rule.addDeptColumn(AuctionInfoDO.class); // auction_info 表
};
}
// CRM 模块
@Bean
public DeptDataPermissionRuleCustomizer crmCustomizer() {
return rule -> {
rule.addDeptColumn(CrmCustomerDO.class); // crm_customer 表
};
}
Spring 启动时的执行过程:
// 1. Spring 收集所有的 DeptDataPermissionRuleCustomizer Bean List<DeptDataPermissionRuleCustomizer> customizers = [ systemCustomizer, rentCustomizer, crmCustomizer ]; // 2. 创建 DeptDataPermissionRule DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi); // 3. 依次应用所有 customizer systemCustomizer.customize(rule); // 添加 admin_user rentCustomizer.customize(rule); // 添加 auction_info crmCustomizer.customize(rule); // 添加 crm_customer // 4. 最终 rule 包含所有表的配置 rule.getTableNames() = ["admin_user", "auction_info", "crm_customer"]
查询任何一个表时,都会自动应用数据权限:
// 查询用户表 userMapper.selectList(null); // 生成 SQL: SELECT * FROM admin_user WHERE dept_id IN (10, 20) // 查询竞拍表 auctionMapper.selectList(null); // 生成 SQL: SELECT * FROM auction_info WHERE dept_id IN (10, 20) // 查询客户表 customerMapper.selectList(null); // 生成 SQL: SELECT * FROM crm_customer WHERE dept_id IN (10, 20)
配置优先级和覆盖
问题: 如果多个模块对同一张表配置了不同的字段,会怎样?
// System 模块
rule.addDeptColumn("user_table", "dept_id");
// Rent 模块
rule.addDeptColumn("user_table", "department_id"); // 不同的字段名
答案: 后面的配置会覆盖前面的配置(Map 的特性)
// 最终 user_table 使用的字段是 "department_id"
建议: 避免多个模块配置同一张表,如果必须配置,请在公共模块中统一管理。
调试技巧
如果想查看最终生效了哪些表的配置,可以在启动时打印日志:
@Configuration
public class DataPermissionConfiguration {
@Bean
public DeptDataPermissionRuleCustomizer rentCustomizer(
@Lazy DeptDataPermissionRule rule) { // 延迟注入
return customRule -> {
customRule.addDeptColumn(AuctionInfoDO.class, "dept_id");
customRule.addDeptColumn(TradeinfoDO.class, "dept_id");
// 打印日志
log.info("[数据权限配置] Rent 模块配置完成");
};
}
// 可选:添加一个 CommandLineRunner 查看最终配置
@Bean
public CommandLineRunner printDataPermissionConfig(DeptDataPermissionRule rule) {
return args -> {
log.info("[数据权限配置] 最终生效的表: {}", rule.getTableNames());
};
}
}
启动日志示例:
[数据权限配置] System 模块配置完成 [数据权限配置] Rent 模块配置完成 [数据权限配置] CRM 模块配置完成 [数据权限配置] 最终生效的表: [admin_user, dept, auction_info, trade_info, crm_customer, crm_contract]
总结
| 方案 | 适用场景 | Bean名称 | 优点 | 缺点 |
|---|---|---|---|---|
| 各模块独立配置 | 大中型项目 | 每个模块不同 | 解耦、灵活 | 需要多个配置类 |
| 公共+业务配置 | 有共享表的项目 | 公共+业务 | 层次清晰 | 稍复杂 |
| 单一配置 | 小型项目 | 单一 | 简单直接 | 耦合度高 |
推荐做法:
-
✅ 使用方案一(各模块独立配置)
-
✅ 给每个 Bean 起不同的名称,避免冲突
-
✅ 框架会自动合并所有配置,无需手动处理
-
✅ 可以根据需要随时添加/删除模块配置
6. 实战示例
6.1 示例1:竞拍信息的数据权限
场景: 销售人员只能查看自己部门的竞拍信息
配置:
rule.addDeptColumn(AuctionInfoDO.class, "dept_id");
业务代码(无需修改):
@Service
public class Auction InfoServiceImpl extends ServiceImpl<AuctionInfoMapper, AuctionInfoDO>
implements AuctionInfoService {
@Override
public List<AuctionInfoDO> getAuctionList(AuctionInfoQueryVO query) {
// 直接查询,框架会自动添加数据权限
return baseMapper.selectList(new LambdaQueryWrapper<AuctionInfoDO>()
.eq(AuctionInfoDO::getStatus, query.getStatus())
);
}
}
执行的 SQL:
-- 假设当前用户在部门10,且角色配置为"本部门数据" SELECT * FROM auction_info WHERE status = 1 AND dept_id = 10 -- 自动添加
6.2 示例2:用户数据(部门 + 个人)
场景: 用户可以查看自己部门的用户,也可以查看自己的信息
配置:
rule.addDeptColumn(SUserDO.class, "dept_id"); rule.addUserColumn(SUserDO.class, "id");
执行的 SQL:
-- 假设当前用户ID=100,部门ID=10 SELECT * FROM s_user WHERE status = 1 AND (dept_id = 10 OR id = 100) -- 自动添加
6.3 示例3:禁用特定方法的数据权限
场景: 管理员统计功能需要查看所有数据,不受数据权限限制
使用注解禁用:
@Service
public class AuctionInfoServiceImpl implements AuctionInfoService {
// 方法1:使用 @DataPermission 注解
@Override
@DataPermission(enable = false)
public Long getTotalCount() {
// 此方法不受数据权限限制
return baseMapper.selectCount(null);
}
// 方法2:使用工具类
@Override
public Long getTotalCount2() {
return DataPermissionUtils.executeIgnore(() -> {
// 此代码块不受数据权限限制
return baseMapper.selectCount(null);
});
}
}
执行的 SQL:
-- 不会添加数据权限条件 SELECT COUNT(*) FROM auction_info
6.4 示例4:排除特定规则
场景: 某些查询只使用用户权限,不使用部门权限
@Service
public class AuctionInfoServiceImpl implements AuctionInfoService {
@Override
@DataPermission(excludeRules = DeptDataPermissionRule.class)
public List<AuctionInfoDO> getMyAuctions() {
// 只添加用户权限条件,不添加部门权限条件
return baseMapper.selectList(null);
}
}
6.5 示例5:仅使用特定规则
@Service
public class AuctionInfoServiceImpl implements AuctionInfoService {
@Override
@DataPermission(includeRules = DeptDataPermissionRule.class)
public List<AuctionInfoDO> getDeptAuctions() {
// 只添加部门权限条件
return baseMapper.selectList(null);
}
}
7. 高级用法
7.1 自定义数据权限规则
除了内置的 DeptDataPermissionRule,你可以实现自己的权限规则:
步骤1:实现 DataPermissionRule 接口
@Slf4j
@AllArgsConstructor
public class CustomDataPermissionRule implements DataPermissionRule {
@Override
public Set<String> getTableNames() {
// 返回需要应用此规则的表名
return Sets.newHashSet("custom_table");
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// 获取当前登录用户
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser == null) {
return null;
}
// 自定义权限逻辑
// 例如:根据用户的自定义属性过滤数据
String customField = (String) loginUser.getContext().get("customField");
// 构建 SQL 条件:WHERE custom_field = ?
return new EqualsTo(
MyBatisUtils.buildColumn(tableName, tableAlias, "custom_field"),
new StringValue(customField)
);
}
}
步骤2:注册规则
@Configuration
public class DataPermissionConfiguration {
@Bean
public CustomDataPermissionRule customDataPermissionRule() {
return new CustomDataPermissionRule();
}
}
7.2 动态控制数据权限
在代码中临时禁用数据权限
// 方式1:使用 Runnable
DataPermissionUtils.executeIgnore(() -> {
// 这里的代码不受数据权限限制
userMapper.selectList(null);
});
// 方式2:使用 Callable(有返回值)
List<UserDO> users = DataPermissionUtils.executeIgnore(() -> {
return userMapper.selectList(null);
});
// 方式3:手动控制
DataPermissionUtils.addDisableDataPermission();
try {
// 执行查询
userMapper.selectList(null);
} finally {
DataPermissionUtils.removeDataPermission();
}
7.3 上下文缓存优化
数据权限信息会缓存在 LoginUser 的上下文中,避免重复计算:
// 首次获取,会调用 permissionApi.getDeptDataPermission()
DeptDataPermissionRespDTO permission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
// 后续获取,直接从上下文读取
if (permission == null) {
permission = permissionApi.getDeptDataPermission(loginUser.getId());
loginUser.setContext(CONTEXT_KEY, permission); // 缓存
}
7.4 SQL 表别名支持
系统支持表别名,即使使用别名查询也能正确添加权限条件:
-- 原始 SQL SELECT a.* FROM auction_info a WHERE a.status = 1 -- 添加权限后 SELECT a.* FROM auction_info a WHERE a.status = 1 AND a.dept_id IN (10, 20)
7.5 复杂查询场景
多表关联查询
-- 原始 SQL SELECT a.*, b.name FROM auction_info a LEFT JOIN trade_info b ON a.trade_id = b.id WHERE a.status = 1 -- 添加权限后(两个表都配置了数据权限) SELECT a.*, b.name FROM auction_info a LEFT JOIN trade_info b ON a.trade_id = b.id WHERE a.status = 1 AND a.dept_id IN (10, 20) -- auction_info 的权限 AND b.dept_id IN (10, 20) -- trade_info 的权限
子查询
-- 原始 SQL SELECT * FROM auction_info WHERE id IN (SELECT auction_id FROM bid_record WHERE amount > 1000) -- 添加权限后 SELECT * FROM auction_info WHERE id IN ( SELECT auction_id FROM bid_record WHERE amount > 1000 AND dept_id IN (10, 20) -- 如果 bid_record 也配置了权限 ) AND dept_id IN (10, 20) -- auction_info 的权限
8. 常见问题
8.1 为什么我的数据权限不生效?
可能原因:
-
表没有配置权限
// 检查是否添加了配置 rule.addDeptColumn(YourDO.class, "dept_id");
-
表缺少必要字段
-- 确保表有 dept_id 或 user_id 字段 ALTER TABLE your_table ADD COLUMN dept_id BIGINT;
-
用户角色没有配置数据权限
-
进入系统管理 → 角色管理
-
编辑角色,设置"数据权限"字段
-
-
方法被标记为禁用数据权限
// 检查是否有此注解 @DataPermission(enable = false)
-
非管理员用户
// 数据权限只对 UserType = ADMIN 生效 if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) { return null; // 不添加权限条件 }
8.2 如何调试数据权限?
方法1:开启 SQL 日志
在 application.yml 中配置:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
查看控制台输出的完整 SQL。
方法2:断点调试
在以下位置打断点:
-
DeptDataPermissionRule.getExpression()- 查看权限条件生成过程 -
DataPermissionRuleHandler.getSqlSegment()- 查看 SQL 拦截过程 -
PermissionServiceImpl.getDeptDataPermission()- 查看权限计算过程
方法3:日志输出
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// ...
log.info("[数据权限] 表名={}, 权限={}", tableName, JsonUtils.toJsonString(deptDataPermission));
// ...
}
8.3 用户切换部门后,能否看到原部门的数据?
默认行为: 不能看到
系统采用的是方案1:dept_id 不修改,导致用户看不到之前的数据。
解决方案:
如果需要支持查看原部门数据,有以下选择:
方案A:数据迁移(推荐)
-- 编写脚本,将用户的历史数据迁移到新部门 UPDATE auction_info SET dept_id = 20 WHERE creator = 100;
方案B:同时使用 dept_id 和 user_id
// 配置时同时添加部门和用户字段 rule.addDeptColumn(AuctionInfoDO.class, "dept_id"); rule.addUserColumn(AuctionInfoDO.class, "creator"); // 生成的 SQL: // WHERE (dept_id IN (20) OR creator = 100) // 用户可以看到新部门的数据 + 自己创建的所有数据
方案C:自定义权限规则
实现自己的 DataPermissionRule,使用更复杂的逻辑。
8.4 如何实现"查看下属数据"的权限?
场景: 经理可以查看直接下属创建的数据
方案:
-
数据库设计
ALTER TABLE your_table ADD COLUMN creator BIGINT; -- 创建人
-
自定义权限规则
public class SubordinateDataPermissionRule implements DataPermissionRule { @Override public Expression getExpression(String tableName, Alias tableAlias) { // 获取当前用户的所有下属ID List<Long> subordinateIds = getSubordinateIds(loginUser.getId()); // 生成条件:WHERE creator IN (下属ID列表) return new InExpression( MyBatisUtils.buildColumn(tableName, tableAlias, "creator"), new ParenthesedExpressionList(new ExpressionList<>( subordinateIds.stream().map(LongValue::new).collect(Collectors.toList()) )) ); } }
8.5 性能优化建议
-
字段索引
-- 为权限字段添加索引 CREATE INDEX idx_dept_id ON auction_info(dept_id); CREATE INDEX idx_user_id ON auction_info(user_id);
-
权限缓存
-
系统已内置缓存,避免重复计算
-
权限信息存储在
LoginUser.context中
-
-
避免过度使用
-
对于统计、报表等查询,考虑禁用数据权限
@DataPermission(enable = false) public StatisticsVO getStatistics() { ... } -
-
合理设计表结构
-
避免在大表上频繁 JOIN
-
考虑数据归档策略
-
8.6 数据权限与功能权限的区别
| 维度 | 功能权限 | 数据权限 |
|---|---|---|
| 控制对象 | 功能(接口、按钮) | 数据(数据行) |
| 实现方式 | @PreAuthorize 注解 |
SQL 自动改写 |
| 判断时机 | 进入方法前 | 执行 SQL 时 |
| 示例 | 是否可以"删除用户" | 可以删除"哪些用户" |
8.7 递归问题
问题: 在 getDeptDataPermission() 方法中查询数据库时,会再次触发数据权限检查,导致递归。
解决方案:
@Override
@DataPermission(enable = false) // 必须禁用
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
// 查询用户角色等信息
// 如果不禁用,会再次调用此方法,导致 StackOverflowError
}
8.8 如何在特定条件下禁用数据权限?
场景: 超级管理员查看所有数据
方案1:在权限规则中判断
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// 超级管理员不受限制
if (loginUser.isSuperAdmin()) {
return null;
}
// 普通用户添加权限条件
return buildExpression(...);
}
方案2:在 Service 中判断
public List<UserDO> getUserList() {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser.isSuperAdmin()) {
// 超级管理员:禁用数据权限
return DataPermissionUtils.executeIgnore(() -> {
return userMapper.selectList(null);
});
} else {
// 普通用户:启用数据权限
return userMapper.selectList(null);
}
}
9. 总结
9.1 核心要点
✅ 配置简单:只需在配置类中添加 addDeptColumn() 或 addUserColumn() ✅ 自动生效:无需修改业务代码,MyBatis 拦截器自动添加 SQL 条件 ✅ 灵活控制:支持 @DataPermission 注解控制单个方法的权限规则 ✅ 性能优化:内置缓存机制,避免重复计算权限 ✅ 可扩展:支持自定义权限规则,满足复杂业务场景
9.2 最佳实践
-
合理规划表结构
-
在设计阶段就考虑数据权限字段(
dept_id、creator等) -
为权限字段添加索引
-
-
明确权限边界
-
区分功能权限和数据权限
-
统计、报表类功能考虑禁用数据权限
-
-
充分测试
-
测试不同角色的数据可见性
-
测试边界情况(无角色、多角色等)
-
-
监控和优化
-
开启 SQL 日志,观察权限条件是否正确
-
分析慢查询,优化权限相关的查询
-
9.3 快速检查清单
配置数据权限时,请确认:
- 表中存在
dept_id或user_id字段 - 在
DataPermissionConfiguration中配置了表和字段 - 角色的"数据权限"字段已正确设置
- 用户已分配角色且角色已启用
- 方法没有被标记为
@DataPermission(enable = false) - 用户类型是
ADMIN(默认只对管理员生效)
附录
A. 相关文件清单
| 文件路径 | 说明 |
|---|---|
DataPermissionConfiguration.java |
数据权限配置类 |
DeptDataPermissionRule.java |
部门数据权限规则实现 |
DataPermissionRule.java |
数据权限规则接口 |
DataPermissionRuleHandler.java |
权限规则处理器 |
@DataPermission |
数据权限注解 |
DataPermissionUtils.java |
数据权限工具类 |
DeptDataPermissionRespDTO.java |
数据权限响应对象 |
DataScopeEnum.java |
数据范围枚举 |
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)