后台菜单注册后点不开?多半是权限节点和路由参数没对齐
做插件时最常遇到的怪事:后台菜单明明加上了,点进去却提示“无权访问”或者直接 404。排查半天发现不是权限配置错了,而是菜单的 URL 参数和路由注册时的参数差了那么一个斜杠或变量名。这篇把菜单注册、权限节点声明、路由参数三者之间的对应关系捋一遍,顺便给出一套自查顺序。
一、菜单注册的两种方式与常见误区
插件后台菜单一般在 plugin->xxx->hook->admin_menu.php 里通过 hook_admin_menu_xxx() 函数输出。常见写法有两种:一种是直接输出 HTML 的 标签,另一种是调用框架提供的菜单数组。很多新手(包括我早期)直接手写 href="index.php?plugin-mypanel&action=setting",看起来没问题,但路由端注册时用的是 param 方式,比如 plugin-mypanel&action=setting¶m=1,少了一个参数就导致页面空白。
建议统一采用框架推荐的菜单数组方式,在 admin_menu.php 里 return 一个多维数组,键名对应路由参数,这样能避免手写 URL 时漏参数。另外注意菜单的 active 状态判断,如果判断条件写的是 $_GET['action'] == 'setting',但实际路由里 action 值带上了前缀(比如 mypanel_setting),那菜单高亮就永远不会触发。
二、权限节点不是“声明了就能用”
权限节点的坑在于:后台菜单里写了 data-permission="mypanel:setting",插件安装时也通过 db::insert() 插入了权限记录,但用户组里没勾选该权限,于是管理员自己都打不开。更隐蔽的问题是——权限节点的键名必须严格对应路由里的 action 值。比如路由注册是 ?action=setting,权限节点就应该是 mypanel:setting,如果写成 mypanel:config,那无论怎么授权都无效。
另外,某些框架版本中权限节点需要同时存在于 bbs_group 表的 permission 字段里(JSON 格式),而不是只靠安装脚本插入一次。如果用户是在插件安装前就存在的旧账号,权限缓存可能还停留在旧数据,需要强制刷新或者让用户重新登录一次。遇到“权限已给但无效”时,先查数据库里该用户组的 permission 字段是否真的包含了新节点,别急着改代码。
三、路由参数细节:别忽略默认值和类型
路由注册通常在 plugin->xxx->hook->route.php 里用 route::register() 或者直接通过 index.inc.php 分发。最容易翻车的三个细节:
1. 参数默认值。如果菜单链接是 ?action=setting,但路由里写的是 function ($action = ''),当 action 为空时走的是默认分支,可能渲染了错误模板。建议路由函数里第一步就校验 action 是否在白名单内,不在则直接抛出 403。
2. 参数类型。比如 id 参数从 URL 里拿到的是字符串,如果直接拿去做 === 比较,数字 1 和字符串 '1' 永远不相等。用 intval() 或强转后再判断。
3. 菜单 URL 中的 & 转义。在 HTML 属性里写 href="index.php?plugin-mypanel&action=setting" 是错的,必须写成 &action=setting。否则浏览器解析时会把 &action 当作 &action 的实体,导致实际收到的参数名变成 amp;action,路由自然匹配不上。
四、自查顺序:从菜单到路由的排查清单
遇到后台菜单打不开,按下面顺序查,基本十分钟内定位:
1. 打开浏览器开发者工具,看 Network 里点击菜单时实际请求的 URL 是什么,和路由注册的规则是否一致(包括参数名、顺序、是否有额外 &)。
2. 在路由分发入口文件(通常是 index.inc.php)临时加一行 var_dump($_GET);,看框架实际收到了哪些参数。
3. 检查权限节点:去数据库 bbs_group 表里找到当前用户组,看 permission 字段里是否包含形如 "mypanel:setting":1 的记录。没有就手动补上,并让该用户重新登录。
4. 确认菜单文件被正确加载。有些插件把菜单写在 hook 目录下但文件名拼错了,或者没有在 plugin->xxx->conf.php 里声明 hook 点,导致菜单根本没注册成功。
5. 最后检查模板缓存。后台页面经常有模板缓存,修改了菜单或路由后没清缓存,看起来就像没改一样。去 tmp 目录删掉对应缓存文件再刷新。
五、一个实际案例:菜单显示了但子页面全部 404
之前做一个内容管理插件,主菜单能打开,但点“添加文章”子菜单时全部 404。排查发现子菜单 URL 写的是 ?action=add&type=article,而路由里只注册了 ?action=add,多出来的 type 参数并没有被路由接受——框架默认只解析已声明的参数,多余的会被丢弃。解决办法是在路由函数里用 request::get('type') 显式获取,而不是依赖路由参数绑定。这个坑很容易被忽略,因为主菜单没带额外参数所以一切正常,一旦子菜单加了筛选条件就炸。
另外,如果插件同时支持前台和后台路由,注意后台路由的前缀。有的框架要求后台路由必须带 admin- 前缀,比如 admin-mypanel&action=add,前台才是 mypanel&action=list。混用的话,后台菜单链接指向了前台路由,权限系统直接拦截。
写到最后总结一句:菜单、权限、路由三者是串联关系,任何一环的参数名不一致,表现都是“页面打不开”。先核对 URL 字符串,再核对权限节点,最后核对路由函数签名,按这个顺序排查效率最高。