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' =&gt; 'GET',
'callback' =&gt; [$this, 'get_note'],
'permission_callback' =&gt; [$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-&gt;get_header('x_wp_nonce');
if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_Error('mwt_bad_nonce', 'Invalid nonce', ['status' =&gt; 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' =&gt; true,
'saved' =&gt; true,
'length' =&gt; mb_strlen($note),
]);
}
public function get_note(\WP_REST_Request $request) {
$note = (string) get_option(self::OPTION_NOTE, '');
return rest_ensure_response([
'ok' =&gt; true,
'note' =&gt; $note,
'length' =&gt; 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'));
?&gt;
&lt;div class="wrap"&gt;
&lt;h1&gt;MWT REST Demo&lt;/h1&gt;
&lt;p&gt;API Base: <code></code>&lt;/p&gt;
&lt;h2&gt;1) Ping(GET)&lt;/h2&gt;
&lt;button class="button" id="mwt_ping_btn"&gt;Ping&lt;/button&gt;
&lt;pre id="mwt_ping_out" style="background:#111;color:#eee;padding:12px;min-height:80px;"&gt;&lt;/pre&gt;
&lt;h2&gt;2) Save Note(POST)&lt;/h2&gt;
&lt;textarea id="mwt_note" class="large-text" rows="5" placeholder="Write something..."&gt;&lt;/textarea&gt;
&lt;p&gt;
&lt;button class="button button-primary" id="mwt_save_btn"&gt;Save&lt;/button&gt;
&lt;button class="button" id="mwt_load_btn"&gt;Load&lt;/button&gt;
&lt;/p&gt;
&lt;pre id="mwt_note_out" style="background:#111;color:#eee;padding:12px;min-height:80px;"&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;script&gt;
(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 () =&gt; {
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 () =&gt; {
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 () =&gt; {
noteOut.textContent = 'Loading...';
const r = await req('/note', {method: 'GET'});
noteOut.textContent = JSON.stringify(r, null, 2);
if (r.json &amp;&amp; r.json.note !== undefined) noteEl.value = r.json.note;
});
})();
&lt;/script&gt;</code></pre>
评论 0