本文约 9,640 字,预计阅读 25 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:演示站的两个致命风险
给客户做演示环境是建站项目的标配:让客户在正式上线前点一点、试一试,比看截图有效得多。但演示环境有两个高频事故:
一是客户在演示站上”认真”录入了数据。 你把演示站给他看,他当成正式站,花两天上传了 50 个产品和公司介绍。等你把正式站部署好、演示站被重置,所有工作白做,客户很生气。
二是演示站被搜索引擎收录。 演示站和正式站内容高度重复,被收录后形成大量重复页面,正式站的收录和排名被稀释。更糟的是演示站的 URL(比如 demo.mimangfei.com)出现在品牌词的搜索结果里,客户会非常不满。
这两个问题的根源是同一个:演示站与正式站之间没有明确的隔离边界。 解决思路也是同一个——从”可写性”和”可见性”两个维度做隔离。
原理分析:两个维度的隔离
维度一:可写性(能不能改)。 目标是让客户能”看到操作效果”但不能”真正改变数据”。
实现方式从强到弱:
- 数据库级别只读:给演示站用的数据库账号只有 SELECT 权限,任何写入都会报错;
- 应用级别只读:用 WordPress 钩子拦截所有写入操作;
- 定时重置:允许写入,但每隔几小时自动恢复到初始状态;
- 不设限:允许自由写入(风险最高,但体验最真实)。
维度二:可见性(能不能被搜到)。 目标是让演示站对搜索引擎和用户不可见。
实现方式:
- HTTP 基础认证(Basic Auth):访问需要输入用户名密码,搜索引擎完全无法抓取;
- IP 白名单:只允许客户公司 IP 访问;
- robots.txt + noindex:软性屏蔽,仍可被直接访问;
- 子域名 + 隐藏入口:不做任何屏蔽,只靠不宣传(不可取)。
组合推荐:
| 场景 | 可写性方案 | 可见性方案 |
|---|---|---|
| 给客户看效果(推荐) | 定时重置(每 6 小时) | HTTP 基础认证 |
| 给客户做培训练习 | 完全可写(独立数据库) | HTTP 基础认证 |
| 公开的模板演示站 | 定时重置 | 允许收录 + canonical 指向正式站 |
| 内部评审 | 只读模式 | IP 白名单 |
关于 WP_ENVIRONMENT_TYPE: WordPress 5.5 引入了这个常量,可选值 local、development、staging、production。设置为 staging 后,部分插件(如 SEO 插件、缓存插件)会自动调整行为(比如 Rank Math 会在非生产环境下提示索引设置)。这是一个很好的环境标识手段,应作为标准配置。
保姆级实操:搭建一套隔离的演示环境
步骤 1:创建独立的演示站点与数据库。 演示站必须与正式站物理隔离(独立目录 + 独立数据库),共享数据库是最危险的做法:
# 1. 创建演示站目录与独立数据库
sudo mkdir -p /var/www/demo
sudo mysql -e "CREATE DATABASE wp_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
-- 2. 创建只读/受限的数据库账号(按需选择,见步骤 3)
CREATE USER 'wp_demo'@'localhost' IDENTIFIED BY '演示库专用密码';
-- 完全只读方案:只给 SELECT
GRANT SELECT ON wp_demo.* TO 'wp_demo'@'localhost';
-- 可写方案(配合定时重置):给完整权限
-- GRANT ALL PRIVILEGES ON wp_demo.* TO 'wp_demo'@'localhost';
FLUSH PRIVILEGES;
# 3. 复制正式站文件到演示站
rsync -a --exclude='wp-config.php' /var/www/html/ /var/www/demo/
cp /var/www/html/wp-config.php /var/www/demo/wp-config.php
# 4. 修改演示站配置
cd /var/www/demo
wp config set DB_NAME wp_demo
wp config set DB_USER wp_demo --config-file=/var/www/demo/wp-config.php
wp config set table_prefix wp_demo_
步骤 2:配置环境变量标识与演示提示。 在演示站的 wp-config.php 里加上:
// /var/www/demo/wp-config.php —— 演示环境专用配置
// 标识环境类型,插件会据此调整行为
define( 'WP_ENVIRONMENT_TYPE', 'staging' );
// 关闭调试显示,但保留日志记录
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', true );
// 禁止后台编辑主题与插件文件(防止演示者误改代码)
define( 'DISALLOW_FILE_EDIT', true );
// 禁止后台安装/更新插件主题(更严格的场景用)
define( 'DISALLOW_FILE_MODS', true );
// 演示模式下禁止向外部发送邮件(避免演示数据触发真实邮件)
define( 'WPMS_MAIL_RETURN_PATH', false );
步骤 3A:实现应用级只读模式(推荐,体验最好)。 用 WordPress 钩子拦截写入操作,客户点击”发布”时会看到友好提示而不是报错:
<?php
/**
* Plugin Name: 演示站只读模式
* Description: 禁止在演示环境进行写操作,仅用于客户演示
* Version: 1.0
*/
// 只在演示环境加载(配合 WP_ENVIRONMENT_TYPE === 'staging')
if ( ! defined( 'WP_ENVIRONMENT_TYPE' ) || WP_ENVIRONMENT_TYPE !== 'staging' ) {
return;
}
/**
* 拦截文章/页面的新建、修改、删除
*/
add_filter( 'wp_insert_post_empty_content', function ( $maybe_empty, $postarr ) {
// 允许自动草稿(进入后台编辑界面需要它)
if ( isset( $postarr['post_status'] ) && 'auto-draft' === $postarr['post_status'] ) {
return $maybe_empty;
}
return true; // 返回 true 会让 wp_insert_post 直接中止
}, 10, 2 );
/**
* 在写入前统一拦截并给出提示(覆盖更多写入场景)
*/
add_action( 'admin_init', function () {
$blocked = [ 'editpost', 'post', 'edit', 'trash', 'delete', 'untrash' ];
$action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : '';
if ( in_array( $action, $blocked, true ) && ! wp_doing_ajax() ) {
wp_die(
'<h2>这是演示环境</h2>' .
'<p>当前站点为只读演示环境,所有修改操作已被禁用,您录入的数据不会保存。</p>' .
'<p>如需正式录入内容,请使用正式站点。</p>' .
'<p><a href="javascript:history.back();">返回上一页</a></p>',
'演示环境只读',
[ 'response' => 403, 'back_link' => false ]
);
}
} );
/**
* 后台顶部显示醒目提示条
*/
add_action( 'admin_notices', function () {
echo '<div class="notice notice-warning" style="border-left-color:#d63638;">';
echo '<p><strong>演示环境(只读)</strong> — 所有修改不会被保存,请勿在此录入真实数据。</p>';
echo '</div>';
} );
add_action( 'wp_head', function () {
if ( ! is_user_logged_in() ) { return; }
echo '<style>#demo-banner{position:fixed;top:0;left:0;right:0;z-index:99999;'
. 'background:#d63638;color:#fff;text-align:center;padding:6px;font-size:14px;}</style>';
echo '<div id="demo-banner">演示环境 · 数据仅供浏览</div>';
} );
把这个文件保存为 /var/www/demo/wp-content/mu-plugins/demo-readonly.php(mu-plugins 目录默认启用且无法在后台关闭,适合放这类强制逻辑)。
步骤 3B:定时重置方案(允许自由操作时用)。 客户需要真实体验”发布一篇文章”的完整流程时,用定时重置比只读更合适:
#!/usr/bin/env bash
# /opt/scripts/reset_demo.sh —— 每 6 小时重置演示站到初始状态
set -uo pipefail
DEMO_DB=wp_demo
DEMO_DIR=/var/www/demo
SNAPSHOT=/opt/demo_snapshot.sql.gz
LOG=/var/log/demo_reset.log
echo "[$(date '+%F %T')] 开始重置演示站" >> "$LOG"
# 1. 从快照恢复数据库
zcat "$SNAPSHOT" | mysql "$DEMO_DB"
if [ $? -ne 0 ]; then
echo "[$(date '+%F %T')] 错误: 数据库恢复失败" >> "$LOG"
exit 1
fi
# 2. 清理演示期间上传的文件(保留初始演示图片)
find "$DEMO_DIR/wp-content/uploads" -type f -newermt '6 hours ago' -delete
# 3. 清理对象缓存与页面缓存
wp --path="$DEMO_DIR" cache flush --allow-root
wp --path="$DEMO_DIR" rewrite flush --hard --allow-root
rm -rf "$DEMO_DIR/wp-content/cache/*"
echo "[$(date '+%F %T')] 重置完成" >> "$LOG"
# 先创建快照(演示站配置好之后执行一次)
mysqldump --single-transaction --default-character-set=utf8mb4 wp_demo | gzip > /opt/demo_snapshot.sql.gz
# 加入 crontab,每 6 小时重置
echo "0 */6 * * * root /opt/scripts/reset_demo.sh" | sudo tee /etc/cron.d/demo-reset
步骤 4:配置可见性隔离——HTTP 基础认证。 这是最有效的一道屏障,搜索引擎完全无法穿透:
# 生成密码文件(用户名 demo,会提示输入密码)
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd_demo demo
# 再给客户加一个账号
sudo htpasswd /etc/nginx/.htpasswd_demo client
server {
listen 443 ssl;
http2 on;
server_name demo.mimangfei.com;
root /var/www/demo;
ssl_certificate /etc/nginx/ssl/mimangfei.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/mimangfei.com/key.pem;
# HTTP 基础认证:访问前必须先通过这一层
auth_basic "演示环境,请输入访问凭据";
auth_basic_user_file /etc/nginx/.htpasswd_demo;
# 可选:叠加 IP 白名单,双重保险
# allow 203.0.113.0/24; # 客户公司出口 IP
# deny all;
# satisfy all; # 需同时满足 IP 与密码
location / { try_files $uri $uri/ /index.php?$args; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param HTTPS on;
}
}
步骤 5:叠加 robots.txt 与 noindex 双保险。 即使基础认证被绕过(比如有人直接分享链接),也不希望被收录:
# Nginx 直接返回全禁的 robots.txt,不走 WordPress
location = /robots.txt {
add_header Content-Type text/plain;
return 200 "User-agent: *\nDisallow: /\n";
}
# 给所有响应加上 noindex 头(覆盖 HTML、PDF 等所有类型)
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
同时在 WordPress 后台「设置 → 阅读」勾选”建议搜索引擎不索引本站点”(对应 blog_public 选项为 0):
wp --path=/var/www/demo option update blog_public 0
wp --path=/var/www/demo option get blog_public # 确认返回 0
如果站点用了 SEO 插件(Yoast / Rank Math),还要在插件设置里关闭站点地图输出与结构化数据:
# Rank Math:关闭站点地图(演示站不需要)
wp --path=/var/www/demo option patch update rank_math_titles noindex_demo true
# 更直接的做法:在演示站停用 SEO 插件
wp --path=/var/www/demo plugin deactivate seo-by-rank-math
步骤 6:数据脱敏。 演示站如果用的是正式站的真实数据副本,必须先脱敏:
# 1. 清空所有真实用户的密码哈希(避免口令泄露风险)
wp --path=/var/www/demo user reset-password --all --skip-email
# 2. 批量替换真实联系信息为演示信息(手机号、邮箱、地址)
wp --path=/var/www/demo search-replace '13800138000' '13800000000' --precise
wp --path=/var/www/demo search-replace '@真实域名.com' '@example.com' --precise
# 3. 删除真实的表单提交记录
wp --path=/var/www/demo post delete $(wp --path=/var/www/demo post list \
--post_type='ff_submissions' --format=ids) --force 2>/dev/null
# 4. 确认没有真实的 API 密钥残留
wp --path=/var/www/demo option list --search='*api_key*' --format=csv --fields=option_name
步骤 7:验证隔离是否生效。 交付客户前逐项确认:
# 1. 未带凭据访问应返回 401
curl -s -o /dev/null -w '未认证访问: %{http_code}\n' https://demo.mimangfei.com/
# 期望: 401
# 2. 带凭据访问应返回 200
curl -s -o /dev/null -u 'demo:密码' -w '认证后访问: %{http_code}\n' https://demo.mimangfei.com/
# 期望: 200
# 3. 检查 noindex 响应头
curl -sI -u 'demo:密码' https://demo.mimangfei.com/ | grep -i x-robots-tag
# 期望: X-Robots-Tag: noindex, nofollow, noarchive
# 4. 检查 robots.txt
curl -s https://demo.mimangfei.com/robots.txt
# 期望: User-agent: * / Disallow: /
# 5. 确认演示站与正式站用的是不同数据库
wp --path=/var/www/demo option get home
wp --path=/var/www/html option get home
常见踩坑
- 演示站与正式站共用数据库。 客户在演示站删了内容,正式站一起没了。必须独立数据库。
- 只做了 noindex 没做认证。 noindex 只是”建议”,页面本身仍可被访问和分享,且部分低质量爬虫不遵守。要拒收必须用基础认证或 IP 白名单。
- 忘了关演示站的邮件发送。 演示站的表单、订单通知会真的发到客户邮箱,造成困扰。用 SMTP 拦截插件或直接禁用邮件发送。
- 演示站保留了真实的 API 密钥。 支付接口、短信接口的密钥留在演示站,一旦泄露会产生真实费用。交付前必须检查并替换为测试密钥。
- 定时器没生效导致重置失败。 cron 里的 PATH 问题导致
wp命令找不到,重置静默失败。脚本里用绝对路径/usr/local/bin/wp。 - 演示图片被 CDN 缓存后清理不掉。 演示站接入了 CDN,重置后 CDN 节点仍有旧内容。演示站不应接入生产 CDN,或者重置后主动刷新 CDN 缓存。
- 案例:一次演示站被收录引发的风波。 乙方给客户做了
demo.客户域名.com的演示站,只配了noindex,没有做认证。结果客户的竞争对手搜索品牌词时,Google 结果里同时出现了正式站和演示站(因为noindex需要爬虫重新抓取后才生效,而在此之前 URL 已被发现和索引),且演示站上还留着未定稿的页面文案。客户认为”内部信息被公开”,要求赔偿。事后处理:加上基础认证 → 在 Google Search Console 提交”临时移除” → 等noindex生效。教训:演示站从创建那一刻起就要做认证隔离,而不是等收录后再补救。 另外提醒一点:演示站的 SSL 证书如果用了泛域名证书,子域名(demo)会被证书透明度日志(Certificate Transparency Logs)公开记录,等于变相泄露了演示站的存在,这也是”从一开始就隔离”的理由之一。
总结
演示环境的判断标准是”客户能看能试,但改不了数据、进不了搜索结果”。执行顺序:独立目录与独立数据库 → wp-config.php 里设置 WP_ENVIRONMENT_TYPE 为 staging → 用 mu-plugins 实现只读模式(或配置定时重置)→ Nginx 加 HTTP 基础认证 → robots.txt 与 X-Robots-Tag 双保险 → 数据脱敏(清密码、换联系方式、删表单记录、查 API 密钥)→ 逐项验证。只读模式适合”看效果”,定时重置适合”练操作”,两者可以二选一,也可以叠加使用。 演示站的服务器安全同样重要,参照 网站上线安全基线配置 加固;演示站的数据快照生成方式可以参考 网站数据备份策略 里的 mysqldump 用法;如果演示站用于培训,交付时还需结合 客户项目交付注意事项 做好说明。
