Skip to content

WordPress 加自定义功能:部署、验证、备份 ​

用 WordPress 做一个"给自己人用"的小站,很快会遇到一个坎:插件市场里的东西都不太对,你要的功能得自己写。

这篇不讲主题开发入门,只讲在真机上安全地改一个跑着的 WordPress ——怎么部署才不丢文件、怎么在不猜密码的情况下测登录态、怎么验证越权没漏、以及改坏了怎么恢复。

前提:容器化,且代码在宿主机上 ​

宿主机  /srv/site/theme-src/     ← 你编辑的地方(git 仓库)
容器内  /var/www/html/wp-content/themes/<theme>/   ← 实际生效的地方

关键点:你的源码在宿主机上,主题目录在容器里。 中间靠一个部署脚本同步。

bash
#!/usr/bin/env bash
set -euo pipefail
THEME=mytheme
FILES=(
  style.css
  functions.php
  front-page.php
  page-order.php
  assets/order.css
  assets/order.js
)
for f in "${FILES[@]}"; do
  docker cp "theme-src/$f" "wp:/var/www/html/wp-content/themes/$THEME/$f"
done

坑一:这个文件清单是硬编码的 ​

上面那个 FILES 数组,是这套流程里最容易出事的地方。

加了一个新模板 page-records.php,只改代码不加清单,结果:部署脚本退出码 0,输出"部署完成",docker cp 也没报错(它只拷贝列出来的文件),但页面上线后是 404。

因为容器里根本没有那个文件——主题明明在,模板不存在,WordPress 就退回到首页或 404。

我在这上面浪费过一轮排查。现在固定做法:加文件的同时,改清单,一次提交。

bash
# 部署后自检:清单里的每个文件都要在容器里存在且大小一致
for f in "${FILES[@]}"; do
  a=$(md5sum "theme-src/$f" | cut -d' ' -f1)
  b=$(docker exec wp md5sum "/var/www/html/wp-content/themes/$THEME/$f" | cut -d' ' -f1)
  [ "$a" = "$b" ] || echo "❌ 不一致: $f"
done

坑二:$wpdb->prefix 已经带了前缀 ​

建自定义表时:

php
// ❌ 得到 wl_wl_so_cats(前缀被写了两遍)
$table = $wpdb->prefix . 'wl_so_cats';

// ✅ 得到 wl_so_cats
$table = $wpdb->prefix . 'so_cats';

$wpdb->prefix 的值本来就是 wl_ 这种(安装时定的)。再拼一次自定义前缀就重复了。症状是建表成功,但查询全空——因为你查的表名和建的表名不一样。

怎么测「登录状态下的页面」,而不去猜密码 ​

这是我最想推荐的一个技巧。要在命令行里验证"登录用户看到的页面",常见做法是去后台重置密码再造一个测试账号——慢,而且污染数据。

正确做法:让 WordPress 自己给你签一个合法 cookie。

bash
# 在容器里跑 PHP,生成一个合法登录 cookie
docker exec wp wp eval '
  $n = "wordpress_logged_in_" . COOKIEHASH;
  echo $n . "=" . wp_generate_auth_cookie(1, time() + 3600, "logged_in");
'

拿到的字符串直接当 Cookie 头用:

bash
curl -s "http://127.0.0.1:8080/my-page/" \
  -H "Cookie: wordpress_logged_in_xxxx=yyyy" \
  -H "Host: www.example.com" \
  -H "X-Forwarded-Proto: https" \
  -o /tmp/page.html -w "%{http_code}\n"

那两个 Header 不是可选的 ​

  • Host:站点是域名配置的,用 127.0.0.1 访问会被 WordPress 的 canonical 重定向 301 到正式域名。你以为页面挂了,其实是重定向了。
  • X-Forwarded-Proto: https:站点 URL 是 https://,缺这个头,WordPress 认为是 http 请求,同样 301。

只带 Cookie 不带你永远只能看到 301,然后误判"登录态无效"。

要进 /wp-admin/ 的话,还需要第二个 cookie:

bash
# wordpress_sec_ 系列的 cookie,同理用 COOKIEHASH 拼
$n2 = "wordpress_sec_" . COOKIEHASH;

坑三:wp eval-file 要先放进共享卷 ​

