本文目录导读:

在Java应用中实现数据权限(也称为行级权限或数据级权限)通常有几种主流方案,下面我给你详细梳理一下常见的实现方式及其适用场景。
核心思路
数据权限的本质是:不同用户或角色查询同一张表时,返回不同的数据行。
- 销售经理能看到所有销售订单
- 普通销售员只能看到自己的订单
- 区域经理能看到本区域的订单
常见实现方案
方案1:基于MyBatis-Plus的拦截器(最常用)
这是目前最主流的方案,通过自定义拦截器在SQL执行前动态拼接权限条件。
// 1. 自定义拦截器实现 InnerInterceptor
@Component
public class DataPermissionInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 获取当前用户
User currentUser = SecurityUtils.getCurrentUser();
// 判断是否需要数据权限过滤
if (currentUser == null || currentUser.isAdmin()) {
return; // 管理员不过滤
}
// 获取原始SQL
String originalSql = boundSql.getSql();
// 根据用户角色拼接权限条件
String newSql = buildPermissionSql(originalSql, currentUser);
// 通过反射修改BoundSql中的sql
ReflectUtil.setFieldValue(boundSql, "sql", newSql);
}
private String buildPermissionSql(String originalSql, User user) {
// 根据用户角色添加不同的条件
if (user.isDeptManager()) {
return "SELECT * FROM (" + originalSql + ") tmp WHERE dept_id = " + user.getDeptId();
} else {
return "SELECT * FROM (" + originalSql + ") tmp WHERE create_by = " + user.getId();
}
}
}
配置到MyBatis-Plus:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new DataPermissionInterceptor());
return interceptor;
}
}
方案2:基于AOP的自定义注解
通过注解标记需要权限控制的方法,在AOP中动态修改查询条件。
// 1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
String tableAlias() default ""; // 表别名
String field() default "org_id"; // 权限字段
}
// 2. AOP切面
@Aspect
@Component
public class DataPermissionAspect {
@Around("@annotation(dataPermission)")
public Object around(ProceedingJoinPoint joinPoint, DataPermission dataPermission) {
// 获取当前用户的组织权限
List<String> accessibleOrgs = getCurrentUserOrgIds();
// 将组织ID列表存入ThreadLocal
DataPermissionContext.setOrgIds(accessibleOrgs);
DataPermissionContext.setTableAlias(dataPermission.tableAlias());
DataPermissionContext.setField(dataPermission.field());
try {
return joinPoint.proceed();
} finally {
DataPermissionContext.clear();
}
}
}
// 3. 在Mapper中使用
@Mapper
public interface OrderMapper {
@DataPermission(tableAlias = "o", field = "org_id")
@Select("SELECT * FROM orders o WHERE o.status = 'active'")
List<Order> getActiveOrders();
}
方案3:基于RBAC+组织机构树的通用方案
这是企业级应用中最完善的方案,支持灵活的组织架构权限。
-- 权限表设计
CREATE TABLE sys_org (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
org_name VARCHAR(100),
org_level INT, -- 组织层级
path VARCHAR(500) -- 路径,如:/1/2/3/
);
CREATE TABLE sys_role_org (
id BIGINT PRIMARY KEY,
role_id BIGINT,
org_id BIGINT,
data_scope INT -- 1:仅本人 2:本部门 3:本部门及子部门 4:全部
);
-- 查询逻辑
SELECT * FROM orders o
WHERE EXISTS (
SELECT 1 FROM sys_role_org ro
JOIN sys_user_role ur ON ur.role_id = ro.role_id
WHERE ur.user_id = #{currentUserId}
AND (
(ro.data_scope = 1 AND o.create_by = #{currentUserId})
OR
(ro.data_scope = 2 AND o.org_id = ro.org_id)
OR
(ro.data_scope = 3 AND o.org_id IN (
SELECT id FROM sys_org WHERE path LIKE (
SELECT CONCAT(path, '%') FROM sys_org WHERE id = ro.org_id
)
))
OR
(ro.data_scope = 4) -- 全部数据
)
)
方案4:基于Sa-Token框架(推荐)
Sa-Token是一个轻量级Java权限框架,提供了开箱即用的数据权限解决方案。
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
// 1. 自定义数据权限接口
@Component
public class CustomDataPermission implements SaDataPermissionHandler {
@Override
public SaDataPermissionResult getDataPermission(List<String> roles) {
SaDataPermissionResult result = new SaDataPermissionResult();
// 根据角色返回不同的权限条件
if (roles.contains("admin")) {
result.setCondition("1=1"); // 全部数据
} else if (roles.contains("dept_manager")) {
result.setCondition("dept_id = " + StpUtil.getLoginIdAsLong());
} else {
result.setCondition("create_by = " + StpUtil.getLoginIdAsLong());
}
return result;
}
}
// 2. 在Service中使用
@Service
public class OrderService {
@SaCheckDataPermission(table = "orders") // 自动拼接权限条件
public List<Order> getOrders() {
return orderMapper.selectList(null);
}
}
缓存优化建议
数据权限查询通常涉及连表查询,建议做缓存优化:
@Component
public class UserPermissionCache {
@Cacheable(value = "user_permission", key = "#userId", unless = "#result.isEmpty()")
public List<Long> getUserAccessibleOrgIds(Long userId) {
// 查询用户可访问的所有组织ID(包含子组织)
return userMapper.getUserAccessibleOrgIds(userId);
}
@CacheEvict(value = "user_permission", key = "#userId")
public void clearUserPermissionCache(Long userId) {
// 权限变更时清除缓存
}
}
最佳实践建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 新项目、快速迭代 | Sa-Token框架 | 开箱即用,集成简单 |
| 已有MyBatis-Plus项目 | 拦截器方案 | 无需改代码,全局生效 |
| 复杂组织机构权限 | RBAC+组织树 | 灵活度高,支持层级 |
| 简单场景(仅本人数据) | MyBatis-Plus条件构造器 | 最简单,最低开销 |
注意事项
- 性能问题:在SQL中拼接子查询会影响性能,建议对大数据量表建立合适的索引
- 测试覆盖:要覆盖测试各种角色组合的权限边界
- 审计日志:记录权限变更日志,便于排查问题
- 兜底策略:如果权限系统故障,应该有降级方案(比如只显示自己的数据)
选择方案时,建议从项目当前阶段和未来预期需求出发,不要过度设计过早优化,初期可以先从简单的注解方案开始,随着系统复杂度提升再逐步升级到更完善的架构。