数据权限控制通过 AOP + 注解 + MyBatis 拦截器 实现,业务代码无感知。支持 本人、本部门、本部门及子部门、全部、自定义 五种权限类型,支持 多租户过滤 与 Nacos 热配置。
@DataPermission:标记需要数据权限控制的方法DataPermissionInterceptor:MyBatis 拦截器,拦截 SQL 执行DataPermissionHandler:权限处理器接口(策略模式,可扩展)SelfDataPermissionHandler:本人数据处理器DeptDataPermissionHandler:本部门数据处理器DeptAndChildDataPermissionHandler:本部门及子部门数据处理器AllDataPermissionHandler:全部数据处理器CustomDataPermissionHandler:自定义条件处理器DataPermissionContext:权限上下文(ThreadLocal)- Redis:存储部门层级关系(可选,用于子部门查询优化)
- 租户隔离:每个租户只能看到自己的数据
- 部门隔离:部门主管只能看到本部门的数据
- 角色权限:不同角色看到不同范围的数据
- 数据安全:员工只能看到自己创建的数据
- 层级管理:经理可以看到下属部门的数据
- 超级管理员:可以看到所有数据
FrameworkJava 采用 MyBatis 拦截器 + 策略模式 实现数据权限控制。
请求 → 设置权限上下文 → 执行业务方法 → MyBatis 执行 SQL
↓
拦截器拦截 SQL → 解析 SQL
↓
获取权限上下文 → 选择权限处理器
↓
构建权限条件 → 修改 SQL(添加 WHERE 条件)
↓
执行修改后的 SQL → 返回结果
在需要使用数据权限的服务模块(如 portal、admin)的 pom.xml 中增加:
<dependency>
<groupId>com.zmbdp</groupId>
<artifactId>zmbdp-common-datapermission</artifactId>
<version>1.0</version>
</dependency>// 仅查询本人数据
@DataPermission(type = DataPermissionType.SELF)
List<Order> selectOrderList();
// 查询本部门数据
@DataPermission(type = DataPermissionType.DEPT)
List<Order> selectDeptOrderList();
// 查询本部门及子部门数据
@DataPermission(type = DataPermissionType.DEPT_AND_CHILD)
List<Order> selectDeptAndChildOrderList();
// 查询全部数据(超级管理员)
@DataPermission(type = DataPermissionType.ALL)
List<Order> selectAllOrderList();
// 自定义条件(如:只查询已支付的订单)
@DataPermission(type = DataPermissionType.CUSTOM, customCondition = "status = '2'")
List<Order> selectPaidOrderList();
// 自定义字段名(默认:user_id、dept_id)
@DataPermission(type = DataPermissionType.SELF, userColumn = "create_user_id")
List<Order> selectMyOrderList();
// 启用多租户过滤
@DataPermission(type = DataPermissionType.SELF, enableTenant = true)
List<Order> selectMyOrderListWithTenant();
// 使用表别名
@DataPermission(type = DataPermissionType.SELF, tableAlias = "o")
List<Order> selectOrderListWithAlias();在 Controller 或 Service 中设置权限上下文:
方式一:使用工具类(推荐)
@RestController
public class OrderController {
@GetMapping("/order/list")
public Result<List<Order>> list() {
// 方式1:自动清理(推荐)
List<Order> orders = DataPermissionUtils.executeWithSelf(getCurrentUserId(), () -> {
return orderMapper.selectOrderList();
});
return Result.success(orders);
// 方式2:手动清理
DataPermissionUtils.setSelf(getCurrentUserId());
try {
return Result.success(orderMapper.selectOrderList());
} finally {
DataPermissionUtils.clear();
}
}
@GetMapping("/order/dept")
public Result<List<Order>> listDept() {
// 查询本部门数据
List<Order> orders = DataPermissionUtils.executeWithDept(
getCurrentUserId(),
getCurrentDeptId(),
() -> orderMapper.selectOrderList()
);
return Result.success(orders);
}
@GetMapping("/order/all")
public Result<List<Order>> listAll() {
// 自定义权限配置
DataPermissionContext context = DataPermissionUtils.builder()
.userId(getCurrentUserId())
.deptId(getCurrentDeptId())
.tenantId(getCurrentTenantId())
.permissionType(DataPermissionType.DEPT)
.isAdmin(isSuperAdmin())
.buildContext();
List<Order> orders = DataPermissionUtils.executeWith(context, () -> {
return orderMapper.selectOrderList();
});
return Result.success(orders);
}
}方式二:直接使用上下文(不推荐)
@RestController
public class OrderController {
@GetMapping("/order/list")
public Result<List<Order>> list() {
// 设置权限上下文
DataPermissionContext context = new DataPermissionContext();
context.setUserId(getCurrentUserId());
context.setDeptId(getCurrentDeptId());
context.setTenantId(getCurrentTenantId());
context.setPermissionType(DataPermissionType.SELF);
context.setIsAdmin(isSuperAdmin());
DataPermissionContext.set(context);
try {
return Result.success(orderMapper.selectOrderList());
} finally {
DataPermissionContext.clear(); // 必须清理
}
}
}| 类型 | 说明 | SQL 示例 |
|---|---|---|
SELF |
仅本人数据 | WHERE user_id = 当前用户ID |
DEPT |
本部门数据 | WHERE dept_id = 当前部门ID |
DEPT_AND_CHILD |
本部门及子部门数据 | WHERE dept_id IN (当前部门ID, 子部门ID...) |
ALL |
全部数据 | 不添加条件 |
CUSTOM |
自定义条件 | WHERE {customCondition} |
在 Nacos 配置 share-datapermission-{env}.yaml(可选,不配置也能用):
datapermission:
# 全局开关(默认:true)
enabled: true
# 默认用户字段名(默认:user_id)
default-user-column: user_id
# 默认部门字段名(默认:dept_id)
default-dept-column: dept_id
# 默认租户字段名(默认:tenant_id)
default-tenant-column: tenant_id
# 是否启用多租户过滤(默认:false)
enable-tenant: falseuserColumn:用户字段名;为空时使用全局配置。deptColumn:部门字段名;为空时使用全局配置。tenantColumn:租户字段名;为空时使用全局配置。enableTenant:是否启用多租户过滤;默认 false。tableAlias:表别名;为空时不使用别名。customCondition:自定义条件(仅CUSTOM类型有效)。
SELECT * FROM t_order WHERE status = '1'本人数据(SELF):
SELECT * FROM t_order WHERE status = '1' AND user_id = 4本部门数据(DEPT):
SELECT * FROM t_order WHERE status = '1' AND dept_id = 5本部门及子部门(DEPT_AND_CHILD):
SELECT * FROM t_order WHERE status = '1' AND dept_id IN (5, 6, 7)自定义条件(CUSTOM):
SELECT * FROM t_order WHERE status = '1' AND (status = '2')多租户过滤:
SELECT * FROM t_order WHERE status = '1' AND user_id = 4 AND tenant_id = 1使用表别名:
SELECT * FROM t_order o WHERE o.status = '1' AND o.user_id = 4- 设置上下文:在 Controller 或 Service 中设置权限上下文
- 清理上下文:使用
try-finally确保上下文被清理,避免内存泄漏 - ThreadLocal:上下文存储在 ThreadLocal 中,线程安全
- 超级管理员(
isSuperAdmin = true)跳过数据权限过滤 - 适用于系统管理员、运维人员等需要查看所有数据的角色
DEPT_AND_CHILD类型需要查询子部门列表- 建议在数据库中维护部门层级关系表(如
parent_id) - 可使用 Redis 缓存部门层级关系,提升性能
- 启用多租户过滤后,会自动添加
tenant_id条件 - 适用于 SaaS 系统,确保租户数据隔离
- MyBatis 拦截器会拦截所有 SQL,建议只在需要的 Mapper 方法上添加注解
- 部门层级查询建议使用 Redis 缓存,避免频繁查询数据库
- 自定义条件建议使用索引字段,避免全表扫描
- 支持 MySQL、PostgreSQL、Oracle 等主流数据库
- 使用 JSqlParser 解析 SQL,支持复杂 SQL(JOIN、子查询等)
- 不支持存储过程、函数调用等特殊 SQL
- 权限上下文管理:使用 AOP 或拦截器统一设置权限上下文,避免在每个方法中重复设置。
- 超级管理员判断:根据用户角色判断是否为超级管理员,跳过数据权限过滤。
- 部门层级缓存:使用 Redis 缓存部门层级关系,提升查询性能。
- 多租户隔离:SaaS 系统建议启用多租户过滤,确保数据安全。
- 自定义条件:复杂权限逻辑可使用自定义条件,灵活控制数据范围。
- 日志记录:记录数据权限过滤日志,便于问题排查和审计。
A: 根据用户角色判断,如 user.getRoles().contains("SUPER_ADMIN"),然后在权限上下文中设置 isSuperAdmin = true。
A: 在数据库中维护部门层级关系表(如 parent_id),递归查询子部门。建议使用 Redis 缓存部门层级关系。
A: 影响很小。MyBatis 拦截器只在有 @DataPermission 注解的方法上生效,SQL 改写是内存操作,性能损耗可忽略。
A: 使用 JSqlParser 解析 SQL,支持复杂 SQL。建议在主表上添加权限条件,避免在 JOIN 表上添加。
A: 在 Nacos 配置 datapermission.enabled = false,或在方法上不添加 @DataPermission 注解。