本文约 5,797 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:三个后台,三份内容,永远不一致
最常见的多端困局是这样的:公司有一个 WordPress 官网,后来又做了小程序,运营还要在微信公众号里发图文。于是同一篇新品介绍要在三个后台各发一遍,改一个价格要改三处。三个月后,PC 站上写”库存充足”,小程序上还是”预售中”,用户在小程序下单后发现发货时间和官网说的不一样,投诉就来了。
内容的”多处维护”是运营成本黑洞,也是数据不一致的根源。正确架构只有一个原则:WordPress 作为唯一的内容源(Single Source of Truth),其他端全部通过接口读取。WordPress 自 4.7 起内置的 REST API 就是为这件事准备的,它把文章、分类、媒体、用户都暴露成标准的 JSON 接口,任何能发 HTTP 请求的客户端都能消费。本文讲清这套架构怎么落地,以及实操中会遇到的字段不够、鉴权、缓存三个具体问题。
原理分析:REST API 是”读”,同步的本质是”拉取”而非”推送”
REST API 是 WordPress 核心自带的,无需插件即可通过 /wp-json/wp/v2/posts 拿到文章列表。它默认是只读的(写入需要鉴权),这对多端同步反而是好事:内容源只有一处,其他端只是不同形式的”视图”,天然不会产生冲突。
要理解的关键点是数据流向:小程序/App 主动拉取 WordPress 的数据,而不是 WordPress 推给它们。这意味着两件事:一是 WordPress 端不需要知道有哪些客户端,新增一个端不需要改服务端;二是各端要自己做缓存,不能每次打开都去打接口。
| 方案 | 适用场景 | 一致性 | 维护成本 |
|---|---|---|---|
| 各端各自维护 | 内容完全不重叠 | 差 | 高 |
| REST API 拉取 | 官网 + 小程序 + App | 好 | 低 |
| 小程序 webview 套网页 | 只需展示、无原生能力 | 最好 | 最低 |
| 数据库直连 | 高定制化后端 | 好 | 高、风险大 |
选择时先看需求:如果小程序只是”把官网内容搬过去”,用 web-view 嵌 H5 页面是最省事的(但微信对 web-view 有业务域名配置要求,且体验一般);如果需要原生列表、下拉刷新、分享卡片,就走 REST API。数据库直连是最不推荐的——跨端直连数据库会把安全边界彻底打散。
保姆级实操
- 确认接口可访问并测试基础查询
浏览器打开 https://你的域名/wp-json/wp/v2/posts?per_page=5,能返回 JSON 就说明接口可用。常用参数:per_page(每页条数,最大 100)、page、_fields(只返回指定字段,减少传输量)、search、categories、orderby=date。X-WP-TotalPages 响应头里有总页数。
- 扩展接口字段:把特色图、摘要、阅读量一起返回
默认接口不含特色图片 URL 和自定义字段,逐个再发请求去取会让小程序端请求数翻倍。用 register_rest_field() 一次性附加:
<?php
add_action( 'rest_api_init', function () {
register_rest_field( 'post', 'cover', array(
'get_callback' => function ( $post ) {
$id = get_post_thumbnail_id( $post['id'] );
if ( ! $id ) {
return '';
}
// 返回中等尺寸,避免小程序拉原图浪费流量
$src = wp_get_attachment_image_src( $id, 'medium_large' );
return $src ? $src[0] : '';
},
'schema' => array( 'type' => 'string', 'context' => array( 'view' ) ),
) );
register_rest_field( 'post', 'cat_name', array(
'get_callback' => function ( $post ) {
$terms = get_the_terms( $post['id'], 'category' );
if ( empty( $terms ) || is_wp_error( $terms ) ) {
return '';
}
return $terms[0]->name;
},
'schema' => array( 'type' => 'string', 'context' => array( 'view' ) ),
) );
} );
// 给小程序用的精简列表接口:只返回必要字段,减少解析成本
add_action( 'rest_api_init', function () {
register_rest_route( 'app/v1', '/feed', array(
'methods' => 'GET',
'permission_callback' => '__return_true',
'args' => array(
'per_page' => array( 'default' => 10, 'sanitize_callback' => 'absint' ),
'cat' => array( 'default' => 0, 'sanitize_callback' => 'absint' ),
),
'callback' => function ( WP_REST_Request $req ) {
$q = new WP_Query( array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => min( (int) $req['per_page'], 30 ),
'cat' => (int) $req['cat'],
'no_found_rows' => true,
) );
$data = array();
foreach ( $q->posts as $p ) {
$data[] = array(
'id' => $p->ID,
'title' => html_entity_decode( get_the_title( $p ), ENT_QUOTES, 'UTF-8' ),
'excerpt' => wp_strip_all_tags( get_the_excerpt( $p ) ),
'cover' => get_the_post_thumbnail_url( $p, 'medium_large' ) ?: '',
'date' => get_the_date( 'Y-m-d', $p ),
'link' => get_permalink( $p ),
);
}
return rest_ensure_response( $data );
},
) );
} );
- 在小程序端请求并缓存
小程序用 wx.request 拉取,域名必须是已备案且在小程序后台加入 request 合法域名的 HTTPS 地址。列表首次请求后写入本地缓存,下拉刷新时再拉一次:
// pages/index/index.js
const API = 'https://mimangfei.com/wp-json/app/v1/feed';
const CACHE_KEY = 'feed_cache';
Page({
data: { list: [], loading: true },
onLoad() {
const cached = wx.getStorageSync(CACHE_KEY);
if (cached && Date.now() - cached.t < 10 * 60 * 1000) {
this.setData({ list: cached.data, loading: false }); // 10 分钟内用缓存
}
this.fetch();
},
fetch() {
wx.request({
url: API,
data: { per_page: 10 },
success: (res) => {
const list = Array.isArray(res.data) ? res.data : [];
this.setData({ list, loading: false });
wx.setStorageSync(CACHE_KEY, { t: Date.now(), data: list });
},
fail: () => this.setData({ loading: false })
});
},
onPullDownRefresh() {
this.fetch();
wx.stopPullDownRefresh();
}
});
- 移动端:不要做独立的移动站
现在很少需要单独建 m.xxx.com。响应式主题 + 同一个 URL 是最省事也最利于收录的做法。如果已有独立移动站,正确做法是让它 301 到 PC 站的对应 URL,而不是两边各发一份内容——这一点和 网站改版时的 301 重定向总表制作 里的思路一致。
- 处理缓存与更新延迟
WordPress 端开了页面缓存后,接口也可能被缓存,导致小程序端看到旧内容。把 /wp-json/ 开头的请求排除出缓存规则(Nginx 里加一条 location,或在缓存插件里排除该路径)。同时给接口响应加 Cache-Control,并在文章更新时清理对应缓存。
- 加一层鉴权与限流
公开内容用只读接口没问题,但如果要通过接口写入(比如小程序提交表单回写到 WordPress),就必须用 Application Passwords(WordPress 后台「用户 → 个人资料」里生成)或 JWT 插件做鉴权,并且用 permission_callback 严格限制——绝不能把写入接口设成 __return_true。
常见踩坑
- 小程序请求的域名未加入合法域名列表:开发工具里能请求(勾了”不校验合法域名”),真机一律失败。
- 案例:某站点把
/wp-json/也纳入了整页缓存,小程序端长期显示三天前的内容,运营以为同步失效,反复手动刷新无效。排查后发现是缓存插件把接口响应也缓存了,加入排除规则后立刻恢复。 - 接口一次返回 100 条带全文的数据:单次响应几 MB,小程序端解析卡顿。用
_fields限制字段,或自建精简接口。 - 写入接口的
permission_callback写成'__return_true':等于把后台写权限对全网开放,任何人都可发文章。 - 特色图片 URL 是 http 开头:小程序要求 HTTPS(
https强制),图片全部加载失败。 - 正文里的相对链接在小程序中打不开:接口返回
post_content需把站内相对链接补成绝对地址,或用 web-view 打开。 - 每打开一次页面就请求一次接口:没做本地缓存,接口被打爆,主机 CPU 飙升。
总结
多端同步的判断标准只有一条:改一处,三端同时生效;做不到就是架构错了。以 WordPress 为唯一内容源、其他端通过 REST API 拉取,是成本最低、一致性最好的方案。落地时三个必做项:用 register_rest_field() 或自建路由精简返回字段、把 /wp-json/ 排除出页面缓存、写入接口必须鉴权。内容在多端展现的形态差异可以参考 网站移动端适配的验收标准,而接口返回的内容质量本身仍取决于 网站内容填充的效率方法 打下的底子。
