WP 插件开发进阶:用 REST API 做一个“可前后端分离”的接口(含示例代码)

WP 插件开发进阶:用 REST API 做一个“可前后端分离”的接口(含示例代码)

很多插件一开始用 admin-ajax.php 也能跑,但当你想做更现代的交互(例如:前台面板、React/Vue、小程序、外部系统同步),REST API 会更清晰:路由明确、返回 JSON 统一、权限与鉴权可控、调试也更舒服。

这篇文章写一个“最小可用 REST 插件”:提供两个接口——

  • GET /wp-json/mwt/v1/ping:健康检查
  • POST /wp-json/mwt/v1/note:保存一条文本(写入 option) 并演示:权限控制、nonce 鉴权、参数校验、返回规范。


1)插件文件结构

mwt-rest-demo/
  mwt-rest-demo.php


2)完整可运行代码(复制即用)

新建:wp-content/plugins/mwt-rest-demo/mwt-rest-demo.php

```php 'GET', 'callback' => [$this, 'ping'], // 公共接口也建议显式写 permission_callback 'permission_callback' => '__return_true', ]);

    register_rest_route('mwt/v1', '/note', [
        'methods'  => 'POST',
        'callback' => [$this, 'save_note'],
        // 写操作必须做权限控制
        'permission_callback' => [$this, 'can_manage'],
        'args' => [
            'note' => [
                'required' => true,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_textarea_field',
                'validate_callback' => function($param) {
                    return is_string($param) && mb_strlen($param) <= 5000;
                }
            ]
        ],
    ]);

register_rest_route('mwt/v1', '/note', [
    'methods'  => 'GET',
    'callback' => [$this, 'get_note'],
    'permission_callback' => [$this, 'can_manage'],
]);

}

public function can_manage(\WP_REST_Request $request) { // ✅ 1) 仅允许后台管理员(可按需求换成自定义 capability) if (!current_user_can('manage_options')) { return new \WP_Error('mwt_forbidden', 'Forbidden', ['status' => 403]); }

// ✅ 2) 校验 REST nonce(前端 fetch 带 X-WP-Nonce)
// 注意:在后台环境可省略,但为了演示,我们强制要求
$nonce = $request->get_header('x_wp_nonce');
if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
    return new \WP_Error('mwt_bad_nonce', 'Invalid nonce', ['status' => 401]);
}

return true;

}

public function ping(\WP_REST_Request $request) { return rest_ensure_response([ 'ok' => true, 'time' => current_time('mysql'), 'site' => home_url(), ]); }

public function save_note(\WP_REST_Request $request) { $note = (string) $request->get_param('note');

update_option(self::OPTION_NOTE, $note, false);

return rest_ensure_response([
    'ok' => true,
    'saved' => true,
    'length' => mb_strlen($note),
]);

}

public function get_note(\WP_REST_Request $request) { $note = (string) get_option(self::OPTION_NOTE, '');

return rest_ensure_response([
    'ok' => true,
    'note' => $note,
    'length' => mb_strlen($note),
]);

}

public function register_menu() { add_options_page( 'MWT REST Demo', 'MWT REST Demo', 'manage_options', 'mwt-rest-demo', [$this, 'render_admin_page'] ); }

public function render_admin_page() { if (!current_user_can('manage_options')) return;

// 生成 REST nonce,前端 fetch 必须带这个
$rest_nonce = wp_create_nonce('wp_rest');
$api_base = esc_url_raw(rest_url('mwt/v1'));

?>
<div class="wrap">
    <h1>MWT REST Demo</h1>

    <p>API Base: <code></code></p>

    <h2>1) Ping(GET)</h2>
    <button class="button" id="mwt_ping_btn">Ping</button>
    <pre id="mwt_ping_out" style="background:#111;color:#eee;padding:12px;min-height:80px;"></pre>

    <h2>2) Save Note(POST)</h2>
    <textarea id="mwt_note" class="large-text" rows="5" placeholder="Write something..."></textarea>
    <p>
        <button class="button button-primary" id="mwt_save_btn">Save</button>
        <button class="button" id="mwt_load_btn">Load</button>
    </p>
    <pre id="mwt_note_out" style="background:#111;color:#eee;padding:12px;min-height:80px;"></pre>
</div>

<script>
(function(){
    const base = ;
    const nonce = ;

    const pingBtn = document.getElementById('mwt_ping_btn');
    const pingOut = document.getElementById('mwt_ping_out');

    const noteEl = document.getElementById('mwt_note');
    const saveBtn = document.getElementById('mwt_save_btn');
    const loadBtn = document.getElementById('mwt_load_btn');
    const noteOut = document.getElementById('mwt_note_out');

    async function req(path, options = {}) {
        const res = await fetch(base + path, {
            ...options,
            headers: {
                'Content-Type': 'application/json',
                'X-WP-Nonce': nonce,
                ...(options.headers || {})
            },
            credentials: 'same-origin'
        });

        const text = await res.text();
        let json;
        try { json = JSON.parse(text); } catch(e) { json = {raw: text}; }

        return {status: res.status, json};
    }

    pingBtn.addEventListener('click', async () => {
        pingOut.textContent = 'Loading...';
        const r = await req('/ping', {method: 'GET', headers: {'X-WP-Nonce': nonce}});
        pingOut.textContent = JSON.stringify(r, null, 2);
    });

    saveBtn.addEventListener('click', async () => {
        noteOut.textContent = 'Saving...';
        const note = noteEl.value || '';
        const r = await req('/note', {method: 'POST', body: JSON.stringify({note})});
        noteOut.textContent = JSON.stringify(r, null, 2);
    });

    loadBtn.addEventListener('click', async () => {
        noteOut.textContent = 'Loading...';
        const r = await req('/note', {method: 'GET'});
        noteOut.textContent = JSON.stringify(r, null, 2);
        if (r.json && r.json.note !== undefined) noteEl.value = r.json.note;
    });
})();
&amp;lt;/script&amp;gt;</code></pre>

评论 0