`admin_init` 里注册 REST 路由:为什么你的自定义 API 返回 404,以及 `rest_api_init` 的"迟到"真相

插件开发 33 浏览 0 回复 返回上级

上周给插件补一个后台用的 AJAX 替代方案,顺手把 `register_rest_route` 塞进了 `admin_init` 里——毕竟只在后台用嘛。结果前端 `wp.apiFetch` 一调,稳如老狗地返回 404。Postman 直接访问也是 404。查了一天,最后发现是钩子选错了,但背后还有一层更隐蔽的坑。

第一个误区:REST 路由不是"后台专属"

WordPress 的 REST API 初始化发生在 `rest_api_init`,这个钩子在 `init` 之后、但和 `admin_init` 没有必然的先后包含关系。更关键的是,REST 请求走的是 `wp-json/` 端点,不经过 `wp-admin/admin-ajax.php`,所以 `admin_init` 根本不会触发——你的路由注册代码等于写在了一个永远不会执行的 if 分支里。

错误示范(我当时的代码):

// ❌ 错:admin_init 里注册 REST 路由
add_action( 'admin_init', function() {
    register_rest_route( 'myplugin/v1', '/sync/', [
        'methods'  => 'POST',
        'callback' => [ $this, 'handle_sync' ],
        'permission_callback' => '__return_true', // 测试时偷懒
    ] );
} );

修正后:

// ✅ 对:rest_api_init 里注册
add_action( 'rest_api_init', function() {
    register_rest_route( 'myplugin/v1', '/sync/', [
        'methods'             => 'POST',
        'callback'            => [ $this, 'handle_sync' ],
        'permission_callback' => [ $this, 'check_sync_perm' ],
    ] );
} );

第二个坑:权限回调的"静默失败"

即使路由注册对了,如果 `permission_callback` 返回 false 或抛异常,REST API 会返回 403 而不是 404。但我遇到的情况是:权限回调里用了 `current_user_can( 'manage_options' )`,而测试时用的 Postman 没带 nonce cookie,结果返回 401。这仨状态码(404/403/401)在排查时很容易互相误导。

我的调试技巧:先临时把 `permission_callback` 换成 `__return_true`,确认路由本身存在;再逐层加权限,区分是"路由没挂上"还是"权限拦住了"。

第三个暗雷:命名空间前缀的斜杠强迫症

`register_rest_route` 的第一个参数是 namespace,第二个是 route。namespace 不要带前导或尾随斜杠,route 建议也不要。但最坑的是:如果你 namespace 写了 `myplugin/v1/`(带尾随斜杠),WordPress 不会报错,而是生成 `wp-json/myplugin/v1//sync/` 这种双斜杠 URL,然后匹配时 404。这个错误在代码里一眼扫过去很难发现。

我现在的习惯是注册完立刻打一行调试:

add_action( 'rest_api_init', function() {
    register_rest_route( ... );
    // 开发时确认路由真的挂上了
    error_log( 'Registered routes: ' . print_r( rest_get_server()->get_routes(), true ) );
} );

延伸:如果一定要在 admin 页面里用 REST

有些场景确实只想在后台暴露接口(比如和 Gutenberg 的 `edit-post` 包交互)。正确做法还是走 `rest_api_init` 全局注册,但在 `permission_callback` 里判断 `is_user_logged_in()` 和具体 capability,而不是试图把路由"藏"进 admin 上下文里。REST API 的设计哲学就是全局端点 + 细粒度权限,不是 admin-ajax.php 的替代品。

最后提一个排查顺序,省得走我老路:

  1. `rest_api_init` 钩子是否挂载?(用 `has_action` 检查)
  2. namespace + route 拼出来是什么 URL?(用 `get_rest_url()` 生成完整地址核对)
  3. 权限回调返回值是什么?(临时替换为 `__return_true` 隔离)
  4. 有没有缓存插件或 object cache 把路由表冻住了?(`rest_get_server()->get_routes()` 实时看)

你们有在 `rest_api_init` 之外的地方注册过路由吗?或者遇到过路由表被缓存插件"吃掉"的情况?

评论0
回复 · 0
还没有回复
微信客服 微信客服