
「数据权限」在 jeecgboot 里是一个让人又爱又恨的功能。爱的是它确实能解决行级数据隔离不用在每个查询里手动拼条件恨的是官网文档把配置路径写得清清楚楚却把真正决定成败的细节全部留给你自己踩。我之前在某外包项目里接了一个需求销售只能看自己创建的客户区域经理能看本区域的客户管理员看全部。当时按官网步骤配了数据权限规则、在查询接口加了注解结果普通销售登录后依然能看到全量数据。这篇文章就把那次排查过程中涨的知识全部整理出来覆盖系统规则、自定义规则、缓存、SQL 日志等常见问题给正在被 jeecgboot 数据权限折磨的同学一份可以按图索骥的避坑手册。1. 为什么照着官网配完接口还是不按规则过滤1.1 一个真实需求引发的连锁排查那个需求本身很简单订单列表要按登录人过滤销售只能看到create_by等于自己的订单。当时我第一反应是去系统管理 → 菜单管理里找到订单菜单新增一条数据权限规则字段名填create_by规则值选「读取当前登录人的用户名」然后在订单列表接口上加PermissionData注解。看起来和官网示例一模一样结果普通销售登录后订单数据一条不少规则完全没生效。排查的第一步是怀疑注解没生效。我加日志、检查切面注解还在方法也被 Spring 代理了。第二步怀疑规则 SQL 有问题反复对照官网示例也没看出区别。最后偶然发现前端页面调用的列表接口挂在「订单管理」菜单下而我配置规则时选中的是「历史订单」菜单。数据权限规则是挂在菜单上的切面在执行时按当前请求归属的菜单权限去找规则菜单对不上规则自然进不到最终 SQL 里。这个坑非常隐蔽因为接口地址、页面功能都没变只有菜单配置变了或者规则挂在父级目录上就会出现「配了等于没配」的现象。1.2 数据权限生效的三个必要条件官网没串起来讲照着官网操作不生效根本原因是文档把三个必要条件分散在不同章节没有一篇内容明确告诉你它们必须同时满足。我在项目里反复验证后总结出这三个条件菜单上必须配置数据权限规则且前端页面请求的接口必须落在该菜单的权限范围内。后端查询方法上必须加数据权限注解而且要加到 Spring 代理能拦截到的方法上。查询 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 ResultIPageOrder list(PageOrder page, RequestParam MapString, 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 时如果用户姓名或部门名称里包含单引号比如OBrien拼出来的 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 这一步开始。