本文目录导读:

在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中拼接子查询会影响性能,建议对大数据量表建立合适的索引
- 测试覆盖:要覆盖测试各种角色组合的权限边界
- 审计日志:记录权限变更日志,便于排查问题
- 兜底策略:如果权限系统故障,应该有降级方案(比如只显示自己的数据)
选择方案时,建议从项目当前阶段和未来预期需求出发,不要过度设计过早优化,初期可以先从简单的注解方案开始,随着系统复杂度提升再逐步升级到更完善的架构。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播