本文约 5,695 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:小程序上线前一天,接口全报 “不在以下 request 合法域名列表”
很多第一做小程序集成的开发者会卡在很基础的地方:小程序对 wx.request 的目标域名有硬性要求——必须是 HTTPS、必须是域名而非 IP、必须备案且加入了小程序后台的服务器域名白名单;而且每月只能修改有限次数。当你把 WordPress 作为内容中台时,这意味着:域名决策必须前置,不能临上线才配;接口路径不能随便改;一切回调也必须在同一批白名单内。除了域名,另一类问题是性能与结构:直接把 /wp-json/wp/v2/posts 原样转发给小程序,content.rendered 里带着一堆 Gutenberg 注释和内联样式,既浪费流量又不好排版。
原理分析:中台职责是「收窄输出」,不是「全量转发」
WordPress REST API 默认输出很”重”:content、excerpt、guid、meta、_links 里 dozens 的关系链接,一个 20 条的列表响应轻松超过 500KB。而小程序通常只需要:标题、摘要、封面、发布时间、作者名、正文 HTML(或富文本节点数组)。
正确的做法是三层:一是内容建模(Custom Post Type 承载非文章类内容),二是字段收窄(用 register_rest_field 加自己的精简字段,或在前台用 _fields= 参数限制),三是缓存(列表级别的 transient 或 CDN)。
鉴权方面,需要写操作时(评论、收藏、投稿)有三种真实可用的选项:WP 5.6 起的应用程序密码(Application Passwords,后台用户 → 应用密码,配合 Basic 认证)、插件 JWT Authentication for WP REST API(wordPress.org 有,需在 wp-config.php 里定义 JWT_AUTH_SECRET_KEY)、或 OAuth 系列插件。只读内容则完全不需要鉴权,只需保证不把草稿或未分类内容漏出去。
| 鉴权方式 | 适用 | 注意点 |
|---|---|---|
| 应用密码 + Basic | 服务端到服务端 | 传输必须 HTTPS |
| JWT 插件 | 小程序登录后持续携带 | 需配 Rewrite 转发 Authorization 头 |
| 自建 Token 路由 | 完全可控 | 自己负责刷新与吊销 |
保姆级实操
步骤 1:先解决域名与备案。 小程序后台「开发 → 开发管理 → 服务器域名」里填写 API 域名,注意该域名必须已完成 ICP 备案且证书有效。图片、上传走的域名同样要加入 uploadFile/downloadFile 列表。
步骤 2:给小程序建轻量 REST 路由。 不要在客户端拼复杂的 _embed 和 _fields,直接服务端给一个专用端点:
add_action( 'rest_api_init', function () {
register_rest_route( 'mimangfei/v1', '/feed', [
'methods' => 'GET',
'permission_callback' => '__return_true',
'args' => [
'page' => [ 'default' => 1, 'sanitize_callback' => 'absint' ],
'per_page' => [ 'default' => 10, 'sanitize_callback' => 'absint' ],
],
'callback' => function ( WP_REST_Request $req ) {
$cache_key = 'mf_feed_' . md5( $req['page'] . '-' . $req['per_page'] );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return rest_ensure_response( $cached );
}
$q = new WP_Query( [
'post_type' => 'post',
'post_status' => 'publish',
'paged' => max( 1, $req['page'] ),
'posts_per_page' => min( 20, max( 1, $req['per_page'] ) ),
] );
$items = [];
foreach ( $q->posts as $p ) {
$items[] = [
'id' => $p->ID,
'title' => get_the_title( $p ),
'excerpt' => wp_trim_words( wp_strip_all_tags( $p->post_content ), 60 ),
'cover' => get_the_post_thumbnail_url( $p->ID, 'medium' ) ?: '',
'date' => get_the_date( 'c', $p ),
'author' => get_the_author_meta( 'display_name', $p->post_author ),
];
}
$data = [
'items' => $items,
'total_page' => $q->max_num_pages,
'total' => (int) $q->found_posts,
];
set_transient( $cache_key, $data, 5 * MINUTE_IN_SECONDS );
return rest_ensure_response( $data );
},
] );
} );
步骤 3:发布新内容时清缓存。 挂 wp_after_insert_post(WP 5.6 起可用):
add_action( 'wp_after_insert_post', function ( $post_id, $post ) {
if ( 'publish' !== $post->post_status ) {
return;
}
global $wpdb;
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mf_feed_%'" );
}, 10, 2 );
步骤 4:给 WordPress REST 补充字段。 如果沿用原生 /wp-json/wp/v2/posts,用 register_rest_field 追加自定义字段更安全(“别修改 content 本身):
add_action( 'rest_api_init', function () {
register_rest_field( 'post', 'mf_meta', [
'get_callback' => function ( $post ) {
return [
'views' => (int) get_post_meta( $post['id'], 'views', true ),
'source' => get_post_meta( $post['id'], 'source_name', true ),
'readtime' => ceil( mb_strlen( wp_strip_all_tags( $post['content']['rendered'] ) ) / 400 ),
];
},
'schema' => null,
] );
} );
步骤 5:如果需要 JWT,务必补 HTTP 头转发。 该插件要求把 Authorization 头传给 PHP,典型做法是:
location / {
try_files $uri $uri/ /index.php?$args;
# 让 PHP 能读到 Authorization 请求头(JWT 插件必需)
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
步骤 6:小程序侧收口。 封装统一的 request 层,统一处理 401(重新登录)与 429(指数退避),并给所有接口加超时:
const BASE = 'https://api.example.com/wp-json/mimangfei/v1';
function request(path, data = {}) {
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE}${path}`,
data,
timeout: 8000,
success: res => {
if (res.statusCode === 200) { resolve(res.data); }
else if (res.statusCode === 401) { wx.navigateTo({ url: '/pages/login/index' }); }
else { reject(new Error('HTTP ' + res.statusCode)); }
},
fail: reject
});
});
}
module.exports = { request };
常见踩坑
- 用 IP + 端口做 API 域名,小程序直接拒绝请求。
- 案例: 某团队在上线当天才发现服务器域名每月只能改 5 次(含次数限制),临时换 CDN 域名导致线上停更近两周。
per_page不设上限,客户端一次拉 100 条,正文全量返回导致单次响应超过 1.5MB,弱网下加载十几秒。- 直接删 transient 用
delete_transient逐个清,漏掉了永久站点晶体管(重要冒险用的是 SQL 前缀匹配)。 - JWT 插件没配
fastcgi_param,本地 nginx 能跑(Apache mod_rewrite 自动处理)线上却 403,jwtauth_no_auth_header错误。 register_rest_field的schema传 null 在某些版本会触发 “missing schema” 的_doing_it_wrong提示;生产环境建议给出完整 schema。- 把
content.rendered里的 Gutenberg 注释一起返回,小程序 rich-text 组件渲染出空行。WordPress REST API 开发基础
总结
小程序 + WordPress 的判断标准是:域名合规是否前置处理、接口输出是否足够瘦、缓存是否随发布自动失效。行动清单:确定 API 域名并入白名单 → 自建精简路由 → transient 缓存 + wp_after_insert_post 清一次 → 客户端统一错误层。真正需要做 JWT 的场景其实不多,能用应用密码解决的就别引入登录态复杂度。接口层的详细写法也可以配合 WordPress REST API 开发基础 一起落地。
