搜了一圈「WordPress 自动推送百度」,能用的插件要么几年没更新,要么设置页只有孤零零一个输入框。我想要的不止这些:编辑文章时决定这篇推不推、列表里一眼看到哪篇推成功、有个地方随时换 token。
那就自己写。代码放进 wp-content/mu-plugins/ —— 这个目录下的文件自动加载、后台停不掉,适合放这种基础设施级别的功能。
整件事拆开就是四块,各自挂在独立的钩子上:

写完之后功能全通。我在后台发了一篇测试文,点发布,日志里出现了推送成功的记录,列表页状态列也亮了。收工。
设置页保存了,但读出来还是旧值
问题出在设置页。我改了个 token,点保存,页面刷新回来 —— 显示的居然还是旧值。
第一反应是保存失败了。去数据库翻 wp_options,zz_baidu_push 这条记录明明写着新 token:
mysql> select option_value from wp_options where option_name='zz_baidu_push';
{"token":"新token","site":"www.example.com","enabled":"1"}
写进去了,但读出来是旧的。这就有意思了 —— 写成功、读失败,说明问题在读取的那一层。
回头看我读配置的函数:
function zz_baidu_cfg( $key ) {
static $cache = null;
if ( null === $cache ) {
$cache = get_option( ZZ_BAIDU_OPT, array() );
}
// ... 从 $cache 里取值
}
static $cache。
我加它,是想少查几次数据库。当时觉得挺合理 —— 一次请求里配置又不会变。
但一次请求里,配置是会变的。
用户点「保存更改」,WordPress 走 options.php → update_option() 把新值写进数据库 → 然后 wp_redirect() 跳回设置页。正常情况下这是两次请求,第二次请求里 static 重新初始化,读到新值。
可我调试时是在同一个 PHP 进程里连写带读的 —— static $cache 早在第一次读的时候就锁死了旧值,后面 update_option 写得再对,我读到的都是那份缓存。
更麻烦的是:如果那次 302 跳转因为任何原因失败(比如前面已经有输出、headers 发不出去),用户就会留在同一次请求里看页面 —— 看到的就是旧 token,以为没保存成功。

修法:把这个缓存删掉
function zz_baidu_cfg( $key ) {
$o = get_option( ZZ_BAIDU_OPT, array() );
if ( ! is_array( $o ) ) {
$o = array();
}
// ... 直接从 $o 取值
}
删掉 static 就够了。
有人担心性能 —— 每次调用都查一次数据库?其实 get_option() 本身就走 WordPress 的对象缓存,同一次请求里重复调用读的是内存。而且 update_option() 会自动失效这层缓存,它永远不会给你脏数据。
我这个手写的 static 缓存,恰好把这层保护绕过去了。
顺手说个别的坑,跟缓存不是一回事,但同在配置读取里:配置来源最好做成两层回落 —— 数据库 option 优先,文件里的 define() 常量兜底。这样已部署的站点在用户第一次打开设置页之前功能不会中断,填过之后就以 option 为准。
还有两个坑
第一个:不同钩子传的参数类型不一样。
我原本只挂了 publish_post,后来发现从草稿点「发布」根本不触发它 —— 走的是 draft_to_publish。补上之后,日志里蹦出警告:
PHP Warning: Object of class WP_Post could not be converted to int
因为 publish_post 传的是文章 ID(整数),而 draft_to_publish 传的是 WP_Post 对象。同一个回调接两种参数,得在开头做个规范化:
if ( $post_id instanceof WP_Post ) {
$post_id = (int) $post_id->ID;
}

第二个:我的「跳过」提示,把真实的成功结果盖掉了。
发布文章时 WordPress 会连着触发两个钩子(draft_to_publish 然后 publish_post)。第一个真发了请求、写了一条「推送成功」;第二个撞上我的 6 小时去重逻辑、写了一条「已推送过」—— 把它盖了。用户看到的提醒成了"好像没推",实际推成功了。
修法:没真正发出请求的分支,不许写通知。一条都不许。
写完怎么验
功能类代码最怕"看着能跑"。我的做法是写个一次性探针脚本,塞进服务器跑,测完删掉:
define('WP_ADMIN', true);
require ABSPATH . 'wp-load.php';
require ABSPATH . 'wp-admin/includes/admin.php'; // 后台函数在这里
wp_set_current_user( $admin_id );
do_action('admin_init'); // 触发设置字段注册
do_action('add_meta_boxes', 'post', $post); // 触发 meta box 注册
关键是要主动触发钩子。我一开始只调 do_meta_boxes(),结果显示「meta box 缺失」——其实是我没触发 add_meta_boxes,那些框压根没注册。差点以为自己写错了。
最后跑了 15 项检查全绿。最有价值的一条是「保存后 zz_baidu_cfg() 能读到新 token 吗」—— 就是它把那个 static 缓存揪出来的。
顺便说,这插件里还有几处我至今没深究的地方,比如后台那些 do_action 的触发时机在不同版本里偶尔有差异。我目前的办法就是多测,用真实的发布动作走一遍,而不是只信代码逻辑。
如果你也在折腾百度推送,先看看我配了半年其实一直失败的那篇,讲的是 site 参数不能带协议这个更致命的坑。