手把手调试 RuoYi-Vue-Plus 数据权限:用IDEA断点摸清 PlusDataPermissionInterceptor 的完整工作流
深度调试RuoYi-Vue-Plus数据权限:IDEA断点追踪全流程实战
当接手一个采用RuoYi-Vue-Plus框架的遗留项目时,数据权限模块往往是需要优先理解的核心功能之一。与传统的代码阅读相比,通过IDEA调试器实时追踪执行流程,能更直观地掌握PlusDataPermissionInterceptor如何动态改写SQL语句。本文将带您从发送测试请求开始,逐步设置关键断点,完整还原数据权限从注解解析到SQL重写的技术细节。
1. 环境准备与调试入口
在开始调试前,确保已配置好以下环境:
- JDK 1.8+ 与 IntelliJ IDEA 2021+
- 正常运行的RuoYi-Vue-Plus项目(建议使用3.5.0+版本)
- 示例Controller方法(如带
@DataPermission注解的查询接口)
关键调试启动步骤:
- 在测试Controller方法上添加
@DataPermission注解
@DataPermission(deptAlias = "d", userAlias = "u") @GetMapping("/list") public TableDataInfo list(DemoEntity entity) { // 业务逻辑 }- 启动项目并打开Postman发送测试请求
- 在IDEA中全局搜索
PlusDataPermissionInterceptor类
提示:调试前建议关闭MyBatis二级缓存,避免旧SQL影响观察效果
2. 拦截器入口断点设置
在PlusDataPermissionInterceptor中设置以下关键断点:
2.1 beforeQuery方法断点
这是拦截器的第一个入口点,当执行Mapper查询时会触发。重点关注三个核心判断:
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 断点1:检查是否忽略数据权限 if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { return; } // 断点2:验证权限有效性 if (dataPermissionHandler.isInvalid()) { return; } // 断点3:SQL解析入口 parserSingle(boundSql, ms, parameter); }调试观察要点:
ms.getId()值应匹配当前Mapper方法全限定名boundSql.getSql()原始SQL语句内容parameter传递的查询参数
2.2 processSelect方法断点
该方法负责处理SELECT语句的重写逻辑:
protected void processSelect(Select select, int index, String sql, Object obj) { // 断点4:观察select对象结构 PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); // 断点5:WHERE条件注入前 Expression where = plainSelect.getWhere(); // 断点6:调用权限处理器 setWhere(plainSelect, where, (MappedStatement) obj); }关键变量监控表:
| 变量名 | 预期值类型 | 调试意义 |
|---|---|---|
| select | net.sf.jsqlparser.statement.select.Select | 解析后的SQL对象 |
| plainSelect.getWhere() | net.sf.jsqlparser.expression.Expression | 原始WHERE条件 |
| obj | MappedStatement | 当前Mapper语句元数据 |
3. 权限上下文追踪
数据权限的核心在于如何将用户信息转换为SQL条件。通过DataPermissionHelper的调试可以清晰看到这一过程:
3.1 用户信息获取流程
在PlusDataPermissionHandler.getSqlSegment()方法中设置断点:
public String getSqlSegment(String where, String mappedStatementId) { // 断点7:获取当前用户 Object user = DataPermissionHelper.getVariable("user"); if (user == null) { // 从数据库加载用户的逻辑 } // 断点8:超级管理员判断 if (isAdmin(user)) { return where; } // 断点9:构建数据过滤条件 return buildDataFilter(where, user, mappedStatementId); }典型调试场景:
- 以admin用户登录时,直接返回原始WHERE条件
- 普通用户登录时,进入
buildDataFilter构建部门过滤条件
3.2 注解解析过程
在findAnnotation方法中观察注解缓存机制:
private DataPermission findAnnotation(String mappedStatementId) { // 断点10:缓存查询 DataPermission dataPermission = dataPermissionCacheMap.get(mappedStatementId); if (dataPermission == null) { // 反射获取注解 dataPermission = AnnotationUtil.getAnnotation(/*...*/); // 断点11:缓存写入 dataPermissionCacheMap.put(mappedStatementId, dataPermission); } return dataPermission; }注意:修改注解后需要重启应用或清除缓存才能生效
4. SQL重写全流程观察
最终的SQL改写发生在setWhere方法中,这是调试最关键的环节:
4.1 原始SQL与改写对比
通过对比boundSql.getSql()的前后变化,可以直观看到:
-- 原始SQL SELECT * FROM sys_user -- 改写后SQL(非管理员) SELECT * FROM sys_user WHERE dept_id IN (100,101)4.2 多条件拼接逻辑
当存在多个权限规则时,观察条件拼接方式:
// 在buildDataFilter方法中 StringBuilder dataFilter = new StringBuilder(); for (Role role : roles) { dataFilter.append(role.getDataScope()) .append(" OR "); } // 最终处理后的WHERE条件 where = "(" + dataFilter.substring(0, dataFilter.length() - 4) + ")"常见调试问题排查:
- 条件缺失:检查
DataPermissionHelper中的用户信息是否正确 - SQL语法错误:观察
JSQLParser解析后的AST结构 - 注解不生效:确认
mappedStatementId是否匹配Mapper方法
通过这种沉浸式调试方法,不仅能理解数据权限的实现原理,更能掌握二次开发时的定制技巧。比如需要扩展权限维度时,可以重点修改PlusDataPermissionHandler.buildDataFilter的实现逻辑。
