news 2026/10/16 4:12:57

JeecgBoot数据权限实战指南:从配置到SQL排错全流程踩坑记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JeecgBoot数据权限实战指南:从配置到SQL排错全流程踩坑记录

「数据权限」在 jeecgboot 里是一个让人又爱又恨的功能。爱的是它确实能解决行级数据隔离,不用在每个查询里手动拼条件;恨的是官网文档把配置路径写得清清楚楚,却把真正决定成败的细节全部留给你自己踩。我之前在某外包项目里接了一个需求:销售只能看自己创建的客户,区域经理能看本区域的客户,管理员看全部。当时按官网步骤配了数据权限规则、在查询接口加了注解,结果普通销售登录后依然能看到全量数据。这篇文章就把那次排查过程中涨的知识全部整理出来,覆盖系统规则、自定义规则、缓存、SQL 日志等常见问题,给正在被 jeecgboot 数据权限折磨的同学一份可以按图索骥的避坑手册。

1. 为什么照着官网配完,接口还是不按规则过滤

1.1 一个真实需求引发的连锁排查

那个需求本身很简单:订单列表要按登录人过滤,销售只能看到create_by等于自己的订单。当时我第一反应是去系统管理 → 菜单管理里找到订单菜单,新增一条数据权限规则,字段名填create_by,规则值选「读取当前登录人的用户名」,然后在订单列表接口上加@PermissionData注解。看起来和官网示例一模一样,结果普通销售登录后,订单数据一条不少,规则完全没生效。

排查的第一步是怀疑注解没生效。我加日志、检查切面,注解还在,方法也被 Spring 代理了。第二步怀疑规则 SQL 有问题,反复对照官网示例也没看出区别。最后偶然发现,前端页面调用的列表接口挂在「订单管理」菜单下,而我配置规则时选中的是「历史订单」菜单。数据权限规则是挂在菜单上的,切面在执行时按当前请求归属的菜单权限去找规则,菜单对不上,规则自然进不到最终 SQL 里。

这个坑非常隐蔽,因为接口地址、页面功能都没变,只有菜单配置变了或者规则挂在父级目录上,就会出现「配了等于没配」的现象。

1.2 数据权限生效的三个必要条件:官网没串起来讲

照着官网操作不生效,根本原因是文档把三个必要条件分散在不同章节,没有一篇内容明确告诉你它们必须同时满足。我在项目里反复验证后,总结出这三个条件:

  1. 菜单上必须配置数据权限规则,且前端页面请求的接口必须落在该菜单的权限范围内。
  2. 后端查询方法上必须加数据权限注解,而且要加到 Spring 代理能拦截到的方法上。
  3. 查询 SQL 的结构必须允许框架追加过滤条件,不能用写死的 SQL 绕过框架拼接。

第一个条件负责「规则存在」,第二个条件负责「规则被触发」,第三个条件负责「规则能生效」。很多人只满足了第二、三个条件,规则自然不生效;也有的人三个条件都满足,但因为菜单对应关系错位,功能依然失灵。官网文档的配置教程只讲了「在哪配」,没讲「配了之后是怎么和代码联动起来的」,这是最大的信息缺口。

1.3 搞清楚权限 SQL 最终拼到哪里

数据权限的本质是行级过滤,也就是在查询 SQL 上追加一段 WHERE 条件。jeecgboot 在处理带注解的查询请求时,会先拿到当前登录用户、再根据规则生成类似create_by = 'zhangsan'的片段,最终作为额外查询条件并到你原本的 where 语句里。

搞清楚这一点之后,调试思路就清晰了:不要去猜规则有没有进 SQL,直接把最终执行的那条 SQL 拿出来看。如果 where 里没有出现预期的权限条件,说明规则没命中;如果出现了但数据还是不对,再去检查条件是 AND 还是 OR、字段名是否匹配。后面我会专门讲怎么把最终 SQL 打出来,那是排查整个数据权限问题最快的一条路。

