📊 数据权限
1. 概述
数据权限是指根据用户角色和权限范围,控制用户能够访问的数据范围。ZsAdmin实现了基于机构的细粒度数据权限控制,确保用户只能访问其权限范围内的数据,有效保护数据安全和隐私。
2. 数据权限模型
2.1 权限范围
ZsAdmin支持以下5种数据权限范围,满足不同业务场景需求:
| 权限范围 | 描述 | 适用场景 |
|---|---|---|
| 🔄 全部数据 | 可以访问系统中所有数据 | 系统管理员、超级管理员 |
| 🎯 自定义 | 可以访问指定机构的数据 | 跨部门管理者、数据分析师 |
| 🏢 所在机构 | 只能访问当前机构的数据 | 部门负责人、普通管理员 |
| 🌳 所在机构及以下 | 可以访问当前机构及其所有层级的子机构数据 | 上级部门负责人、区域管理者 |
| 👤 仅自己 | 只能访问自己创建的数据 | 普通员工、一线操作人员 |
2.2 实现原理
数据权限通过MyBatis Plus的插件机制实现,在SQL执行前动态解析并添加数据范围条件,核心优势:
- ✅ 无侵入性设计,无需修改现有SQL
- 🔧 支持动态扩展,可自定义数据权限规则
- ⚡ 高性能实现,SQL级别的权限控制
- 🔍 细粒度控制,支持多维度权限组合
3. 核心实现
3.1 数据权限插件配置
java
@Configuration
@EnableConfigurationProperties(TenantProperties.class)
public class MybatisPlusConfig {
@NotNull
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties tenantProperties) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件(可选)
if (Boolean.TRUE.equals(tenantProperties.getEnable())) {
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MultiTenantHandler(tenantProperties)));
}
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新和删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 🎯 数据权限插件 - 核心配置
MyDataPermissionInterceptor myDataPermissionInterceptor = new MyDataPermissionInterceptor();
myDataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());
interceptor.addInnerInterceptor(myDataPermissionInterceptor);
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}3.2 数据权限处理器
java
/**
* 🎯 数据权限处理器
* 核心逻辑:根据用户权限动态生成SQL过滤条件
*/
@Component
public class MyDataPermissionHandler {
private static final Logger logger = LoggerFactory.getLogger(MyDataPermissionHandler.class);
/**
* 生成数据权限SQL片段
*/
@Nullable
public Expression getSqlSegment(@NotNull PlainSelect plainSelect, @NotNull String mappedStatementId) {
// 1. 获取方法上的@DataScope注解
DataScope dataScope = this.getDataScope(mappedStatementId);
if (dataScope == null) {
return plainSelect.getWhere(); // 无注解,不进行数据权限过滤
}
// 2. 执行数据权限过滤
return doFilter(plainSelect, plainSelect.getWhere(), dataScope);
}
/**
* 核心过滤逻辑
*/
@Nullable
public Expression doFilter(@NotNull PlainSelect plainSelect, @Nullable Expression where, @NotNull DataScope dataScope) {
// 🔐 安全校验:获取当前登录用户信息
LoginUserInfo loginUserInfo = SecurityUtil.getUserInfo();
if (loginUserInfo == null || loginUserInfo.getSysUser() == null) {
logger.warn("未获取到登录用户信息,跳过数据权限过滤");
return where;
}
// 🔐 权限校验:管理员直接跳过数据权限过滤
if (Objects.equals(loginUserInfo.getSysUser().getIsAdmin(), AdminEnum.Admin.getValue())) {
return where;
}
// 🔐 权限校验:获取用户数据权限范围
Set<DataScopeEnum> dataScopeTypes = Optional.ofNullable(loginUserInfo.getDataPermission())
.map(DataPermission::getDataScopeTypes)
.orElse(Collections.emptySet());
if (dataScopeTypes.isEmpty() || dataScopeTypes.contains(DataScopeEnum.ALL)) {
return where; // 无权限或全部权限,直接返回
}
// 📊 解析表信息和字段配置
Table table = (Table) plainSelect.getFromItem();
String aliasName = getTableAlias(table, dataScope);
String deptColumn = Optional.ofNullable(dataScope.deptField()).orElse(aliasName + ".create_dept_id");
String userColumn = Optional.ofNullable(dataScope.userField()).orElse(aliasName + ".creator");
// 🎯 生成数据权限条件表达式
List<Expression> expressions = dataScopeTypes.stream()
.filter(scope -> scope != DataScopeEnum.ALL)
.map(scope -> createExpressionForScope(scope, loginUserInfo, deptColumn, userColumn))
.filter(Objects::nonNull)
.toList();
// 🔗 组合多条件为OR关系
Expression dataPermissionExpression = expressions.stream()
.reduce(OrExpression::new)
.orElse(null);
// 🛡️ 安全处理:无有效权限条件时返回原始WHERE
if (dataPermissionExpression == null) {
return where;
}
// 📝 组合原始条件和数据权限条件
return where == null
? dataPermissionExpression
: new AndExpression(where, new Parenthesis(dataPermissionExpression));
}
/**
* 获取表别名(优先使用注解配置,其次使用SQL表别名,最后使用表名)
*/
private String getTableAlias(Table table, DataScope dataScope) {
String annotationAlias = dataScope.tableAlias();
if (annotationAlias != null && !annotationAlias.isEmpty()) {
return annotationAlias;
}
return Optional.ofNullable(table.getAlias())
.map(Alias::getName)
.orElse(table.getName());
}
/**
* 根据数据范围类型创建对应的SQL表达式
* 使用Java 14 switch表达式,简洁高效
*/
private Expression createExpressionForScope(DataScopeEnum dataScope, LoginUserInfo loginUserInfo,
String deptColumn, String userColumn) {
return switch (dataScope) {
case CUSTOM -> createInExpression(deptColumn, loginUserInfo.getDataPermission().getDeptIds());
case DEPT -> createEqualsExpression(deptColumn, loginUserInfo.getSysUser().getSysDeptId());
case DEPT_AND_CHILD -> createInExpression(deptColumn, loginUserInfo.getDataPermission().getDeptIds());
case SELF -> createEqualsExpression(userColumn, loginUserInfo.getSysUser().getSysUserId());
default -> null;
};
}
/**
* 创建IN条件表达式(复用方法,减少重复代码)
*/
private Expression createInExpression(String columnName, Set<Long> values) {
if (values.isEmpty()) return null;
List<Expression> valueExpressions = values.stream()
.map(LongValue::new)
.map(longValue -> (Expression) longValue)
.collect(Collectors.toList());
return new InExpression(new Column(columnName),
new Parenthesis(new ExpressionList<>(valueExpressions)));
}
/**
* 创建=条件表达式(复用方法,减少重复代码)
*/
private Expression createEqualsExpression(String columnName, Long value) {
return new EqualsTo(new Column(columnName), new LongValue(value));
}
/**
* 从Mapper方法中获取@DataScope注解
*/
@Nullable
private DataScope getDataScope(@NotNull String mappedStatementId) {
try {
String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
Class<?> clazz = Class.forName(className);
return Arrays.stream(clazz.getDeclaredMethods())
.filter(m -> m.getName().equals(methodName))
.findFirst()
.map(method -> method.getAnnotation(DataScope.class))
.orElse(null);
} catch (ClassNotFoundException e) {
logger.error("获取mapper类失败", e);
return null;
}
}
}3.3 数据权限注解
java
/**
* 🎯 数据权限注解
* 用于标记需要进行数据权限过滤的Mapper方法
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 表别名(多表关联时使用)
*/
String tableAlias() default "";
/**
* 部门限制范围的字段名称
* 默认:creator_dept(创建人部门ID)
*/
String deptField() default "creator_dept";
/**
* 用户限制范围的字段名称
* 默认:creator(创建人ID)
*/
String userField() default "creator";
}4. 数据权限配置
4.1 角色数据权限配置
在角色管理模块中,可以为每个角色配置数据权限范围:
- 选择权限范围:从5种权限范围中选择合适的权限
- 自定义权限配置:
- 选择具体的机构/部门
- 支持多选和层级选择
- 可视化机构树操作
- 权限继承:子角色默认继承父角色的数据权限
5. 数据权限使用
5.1 在Mapper层使用
使用方式简单快捷:只需在Mapper方法上添加@DataScope注解即可
java
@Mapper
public interface SysUserMapper extends DataPermissionMapper<SysUserEntity> {
/**
* 🎯 查询用户列表(带数据权限)
*/
@DataScope(tableAlias = "u", deptField = "u.dept_id", userField = "u.create_by")
IPage<SysUserEntity> page(Page<SysUserEntity> page, @Param("params") Map<String, Object> params);
/**
* 🎯 查询用户详情(带数据权限)
*/
@DataScope
SysUserEntity selectUserById(@Param("id") Long id);
}5.2 在XML中使用
无需修改XML,数据权限插件会自动处理
xml
<select id="selectUserList" resultType="SysUser">
select * from sys_user u
<where>
<!-- 其他业务条件 -->
<if test="username != null and username != ''">
and u.username like concat('%', #{username}, '%')
</if>
<!-- 🎯 数据权限条件会自动添加到这里 -->
</where>
</select>6. 应用场景
6.1 企业管理系统
| 用户角色 | 数据权限配置 | 业务效果 |
|---|---|---|
| 👑 CEO | 🔄 全部数据 | 查看全公司所有数据,掌握全局运营情况 |
| 🧑💼 部门经理 | 🌳 所在机构及以下 | 查看本部门及所有子部门数据,管理团队绩效 |
| 👨💼 主管 | 🏢 所在机构 | 查看本部门数据,管理部门日常事务 |
| 👩💻 普通员工 | 👤 仅自己 | 只能查看自己创建的数据,保护个人工作空间 |
6.2 多租户系统
- 租户隔离:每个租户只能访问自己的数据
- 跨租户管理:支持平台管理员管理所有租户数据
- 租户内权限:租户内可按角色配置不同数据权限
- 数据共享:支持租户间数据授权和共享
6.3 数据敏感场景
- 财务数据:仅财务人员可查看,严格的数据权限控制
- 客户信息:仅销售团队可查看,保护客户隐私
- 操作日志:仅管理员可查看,确保系统安全
- 统计报表:按角色配置不同的数据可见范围
7. 最佳实践
- 最小权限原则:仅授予用户必要的数据访问权限
- 权限继承设计:合理设计角色层级,避免权限冲突
- 定期权限审计:定期检查和调整用户数据权限
- 数据脱敏配合:敏感数据需结合数据脱敏技术
- 日志记录:记录所有数据访问行为,便于审计和追溯
- 性能优化:
- 合理设计数据权限规则,避免复杂SQL
- 对频繁访问的数据进行缓存
- 定期优化数据权限查询性能
8. 常见问题
Q: 数据权限不生效怎么办?
A: 检查以下几点:
- ✅ 确认已添加
@DataScope注解 - ✅ 确认插件配置正确
- ✅ 确认用户已分配数据权限
- ✅ 查看日志,检查是否有错误信息
Q: 多表关联时如何使用数据权限?
A: 使用tableAlias属性指定表别名:
java
@DataScope(tableAlias = "u", deptField = "u.dept_id")Q: 如何自定义数据权限规则?
A: 继承DataPermissionHandler类,重写相应方法,实现自定义逻辑
9. 总结
ZsAdmin的数据权限系统采用了插件化、注解化、动态化的设计理念,实现了高效、灵活、细粒度的数据权限控制。通过合理配置和使用数据权限,可以有效保护数据安全,防止数据泄露,同时提高系统的可维护性和扩展性。
数据权限是企业级应用的重要组成部分,在设计和实现时需要综合考虑业务需求、性能要求和安全标准,确保系统既安全又易用。