用 docker run --volumes-from wp 这种方式跑 wp-cli 时,那个临时容器和主容器不共享 /tmp。

所以 wp eval-file /tmp/test.php 会报找不到文件。姿势是:

bash
docker cp test.php wp:/var/www/html/test.php
docker exec wp wp eval-file /var/www/html/test.php
docker exec wp rm /var/www/html/test.php   # 用完就删,别留在 web 根目录

⚠️ 最后那行别忘。/var/www/html/ 是公开目录,测试脚本留在那儿可能直接被人访问。

越权测试:别只看按钮消失了 ​

做"只有某些用户能改"的功能时,前端隐藏按钮不算权限控制。要测的是"带合法请求直接打接口"。

做法:临时建一个最低权限用户(subscriber),用他的 cookie 发请求:

bash
# 1. 建测试用户
docker exec wp wp user create tester3 tester3@example.com --role=subscriber --user_pass=<随机>

# 2. 用他的 cookie,带上真实 nonce,直接 POST 修改请求
curl -s -X POST "http://127.0.0.1:8080/wp-admin/admin-post.php" \
  -H "Cookie: $(生成 tester3 的 cookie)" \
  -H "Host: www.example.com" -H "X-Forwarded-Proto: https" \
  -d "action=my_update_order&order_id=12&status=accepted&_wpnonce=<合法 nonce>"

预期结果是"数据没变"。如果状态真被改了,说明你的权限检查写在了前端或者根本没写。

测完记得删测试用户和测试数据:

bash
docker exec wp wp user delete tester3 --yes

备份:三件套,缺一不可 ​

改之前先确认备份是能恢复的,不是"应该能恢复"。

我的结构:

层位置频率保留
本地宿主机 backups/每天 04:0014 份
异地云挂载/对象存储每天 04:307 份
加密随异地包同上AES-256

异地包里放两类东西:

  • 源码包(明文) —— 出事了能立刻看,方便排错
  • 完整数据包(AES-256 加密) —— 含数据库、uploads/、wp-config.php、.env、发信密钥
bash
# 加密(口令单独存一个 600 的文件,不进仓库)
tar czf - db uploads wp-config.php \
  | openssl enc -aes-256-cbc -pbkdf2 -salt -pass file:/root/.backup_pass \
  > /tmp/site-full.tar.gz.enc

⚠️ 云挂载点不能直写。这类挂载(rclone/WebDAV)上,> 重定向会报 Bad file descriptor,tar 直写出来是 0 字节但不报错。必须先在本地 /tmp 打包好,再 cp 过去,并核对 md5:

bash
cp /tmp/site-full.tar.gz.enc /mnt/cloud/backup/
md5sum /tmp/site-full.tar.gz.enc /mnt/cloud/backup/site-full.tar.gz.enc

这条坑我单独写了一篇:云挂载为什么不能直写。

一个常被忽略的开关:force_from_email ​

如果站里装了 SMTP 插件,注意这个选项。打开之后,全站所有邮件(不只是你那个功能发的)都会用同一个发件人——包括评论通知、密码重置、WordPress 自动邮件。

好处是发信域统一、不容易进垃圾箱;坏处是用户收到"密码重置"邮件,发件人却叫「某某点单」,看着像钓鱼。

另外要确认发件地址是否真的能收信。发信域名和收信域名(MX)可以是完全不同的两条链路——我的 love@ 地址只能发不能收,所以邮件模板里必须写"无需回复"或者给一个真实可收的回复地址。

清单 ​

在跑着的站点上加自定义功能,每次走一遍:

  1. 改源码在宿主机 → 同步更新部署脚本的文件清单
  2. 部署后逐个文件核对 md5,别信退出码
  3. 测页面:用 wp eval 生成 cookie,带 Host + X-Forwarded-Proto
  4. 测权限:用最低权限用户 + 真实 nonce 直接打接口
  5. 清理测试用户、测试数据、临时 PHP 文件
  6. 先在本地打包含 DB + uploads + 配置,再 cp 到异地并核对 md5

一句话版本:在活着的站点上改东西,最大的风险不是写错代码,是你以为部署生效了。所以每一步都要有"看起来对了不算,得验证"的那一下。