2. 规则配置落地:菜单、注解、字段名三件套

2.1 菜单规则的正确打开方式

配置入口在「系统管理 → 菜单管理」,找到目标菜单后进入数据权限规则配置页。新增规则时有几个关键项:规则名称、字段名、规则值。规则名称只是备注,方便你区分用途;真正决定行为的是字段名和规则值。

字段名必须填数据库表的真实字段名,比如create_by、sys_org_code、sys_company_code,不是 Java 实体里的createBy、sysOrgCode。规则值选择系统规则时,常见有三个选项:读取当前登录人的用户名、读取当前登录人的部门编码、读取当前登录人的公司编码。选好之后保存,规则就算配置完成。

这里有一个官网没细说的点:数据权限规则挂在「菜单」上,不是挂在「目录」上。如果你把规则配在目录节点上,子菜单的查询接口不一定能继承到。经验做法是,规则配在具体页面菜单上,配完后立刻用测试账号重新登录、重新打开页面验证,不要改完就在旧会话里刷新,否则会被用户上下文干扰。

2.2 @PermissionData 注解的摆放位置

代码侧的工作是在查询方法上加注解。最常见的情况是加在 Controller 层:

@RestController @RequestMapping("/order") public class OrderController { @GetMapping("/list") @PermissionData public Result<IPage<Order>> list(Page<Order> page, @RequestParam Map<String, Object> params) { // 查询逻辑 return Result.OK(orderService.page(page, queryWrapper)); } }

注解能不能生效,核心在于加注解的方法是否被 Spring AOP 代理。Controller 是最稳的位置,因为请求进来必然经过它。如果你把注解加在 Service 方法上,而 Service 内部又有同类方法的this.xxx()调用,注解就不会被代理拦截,因为this调用绕过了代理对象。

另外一个容易忽略的情况是:Controller 方法只是一个转发壳,真正的分页查询写在 Service 里。这时注解加在 Controller 上能不能让 Service 里的查询拿到权限上下文,取决于框架版本把数据权限上下文放在哪个环节。最稳妥的做法是:Controller 方法加注解,Service 查询方法也加注解,两边都覆盖,避免因内部调用链过长导致上下文丢失。

2.3 字段名写库表字段还是实体属性?写错直接报错

数据权限规则里的「字段名」最终会被拼进 SQL,所以写错的话会在执行阶段直接报错,而不是静默失败。最常见的错误就是把实体属性名当成数据库字段名填进去,例如填了createBy,最终 SQL 变成WHERE createBy = 'admin',数据库报Unknown column。

业务含义数据库字段实体属性
创建人create_bycreateBy
部门编码sys_org_codesysOrgCode
公司编码sys_company_codesysCompanyCode

如果查询 SQL 里用了表别名,字段名还要考虑别名前缀。比如主表起了别名o,而create_by字段在多个表里都存在,那规则里就要写o.create_by = '#{sysUserCode}'这种带别名的写法,否则数据库会报字段不明确。这一点在自定义规则里尤其常见,因为系统规则拼出来的条件未必带别名。

3. 系统规则:用户名、部门、公司那些隐藏行为

3.1 用户名规则的边界:不等于「只看自己」

配置「用户名」系统规则后,框架生成的 SQL 类似create_by = 'admin'。这个条件本身很简单,但有个前提:你的业务表create_by字段里存的值,必须和框架取到的登录用户标识一致。jeecgboot 默认取的是登录用户名username,如果你们项目在写入数据时把create_by存成了真实姓名或者用户 ID,这条规则要么过滤不到任何数据,要么把该用户的数据全部滤掉,表现就是「有的人看不到数据,有的人反而能看到别人的数据」。

另一个边界是历史数据。很多表在早期没有维护create_by,老数据这一列是空值。启用用户名规则后,这些空值数据对所有普通用户都不可见,看起来像「数据丢失」,其实是规则过滤掉了。

遇到这种情况,建议先写一个临时 SQL 统计一下表里create_by的取值分布,再和当前登录用户标识对比。如果确实存在多种存法,优先做一次数据订正,统一成用户名,而不是去修改数据权限规则将就历史脏数据。

3.2 部门规则与子部门:一个 LIKE 引发的血案

部门规则的核心是取当前登录人的部门编码,然后拼sys_org_code = 'xxx'这样的条件。更复杂的场景是勾选「包含子部门」选项,这时框架会先查出当前部门及所有下级部门的编码集合,再拼成一个范围条件。

jeecgboot 的组织机构表里org_code是层级编码,比如顶级部门A01,子部门A01A01、A01A02。包含子部门时,常见生成逻辑是:

(sys_org_code = 'A01' OR sys_org_code LIKE 'A01%')

这个方案依赖一个前提:部门编码必须具备层级前缀关系。如果你项目里改了部门编码规则,用的是 UUID 或纯数字无前缀编码,那么LIKE 'A01%'根本带不出子部门,最终结果会变成只能看到本部门数据,子部门数据全部消失。

更常见的坑是业务表里压根没有sys_org_code这个字段,只有dept_id外键。系统规则拼出来的是sys_org_code = 'A01',但业务表里根本没有这一列,接口直接报错。这种时候要么在业务表里冗余一个部门编码字段,要么走自定义规则去 join 部门表。

3.3 公司规则与多规则叠加:AND 还是 OR 得先知道

公司规则和部门规则的思路类似,适用于多组织架构的系统。配置字段名sys_company_code,读取当前登录人的公司编码,就能实现「只看本公司数据」的过滤。

真正需要警惕的是多规则叠加。你可以在同一个菜单下配两条系统规则,比如「用户名规则 + 部门规则」。框架在拼接多条规则时,默认使用 AND 连接,生成的效果是:

(create_by = 'admin' AND sys_org_code = 'A01')

这和你想象的「本人创建的或者本部门的数据」完全是两回事。如果你希望满足任一条件就放行,用系统规则组合是做不到的,必须上自定义规则写 OR 条件。

目标需求系统规则组合实际生成效果
只看本人创建用户名规则create_by = 'admin'
看本部门及子部门部门规则(含子部门)(sys_org_code = 'A01' OR sys_org_code LIKE 'A01%')
本部门或本人创建的并集用户名 + 部门规则create_by = 'x' AND sys_org_code = 'A01'(不是你要的并集)

这个「默认 AND」的行为在官网文档里几乎没有强调,却影响了很多规则的预期效果。我的建议是,先明确业务语义是「且」还是「或」,再决定用什么规则;发现自己配的系统规则组合结果不对,大概率是 AND/OR 语义理解错了。

4. 自定义规则:能力上限高,坑也最深

4.1 自定义 SQL 和脚本两种写法的适用边界

当系统规则满足不了「或」关系、跨表过滤、按角色分流这些复杂场景时,就得用自定义规则。jeecgboot 的自定义规则大致分两种:一种是在规则值里直接写 SQL 条件片段,一种是通过脚本返回 SQL 片段。

自定义 SQL 适合逻辑相对固定的情况,比如:

(sys_company_code = '#{sysCompanyCode}' OR create_by = '#{sysUserCode}')

注意这里返回的是条件片段,不能带where关键字,也不能带末尾分号。框架会把这段内容直接拼到主查询上,语法错误会在执行阶段暴露。

脚本方式适合需要判断角色、循环组织层级之类的逻辑,示意如下:

var sql = "(sys_org_code = '" + sysOrgCode + "' OR create_by = '" + sysUserCode + "')"; sql;

脚本的返回值就是最终拼入的 SQL 片段。适用边界上,我的建议是能写 SQL 条件就不要写脚本,因为脚本在请求链路中执行,如果里面写了复杂循环或外部调用,性能问题很难定位。只有遇到条件分支和动态拼接需求时,才考虑脚本方案。

4.2 模板占位符、JS 引擎与 Java 版本的那点事

自定义规则里最隐蔽的坑有三个,都和运行环境有关。

第一个坑是模板占位符写法不统一。数据权限规则里的#{sysUserCode}、${sysOrgCode}这类占位符,不同 jeecgboot 版本使用的模板引擎可能不一样,有的版本接受${},有的版本接受#{}。官网示例往往没写清楚适用于哪个版本,直接照抄很容易出现模板渲染异常。判断方法很简单:配置完规则后调用一次接口,如果日志里出现模板解析错误,优先怀疑占位符风格和版本不匹配。

第二个坑是 JS 引擎。老项目默认依赖 JDK 自带的脚本引擎,但 JDK 高版本把内置引擎移除了。如果你们做 JDK 版本升级后,数据权限脚本突然全部失效,接口直接报找不到脚本引擎,那就是引擎缺失问题。新版 jeecgboot 会内置兼容引擎,但自己管理依赖时很容易忽略这一点。

第三个坑是特殊字符。脚本拼接 SQL 时,如果用户姓名或部门名称里包含单引号,比如O'Brien,拼出来的 SQL 会被截断甚至报错。对可能含特殊字符的值,要提前做转义或替换,不要侥幸认为生产数据一定干净。

4.3 一次「改完配置接口 500」的全链路排查

有一次我在项目里遇到一个诡异的 500:某个列表接口在别人改了一条数据权限规则之后开始报错,日志里明显是模板渲染失败,但页面配置看起来又没毛病。整个排查过程可以作为模板参考:

第一步,看接口异常日志,确认报错发生在数据权限规则处理环节,而不是业务查询本身。这一步排除了 SQL 写错、表结构变更等干扰项。

第二步,打开菜单管理,检查那条刚改过的自定义规则。发现里面的占位符用的是${sysOrgCode},而同项目其它正常菜单的规则用的都是#{}风格。

第三步,对比正常规则,把占位符改成项目实际支持的风格,重新登录测试账号,接口恢复。复盘结论是:改规则的人拿着官网示例直接抄,但官网示例没标注版本,项目实际模板引擎和示例不一致,报错也就顺理成章。

这件事之后我养成一个习惯:写自定义规则永远先写一条最简单的条件,验证跑通后再慢慢加复杂度。一次性写一大段脚本或 SQL,一旦报错,你根本分不清是占位符问题、语法问题还是字段问题。

5. 排错与验收:把看不见的 SQL 拖到台面上

5.1 三步看到完整的最终 SQL

数据权限问题最忌讳「猜」。最有效的排查方式就是看到最终执行的那条 SQL。操作分三步:

第一步,在application.yml里把查询 Mapper 的日志级别打开:

logging: level: your.mapper.package: debug

第二步,调用出问题的接口,在日志里找到 MyBatis 打印的Preparing:那一行,看 where 条件里是否出现了预期中的权限条件。

第三步,对照最终 SQL 判断问题方向。如果权限条件根本没出现,规则没命中,回到菜单对应关系、注解位置、缓存三个方面检查;如果权限条件出现了但数据不对,重点看它是 AND 还是 OR、字段名是否匹配、值是否为 null。

有些项目还引入了 p6spy 或慢 SQL 日志,也能看到完整 SQL。不管用哪种方式,拿到最终 SQL 之后,一次排查顶十次猜测。

5.2 缓存导致配置不生效的高频现场

「改了菜单数据权限规则,刷新页面还是旧效果」这个现象,十次里有八次是缓存造成的。jeecgboot 的菜单权限和部分规则信息会缓存起来,用户登录后规则快照也可能已经在缓存里。

遇到这种情况,不要急着怀疑代码,先做两件事:第一,去系统管理的缓存清理页面把相关缓存清掉,或者直接重启服务;第二,让测试账号退出登录,重新登录一次,让规则快照重新生成。

这里有个容易混淆的点:页面刷新不代表用户登录态刷新,数据权限是按登录用户的上下文去取规则和参数的。如果你在浏览器里反复刷新都看不到新规则,一定记得重新登录再验证,这一步能省下大量排查时间。

5.3 上线前用三种账号各测一遍

数据权限功能上线前,我强烈建议准备三种账号逐项验证:管理员账号、普通员工账号、部门负责人账号。管理员账号用来确认「不受限用户不受影响」,普通员工账号用来验证「只看本人数据」,部门负责人账号用来验证「看本部门及子部门数据」。

每种账号至少验证三件事:应该看到的数据有没有出现;不该看到的数据有没有泄漏;切换部门、切换角色之后,可见数据集合是否随之变化。尤其是「不该看到的数据」,很多人只测了「能看到什么」,没测「看不到什么」,结果规则根本没生效也没被发现。

最好在列表页展示创建人、创建部门这两列,测试时肉眼就能判断过滤结果是否正确。等数据权限规则真正跑顺之后,再把敏感列隐藏。

我在实际项目里有一个很深的体会:数据权限的坑,多数不是配置端或代码端单独的问题,而是两端没有对上。菜单和接口的对应关系、字段名风格、缓存和登录态,这些「中间层」才是事故高发区。所以我把最终 SQL 日志当作调试数据权限的第一工具,这个习惯帮我避开了大量盲猜。如果你也正在被这个功能折磨,不妨先从打 SQL 这一步开始。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 4:12:57

EF Core 操作 PostgreSQL JSONB 的三种方式:性能实测与选型建议

EF Core 和 PostgreSQL 的组合在 .NET 生态里使用得越来越频繁&#xff0c;但只要一提到 JSONB&#xff0c;很多人的第一反应就是“用 EF Core 操作到底该怎么搞”。官方文档写了但不够详细&#xff0c;社区里说法又五花八门&#xff0c;什么“必须写原生 SQL”“用 OwnsOne 映…

作者头像 李华
网站建设 2026/10/16 4:12:56

SQL执行顺序全解:从执行计划到性能优化

1. 一条SQL语句的"执行剧本"&#xff1a;先别急着背口诀很多人一开始接触SQL执行顺序&#xff0c;第一反应就是去背那串口诀&#xff1a;FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。背是背下来了&#xff0c;可真到写复杂查询的时候&…

作者头像 李华
网站建设 2026/10/16 4:12:28

PYNQ开发入门:AXI_GPIO寄存器读写与Overlay加载实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 4:11:52

Fabric应用集成:fabric-gateway-java核心原理与生产实践

1. 为什么非得用 fabric-gateway-java&#xff1f;——从“手撕gRPC”到“开箱即用”的真实演进路径我第一次在某高校区块链实验室参与Fabric项目时&#xff0c;团队里三位开发者花了整整两周时间&#xff0c;硬是用原生gRPC Java SDK写完了一套链码调用逻辑。不是他们水平不够…

作者头像 李华
网站建设 2026/10/16 4:11:52

免费跑通Claude Code:三条实操路线与踩坑指南

做 AI 编程这块的&#xff0c;最近应该没人没听过Claude Code这个词。终端里的 AI 结对编程&#xff0c;一句话描述需求&#xff0c;它自己读代码、搜文件、跑测试、改 bug&#xff0c;那种“手不离键盘”的体验确实很上瘾。可真正上手时&#xff0c;第一道墙不是命令行的学习成…

作者头像 李华
网站建设 2026/10/16 4:11:33

SQL日期函数DATEDIFF详解:从边界条件到性能优化实战

DATEDIFF 应该是 SQL 里最常用也最容易被轻视的函数之一。很多开发者在项目初期用它算个日期差&#xff0c;感觉简单到不需要看文档——两个日期一减&#xff0c;天数出来了&#xff0c;完事。但等你真正处理过跨年数据、排查过慢查询、或者接过一个用了 Oracle 和 PostgreSQL …

作者头像 李华