自定义前台 Controller 访问 404,我一般不先清十遍缓存,而是把 URL 拆成 frontName、controller、action 三段逐个对。

先用最短路径排除目录层级问题

etc/frontend/routes.xml:

<router id="standard">
    <route id="vendor_module" frontName="tools">
        <module name="Vendor_Module"/>
    </route>
</router>

访问 /tools/ping/index 时,类应是:

VendorModuleControllerPingIndex

文件为:

app/code/Vendor/Module/Controller/Ping/Index.php

Linux 文件系统区分大小写。目录写成 controller/ping,本机 macOS 可能没事,上服务器就会 404。

action类要满足当前版本的接口要求

class Index implements MagentoFrameworkAppActionHttpGetActionInterface
{
    public function __construct(
        private MagentoFrameworkControllerResultJsonFactory $jsonFactory
    ) {}

    public function execute()
    {
        return $this->jsonFactory->create()->setData(['ok' => true]);
    }
}

如果这是 POST 接口,则实现 HttpPostActionInterface,并处理 Form Key 或明确实现合适的 CSRF 逻辑。不要为了让请求通过就全局关闭 CSRF。

再运行:

php bin/magento module:status Vendor_Module
php bin/magento cache:clean config

先让最短的 JSON Action 返回,再逐步加依赖和业务逻辑。这样能清楚区分“路由没命中”和“Controller 构造失败后被错误页接管”。