搜索全站
安装部署 25 分钟阅读

给客户站做只读演示环境的做法:演示站与正式站的隔离方案

给客户做演示环境,既要能操作又不能改坏数据、不能被搜索引擎收录。本文给出只读模式代码、HTTP 基础认证、robots 与 noindex 隔离、独立数据库与自动重置四种方案的组合做法。

U
UFO_PF 2017-10-11 · 阅读 25 分钟
给客 安装部署
阅读提示
本文约 9,640 字,预计阅读 25 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:演示站的两个致命风险

给客户做演示环境是建站项目的标配:让客户在正式上线前点一点、试一试,比看截图有效得多。但演示环境有两个高频事故:

一是客户在演示站上”认真”录入了数据。 你把演示站给他看,他当成正式站,花两天上传了 50 个产品和公司介绍。等你把正式站部署好、演示站被重置,所有工作白做,客户很生气。

二是演示站被搜索引擎收录。 演示站和正式站内容高度重复,被收录后形成大量重复页面,正式站的收录和排名被稀释。更糟的是演示站的 URL(比如 demo.mimangfei.com)出现在品牌词的搜索结果里,客户会非常不满。

这两个问题的根源是同一个:演示站与正式站之间没有明确的隔离边界。 解决思路也是同一个——从”可写性”和”可见性”两个维度做隔离。

原理分析:两个维度的隔离

维度一:可写性(能不能改)。 目标是让客户能”看到操作效果”但不能”真正改变数据”。

实现方式从强到弱:

  1. 数据库级别只读:给演示站用的数据库账号只有 SELECT 权限,任何写入都会报错;
  2. 应用级别只读:用 WordPress 钩子拦截所有写入操作;
  3. 定时重置:允许写入,但每隔几小时自动恢复到初始状态;
  4. 不设限:允许自由写入(风险最高,但体验最真实)。

维度二:可见性(能不能被搜到)。 目标是让演示站对搜索引擎和用户不可见。

实现方式:

  1. HTTP 基础认证(Basic Auth):访问需要输入用户名密码,搜索引擎完全无法抓取;
  2. IP 白名单:只允许客户公司 IP 访问;
  3. robots.txt + noindex:软性屏蔽,仍可被直接访问;
  4. 子域名 + 隐藏入口:不做任何屏蔽,只靠不宣传(不可取)。

组合推荐:

场景 可写性方案 可见性方案
给客户看效果(推荐) 定时重置(每 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

常见踩坑

  1. 演示站与正式站共用数据库。 客户在演示站删了内容,正式站一起没了。必须独立数据库。
  2. 只做了 noindex 没做认证。 noindex 只是”建议”,页面本身仍可被访问和分享,且部分低质量爬虫不遵守。要拒收必须用基础认证或 IP 白名单。
  3. 忘了关演示站的邮件发送。 演示站的表单、订单通知会真的发到客户邮箱,造成困扰。用 SMTP 拦截插件或直接禁用邮件发送。
  4. 演示站保留了真实的 API 密钥。 支付接口、短信接口的密钥留在演示站,一旦泄露会产生真实费用。交付前必须检查并替换为测试密钥。
  5. 定时器没生效导致重置失败。 cron 里的 PATH 问题导致 wp 命令找不到,重置静默失败。脚本里用绝对路径 /usr/local/bin/wp。
  6. 演示图片被 CDN 缓存后清理不掉。 演示站接入了 CDN,重置后 CDN 节点仍有旧内容。演示站不应接入生产 CDN,或者重置后主动刷新 CDN 缓存。
  7. 案例:一次演示站被收录引发的风波。 乙方给客户做了 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 用法;如果演示站用于培训,交付时还需结合 客户项目交付注意事项 做好说明。

U
UFO_PF作者

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

留下你的想法

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