插件菜单注册时漏了一个`auth_rule`的`type`字段,后台直接给我整出两套平行宇宙菜单
上周给后台加一个新插件,菜单显示正常、路由能访问、按钮也在,但权限管理里死活找不到对应的节点。超管账号进去一切OK,普通管理员进去404。排查了俩小时,最后发现是auth_rule表里的type字段填岔了。
简单说下背景。我们这边插件注册菜单走的是统一钩子,在PluginService里组装好数组,往系统菜单表插一条,同时往权限规则表插一条。菜单表管展示,权限表管校验,两条数据用name字段(也就是路由标识)关联。听起来很清晰对吧?坑就埋在这"同时"俩字里。
菜单表的type我填的1代表插件菜单,权限表的type我顺手也填了1。但我们系统里权限节点的type枚举是:0目录、1菜单、2按钮/接口。插件菜单在菜单表是1没错,可到了权限表,这个节点本质上是"需要校验的页面入口",应该算1菜单节点——这倒是对的。问题出在子节点上。
插件里有个"批量导出"的功能,我在权限表给它单独注册了一个auth_rule记录,想着用来控制按钮显隐。这个节点没有对应菜单,纯纯的权限点,type应该填2。我当时脑抽填了1,结果权限树渲染时,这个节点被当成"隐藏菜单"处理了,父节点找不着它,它自己也挂不到任何菜单下面。更绝的是,后台的权限分配接口查节点时用了where type != 2来过滤纯按钮,导致这个节点在角色配置页完全消失;但前端路由守卫校验时走的是另一套查询,没过滤这个type,所以超管能访问——因为超管绕过了节点校验直接放行。
普通管理员呢?角色绑定的时候没这个节点,校验自然失败,跳转404。同一套数据,两套查询条件,呈现出"超管有、普通管理员无"的诡异现象,跟平行宇宙似的。
修完之后我复盘了下,这类问题其实有迹可循。插件菜单注册最好拆成三步走,别图省事一条SQL搞定:
第一步,菜单表只管展示层级。 pid、title、icon、href这些,type按菜单系统的规矩来,插件的一般固定1。
第二步,权限节点单独声明,别复用菜单的type语义。 哪怕名字一样,这俩字段的枚举值含义可能完全不同。我建议在插件配置里写死一个auth_nodes数组,每个节点明确标注type、name、title,注册时循环插入,而不是动态从菜单数组推导。
第三步,注册完主动校验一致性。 我写了个后置钩子,插件启用后自动检查:菜单表有的路由,权限表必须有对应记录;权限表type=1的节点,菜单表必须有展示入口;type=2的节点,菜单表必须没有同名记录。三条规则任一失败,日志报警,插件标黄提示"权限配置异常"。
还有个细节:权限节点的name字段建议带插件标识前缀,比如plugin.order/export。我们早期有过两个插件都注册了user/list节点,结果权限表唯一索引炸了,后启用的插件直接覆盖前者,前一个插件的权限控制就废了。现在强制要求plugin_{code}/开头,数据库加联合唯一索引(name, type),算是把命名冲突也堵上了。
最后吐槽一句,这种字段语义漂移的问题,文档里往往不会写。你看菜单表的type和权限表的type,名字一样、值范围也差不多,谁想得到底层逻辑完全是两回事。下次再遇到"有的账号行、有的账号不行"的bug,我第一反应就是查这种"同名不同命"的字段。