超级管理员能看到自定义菜单,普通角色登录后整组菜单消失,通常不是路由问题,而是菜单绑定的 ACL resource 没有被该角色授权。若所有角色都看不到,再检查模块和 XML 是否被加载。
menu.xml 与 acl.xml 的 ID 必须完全一致
<!-- etc/adminhtml/menu.xml -->
<add id="Vendor_Module::report"
title="Report"
module="Vendor_Module"
sortOrder="50"
parent="Magento_Backend::content"
action="vendor/report/index"
resource="Vendor_Module::report" />
<!-- etc/acl.xml -->
<resource id="Magento_Backend::admin">
<resource id="Vendor_Module::report" title="Report" sortOrder="50" />
</resource>
大小写、模块前缀或尾部单复数任一不同,都会变成两个资源。不要为了让菜单出现而临时改成 Magento_Backend::admin,这相当于扩大权限。
确认模块和合并后的 ACL
bin/magento module:status Vendor_Module
bin/magento cache:clean config
bin/magento setup:di:compile
grep -R "Vendor_Module::report" app/code vendor/*/*/etc
开发环境 XML 能加载,不代表生产编译产物一定同步。多节点部署应确认代码版本一致;只清一个节点的 config cache 可能产生时有时无的菜单。
角色保存后要重新建立后台会话
在“系统 → 权限 → 角色”中为目标角色勾选资源并保存,然后让该管理员退出重新登录。旧会话可能保留授权树。若资源树里根本没有该节点,问题仍在 acl.xml,而不是角色选择。
控制器也要使用相同授权边界
class Index extends MagentoBackendAppAction
{
public const ADMIN_RESOURCE = 'Vendor_Module::report';
public function execute()
{
return $this->resultFactory->create(
MagentoFrameworkControllerResultFactory::TYPE_PAGE
);
}
}
菜单显示但点击 Access Denied,常见原因是 Controller 的 ADMIN_RESOURCE 与菜单 resource 不一致。相反,控制器放宽而菜单收紧也会留下可直接访问的路由。
验证时至少使用两个账号:一个拥有该资源,一个明确不拥有。前者应看到并能访问,后者菜单不可见且直接输入 URL 也应被拒绝。只有界面和服务端授权同时成立,ACL 才算正确。

