尧图精选

MyBatis-Plus数据权限控制实现与优化

🕒 发布时间:2026/9/17 19:30:49 📁 来源:尧图网络
1. MyBatis-Plus数据权限控制的核心价值与应用场景在企业级应用开发中数据权限控制是保证系统安全性的重要机制。与传统的功能权限不同数据权限关注的是同一功能下不同用户能看到的数据范围差异。这种细粒度的权限控制对于多租户系统、集团型组织架构等场景尤为重要。1.1 数据权限的四种基础模式1.1.1 全权限模式全权限模式通常仅开放给系统管理员允许查看和操作系统中的所有数据。在实际项目中我们通常会在数据权限拦截器中首先判断用户角色如果是管理员则直接跳过后续权限过滤逻辑。// 管理员权限判断示例 if (user.isAdmin()) { return originalSql; // 直接返回原始SQL }1.1.2 本级权限本级权限限制用户只能访问自己创建或直接关联的数据。这种模式常见于CRM系统例如销售人员只能看到自己跟进的客户。实现时通常会在WHERE条件中添加create_by #{userId}过滤条件。1.1.3 本级及子级权限这种模式适用于树形组织结构如部门层级。用户可以看到自己所在部门及所有下级部门的数据。实现这种权限需要考虑组织结构的递归查询-- 部门层级查询示例 SELECT * FROM sys_user WHERE dept_id IN ( SELECT id FROM sys_dept WHERE FIND_IN_SET(#{deptId}, ancestors) )1.1.4 自定义权限自定义权限最为灵活可以根据业务需求配置不同的数据范围。例如按区域划分的数据权限按项目划分的可见范围混合维度的数据过滤1.2 MyBatis-Plus的实现优势相比传统实现方案MyBatis-Plus的拦截器机制具有明显优势无侵入性不需要修改业务代码通过拦截器统一处理性能优异在SQL执行前进行过滤避免不必要的数据传输灵活配置支持基于注解的细粒度控制易于维护权限逻辑集中管理降低维护成本重要提示在实际项目中应避免在业务层进行数据过滤。这种先查询后过滤的方式不仅性能低下还可能造成敏感数据泄露。2. MyBatis-Plus拦截器深度解析2.1 MyBatis插件体系回顾MyBatis的插件体系基于责任链模式实现允许我们在SQL执行的各个关键节点插入自定义逻辑。MyBatis-Plus的InnerInterceptor接口进一步简化了拦截器的开发public interface InnerInterceptor { // 查询前拦截 void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql); // 更新前拦截 void beforeUpdate(Executor executor, MappedStatement ms, Object parameter, BoundSql boundSql); }2.2 数据权限拦截器核心实现一个完整的数据权限拦截器需要处理以下几个关键点SQL解析使用JSqlParser解析原始SQL权限上下文获取当前用户的权限范围条件构建根据权限规则生成过滤条件SQL重写将条件注入到原始SQL中下面是核心处理流程的伪代码public void beforeQuery(...) { // 1. 获取原始SQL String originalSql boundSql.getSql(); // 2. 获取权限上下文 DataPermissionContext context getContext(); // 3. 解析SQL并添加条件 Statement stmt CCJSqlParserUtil.parse(originalSql); stmt.accept(new DataPermissionVisitor(context)); // 4. 更新BoundSql updateBoundSql(boundSql, stmt.toString()); }2.3 SQL重写策略根据不同的查询场景我们需要采用不同的SQL重写策略2.3.1 简单查询的条件追加对于没有JOIN的简单查询直接在WHERE子句中追加条件-- 原始SQL SELECT * FROM sys_user -- 重写后 SELECT * FROM sys_user WHERE dept_id IN (1,2,3)2.3.2 JOIN查询的特殊处理对于包含JOIN的复杂查询需要确定条件应该加到哪个表上-- 原始SQL SELECT u.*, d.dept_name FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id d.id -- 重写后 SELECT u.*, d.dept_name FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id d.id WHERE u.dept_id IN (1,2,3)2.3.3 子查询的处理当遇到子查询时需要递归处理每个子查询Override public void visit(SubSelect subSelect) { subSelect.getSelectBody().accept(this); }3. 完整数据权限系统设计3.1 系统架构设计一个健壮的数据权限系统通常包含以下组件权限上下文存储当前用户的权限信息规则配置定义不同场景下的权限规则拦截器核心负责SQL解析和重写注解驱动提供声明式的权限控制┌─────────────────────────────────┐ │ 用户请求 │ └───────────────┬─────────────────┘ │ ┌───────────────▼─────────────────┐ │ 权限上下文构建 │ │ ┌─────────────────────────────┐ │ │ │ - 用户信息 │ │ │ │ - 角色信息 │ │ │ │ - 部门信息 │ │ │ │ - 数据范围 │ │ │ └─────────────────────────────┘ │ └───────────────┬─────────────────┘ │ ┌───────────────▼─────────────────┐ │ 注解/配置规则解析 │ └───────────────┬─────────────────┘ │ ┌───────────────▼─────────────────┐ │ MyBatis-Plus拦截器 │ │ ┌─────────────────────────────┐ │ │ │ 1. SQL解析 │ │ │ │ 2. 条件注入 │ │ │ │ 3. SQL重写 │ │ │ └─────────────────────────────┘ │ └───────────────┬─────────────────┘ │ ┌───────────────▼─────────────────┐ │ 数据库查询执行 │ └─────────────────────────────────┘3.2 核心注解设计3.2.1 DataPermission注解Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DataPermission { // 表别名 String tableAlias() default ; // 权限字段 String field() default dept_id; // 权限类型 DataPermissionType type() default DataPermissionType.DEPT; // 是否忽略权限控制 boolean ignore() default false; }3.2.2 DataScope注解Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface DataScope { // 部门表别名 String deptAlias() default ; // 用户表别名 String userAlias() default ; // 权限字符 String permission() default ; }3.3 权限上下文实现权限上下文需要包含用户的基本信息和数据范围public class DataPermissionContext { private Long userId; private String username; private Long deptId; private SetLong deptIds; // 有权限的部门ID集合 private Integer dataScope; // 数据范围类型 private boolean admin; // 是否管理员 // 获取部门条件 public String getDeptCondition(String tableAlias, String field) { String column StringUtils.isNotBlank(tableAlias) ? tableAlias . field : field; switch (dataScope) { case 1: return ; // 全部数据 case 2: return String.format( AND %s IN (%s), column, deptIds.stream().map(String::valueOf).collect(Collectors.joining(,))); case 3: return String.format( AND %s %d, column, deptId); // 其他情况处理... } } }4. 高级特性实现4.1 多租户数据隔离多租户系统的数据隔离是数据权限的特殊形式通常有以下实现方式独立数据库每个租户使用单独的数据库实例共享数据库独立Schema同一数据库实例不同Schema共享Schema通过tenant_id字段区分下面是通过MyBatis-Plus拦截器实现共享Schema方案的代码片段public class TenantInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 获取当前租户ID String tenantId TenantContext.getCurrentTenantId(); if (StringUtils.isNotBlank(tenantId)) { // 解析并修改SQL Statement stmt CCJSqlParserUtil.parse(boundSql.getSql()); if (stmt instanceof Insert) { // 处理INSERT语句 handleInsert((Insert) stmt, tenantId); } else if (stmt instanceof Select) { // 处理SELECT语句 handleSelect((Select) stmt, tenantId); } // 更新SQL updateBoundSql(boundSql, stmt.toString()); } return invocation.proceed(); } private void handleSelect(Select select, String tenantId) { select.getSelectBody().accept(new TenantVisitor(tenantId)); } }4.2 数据权限缓存优化频繁的SQL解析和重写会影响系统性能可以通过缓存优化public class CachedDataPermissionInterceptor extends DataPermissionInterceptor { private final CacheString, String sqlCache Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(); Override protected String processDataPermission(String sql, DataPermissionContext context) { String cacheKey generateCacheKey(sql, context); return sqlCache.get(cacheKey, k - super.processDataPermission(sql, context)); } private String generateCacheKey(String sql, DataPermissionContext context) { return sql.hashCode() | context.getUserId() | context.getDataScope(); } }5. 复杂业务场景解决方案5.1 多层次部门权限控制对于大型组织的层级结构我们需要递归查询所有子部门public ListLong getDeptAndChildIds(Long deptId) { // 使用CTE递归查询(MySQL 8.0) String sql WITH RECURSIVE dept_tree AS ( SELECT id FROM sys_dept WHERE id ? UNION ALL SELECT d.id FROM sys_dept d JOIN dept_tree dt ON d.parent_id dt.id ) SELECT id FROM dept_tree; return jdbcTemplate.queryForList(sql, Long.class, deptId); }5.2 行列混合权限控制某些场景需要同时控制行级(能看到哪些记录)和列级(能看到哪些字段)权限public class ColumnPermissionInterceptor implements InnerInterceptor { Override public void beforeQuery(...) { // 行级权限处理 String filteredSql dataPermissionInterceptor.processDataPermission(originalSql, context); // 列级权限处理 if (needColumnFilter(context)) { Statement stmt CCJSqlParserUtil.parse(filteredSql); stmt.accept(new ColumnPermissionVisitor(context)); filteredSql stmt.toString(); } updateBoundSql(boundSql, filteredSql); } }6. 性能优化与安全防护6.1 性能优化策略SQL解析缓存缓存解析后的SQL AST权限结果缓存缓存最终生成的SQL批量处理优化对批量操作进行特殊处理懒加载策略延迟加载权限数据6.2 安全防护措施SQL注入防护使用预编译方式拼接条件权限绕过检查验证最终SQL是否包含必要条件敏感操作日志记录所有权限变更操作定期安全审计检查权限配置是否合理7. 测试策略完善的测试是保证数据权限系统可靠性的关键7.1 单元测试重点SQL解析测试验证各种SQL语句能否正确解析条件注入测试检查条件是否正确添加到SQL中边界情况测试测试空条件、多条件组合等情况7.2 集成测试场景多租户隔离测试确保租户间数据完全隔离性能压力测试评估权限系统对性能的影响并发场景测试验证线程安全性7.3 测试工具推荐H2内存数据库快速执行单元测试JMeter进行性能压力测试TestContainers集成测试使用真实数据库在实际项目中我们通常会遇到各种复杂的数据权限需求。通过MyBatis-Plus拦截器实现的数据权限方案不仅能够满足大多数业务场景还能保持代码的整洁和可维护性。关键在于合理设计权限模型并在性能和安全性之间找到平衡点。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →