搜索全站
建站 15 分钟阅读

微信小程序 + WordPress 内容中台:REST API 输出架构与接口设计

内容在 WordPress,展示在小程序,中间那层接口怎么设计才稳?本文讲鉴权方式选择、字段精简输出、为小程序定制 REST 路由,以及必须提前准备的域名与合规前置条件。

U
UFO_PF 2017-07-31 · 阅读 15 分钟
微信 建站
阅读提示
本文约 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 };

常见踩坑

  1. 用 IP + 端口做 API 域名,小程序直接拒绝请求。
  2. 案例: 某团队在上线当天才发现服务器域名每月只能改 5 次(含次数限制),临时换 CDN 域名导致线上停更近两周。
  3. per_page 不设上限,客户端一次拉 100 条,正文全量返回导致单次响应超过 1.5MB,弱网下加载十几秒。
  4. 直接删 transient 用 delete_transient 逐个清,漏掉了永久站点晶体管(重要冒险用的是 SQL 前缀匹配)。
  5. JWT 插件没配 fastcgi_param,本地 nginx 能跑(Apache mod_rewrite 自动处理)线上却 403,jwtauth_no_auth_header 错误。
  6. register_rest_field 的 schema 传 null 在某些版本会触发 “missing schema” 的 _doing_it_wrong 提示;生产环境建议给出完整 schema。
  7. 把 content.rendered 里的 Gutenberg 注释一起返回,小程序 rich-text 组件渲染出空行。WordPress REST API 开发基础

总结

小程序 + WordPress 的判断标准是:域名合规是否前置处理、接口输出是否足够瘦、缓存是否随发布自动失效。行动清单:确定 API 域名并入白名单 → 自建精简路由 → transient 缓存 + wp_after_insert_post 清一次 → 客户端统一错误层。真正需要做 JWT 的场景其实不多,能用应用密码解决的就别引入登录态复杂度。接口层的详细写法也可以配合 WordPress REST API 开发基础 一起落地。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。