欢迎光临
我们一直在努力

实战经验:在服务器层面复制 Woo Multi Currency 插件并同步配置(WordPress / 宝塔 / Bash)

一、背景与问题来源

在使用宝塔 Linux 面板管理多个 WordPress 站点的过程中,我遇到一个非常常见但官方流程无法覆盖的需求:

  • 服务器上有多个独立 WordPress 站点;
  • 其中一个站点(abc.shop)已经安装并深度定制了一个插件;
  • 该插件为 Woo Multi Currency(VillaTheme),用于 WooCommerce 的多货币展示与结算;
  • 我对插件源码做过修改,因此不能通过 WordPress 后台安装 / 激活的方式在其他站点复用;
  • 目标是将该插件完整复制到其他站点(bcd.com、cde.site),并保证功能与配置生效。

这就引出了几个核心问题:

  • 这个插件除了 /wp-content/plugins/woo-multi-currency 目录外,还依赖哪些文件?
  • 它在数据库中写入了哪些内容?是否需要同步?
  • 如何用脚本化、可重复、可回溯的方式完成整个复制过程?

  • 二、插件简介:Woo Multi Currency 是什么?

    Woo Multi Currency(VillaTheme) 是一个用于 WooCommerce 的多货币插件,核心功能包括:

    • 多币种价格展示(基于汇率换算);
    • 自动识别用户地区(可选);
    • 货币切换器(前端组件 / Shortcode);
    • 订单记录原始货币信息;
    • 与 WooCommerce 价格计算、结账流程深度绑定。

    从实现方式上看,它属于典型的:

    “插件目录 + wp_options 配置驱动型插件”

    这对后续复制方案至关重要。


    三、第一性原理分析:复制插件到底要复制什么?

    从 WordPress 插件的运行机制出发,一个插件要“生效”,通常依赖三类东西:

    1️⃣ 插件代码(文件系统)

    • 插件主目录:

      wp-content/plugins/woo-multi-currency/

    • 如果改过源码,这一部分必须原样复制。

    2️⃣ 插件配置(数据库)

    绝大多数 WordPress 插件的配置,都会存储在:

    wp_options

    表中,而不是写入代码。

    3️⃣ 运行时数据(缓存 / uploads / meta)

    例如:

    • wp-content/uploads/xxx
    • wp_postmeta
    • wp_usermeta
    • wp_woocommerce_order_itemmeta

    是否需要复制,取决于插件实现方式。


    四、实际排查结果(以 adc.shop 为例)

    1. 数据库中与插件相关的内容

    执行:

    SELECT * FROM wp_options WHERE option_name LIKE '%woo_multi_currency%';

    得到关键结果:

    • woo_multi_currency_params

      • 一个 serialize 的数组
      • 包含:默认货币、启用币种、汇率、小数位数、UI 配色、自动识别等
      • 属于插件的核心配置
    • villatheme_woo-multi-currency_start_use

      • 值为 1
      • 本质是一个“插件已使用 / 已初始化”的标记

    而以下表查询均为空:

    wp_postmeta
    wp_usermeta
    wp_woocommerce_order_itemmeta

    说明:

    该插件在当前站点中 不依赖内容级 / 用户级 / 订单级额外元数据。

    2. 文件系统相关目录

    检查结果:

    • /wp-content/uploads/woo-multi-currency ❌ 不存在
    • /wp-content/languages/plugins/ ❌ 不存在

    结论非常清晰:

    只需要复制插件目录 + 同步 wp_options 中的两条记录即可。


    五、为什么不能“直接导出 SQL 再导入”?

    在多站点环境中,每个站点可能:

    • 使用不同的数据库;
    • 使用不同的 $table_prefix;
    • DB 用户、密码、主机不同;

    如果手写 SQL,很容易出错,也不利于后期复用。

    因此,我选择:

    用 Bash 脚本自动解析 wp-config.php,动态处理数据库信息。


    六、核心实现思路(脚本设计)

    整个脚本做了以下事情:

    1️⃣ 自动解析 wp-config.php

    从每个站点的:

    /www/wwwroot/{site}/wp-config.php

    中提取:

    • DB_NAME
    • DB_USER
    • DB_PASSWORD
    • DB_HOST
    • $table_prefix

    ⚠️ 实战中发现一个坑:
    define() 使用的是 双引号,最初的正则只支持单引号,导致解析失败。
    最终版本同时兼容 ' 和 "。


    2️⃣ 导出源站点插件配置(精确到 option)

    mysqldump ... wp_options \\
    –where="option_name='woo_multi_currency_params'
    OR option_name='villatheme_woo-multi-currency_start_use'"

    只导出 必要配置,而不是整个 wp_options。


    3️⃣ 替换表前缀并导入目标站点

    • 自动将 wp_options → {target_prefix}options
    • 导入前先删除目标站点已有同名 option(防止重复)
    • 导入前对目标站点配置做 备份

    4️⃣ 同步插件源码(rsync)

    rsync -a –delete \\
    abc/wp-content/plugins/woo-multi-currency/ \\
    target/wp-content/plugins/woo-multi-currency/

    确保:

    • 覆盖旧文件;
    • 保留你修改过的源码;
    • 不依赖 WordPress 后台安装。

    七、执行方式(后台 + 可追溯)

    把下面完整脚本保存为 /root/copy_woo_multi_currency.sh(以 root 或有权限用户执行):

    #!/bin/bash
    # copy_woo_multi_currency.sh
    # 功能说明:
    # 1. 从源站点 abc.shop 复制已经被修改过的 woo-multi-currency 插件文件
    # 2. 同步指定的插件配置项(options)到多个目标站点数据库
    #
    # 设计用途:
    # 源站点路径:/www/wwwroot/abc.shop
    # 目标站点路径:
    # – /www/wwwroot/bcd.com
    # – /www/wwwroot/cde.site
    #
    # 日志文件:/root/copy_woo_multi_currency.log
    #
    # 使用方式:
    # 1. 将脚本放在 /root 目录
    # 2. chmod +x copy_woo_multi_currency.sh
    # 3. 后台运行:
    # nohup bash /root/copy_woo_multi_currency.sh > /root/copy_woo_multi_currency.log 2>&1 &

    # 遇到任何错误立即退出;未定义变量视为错误;管道中任一命令失败则整体失败
    set -euo pipefail

    # 设置 IFS,避免因为空格导致循环或变量解析异常
    IFS=$'\\n\\t'

    # 定义日志文件路径
    LOG="/root/copy_woo_multi_currency.log"

    # 将 stdout 和 stderr 同时输出到终端并追加写入日志文件
    exec > >(tee -a "$LOG") 2>&1

    echo "===== $(date '+%F %T') START copy_woo_multi_currency ====="

    # —————————–
    # 基础路径与站点配置
    # —————————–

    # 源站点域名
    SRC_SITE="abc.shop"

    # 源站点 WordPress 根目录
    SRC_PATH="/www/wwwroot/${SRC_SITE}"

    # 插件相对路径
    PLUGIN_DIR_REL="wp-content/plugins/woo-multi-currency"

    # 源站点插件完整路径
    PLUGIN_SRC="${SRC_PATH}/${PLUGIN_DIR_REL}"

    # 目标站点域名数组
    TARGET_SITES=("bcd.com" "cde.site")

    # 所有站点的统一根目录
    WWW_ROOT="/www/wwwroot"

    # —————————–
    # 需要同步的 WordPress option 名称
    # —————————–
    OPTIONS_TO_COPY=(
    "woo_multi_currency_params"
    "villatheme_woo-multi-currency_start_use"
    )

    # —————————–
    # 基础工具依赖检查
    # —————————–
    for cmd in rsync mysql mysqldump grep sed awk cat; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "ERROR: required command '$cmd' not found. Install it and retry."
    exit 1
    fi
    done

    # —————————–
    # 工具函数:解析 wp-config.php
    # 功能:
    # – 提取数据库名、用户名、密码、主机
    # – 提取表前缀 $table_prefix
    # 参数:
    # – WordPress 根目录路径
    # 返回:
    # – DB_NAME|DB_USER|DB_PASSWORD|DB_HOST|TABLE_PREFIX
    # —————————–
    parse_wp_config() {
    local wp_root="$1"
    local cfg="${wp_root}/wp-config.php"

    # 如果 wp-config.php 不存在,直接报错
    if [ ! -f "$cfg" ]; then
    echo "ERR::no-wp-config"
    return 1
    fi

    # 使用 grep + sed 提取 define('DB_XXX', 'value') 中的值
    local DB_NAME DB_USER DB_PASSWORD DB_HOST TABLE_PREFIX
    DB_NAME=$(grep -E "define\\(\\s*'DB_NAME'" "$cfg" | sed -E "s/.*'DB_NAME'\\s*,\\s*'([^']+)'.*/\\1/")
    DB_USER=$(grep -E "define\\(\\s*'DB_USER'" "$cfg" | sed -E "s/.*'DB_USER'\\s*,\\s*'([^']+)'.*/\\1/")
    DB_PASSWORD=$(grep -E "define\\(\\s*'DB_PASSWORD'" "$cfg" | sed -E "s/.*'DB_PASSWORD'\\s*,\\s*'([^']+)'.*/\\1/")
    DB_HOST=$(grep -E "define\\(\\s*'DB_HOST'" "$cfg" | sed -E "s/.*'DB_HOST'\\s*,\\s*'([^']+)'.*/\\1/")

    # 提取 $table_prefix 变量
    TABLE_PREFIX=$(grep -E "^\\s*\\\\\\$table_prefix" "$cfg" | sed -E "s/.*\\\\\\$table_prefix\\s*=\\s*'([^']+)'.*/\\1/")

    # 如果没有找到表前缀,默认使用 wp_
    if [ -z "$TABLE_PREFIX" ]; then
    TABLE_PREFIX="wp_"
    fi

    # 通过 | 分隔输出,便于后续 awk 解析
    printf "%s|%s|%s|%s|%s" "$DB_NAME" "$DB_USER" "$DB_PASSWORD" "$DB_HOST" "$TABLE_PREFIX"
    return 0
    }

    # —————————–
    # 校验源插件目录是否存在
    # —————————–
    if [ ! -d "$PLUGIN_SRC" ]; then
    echo "ERROR: plugin source directory not found: $PLUGIN_SRC"
    exit 1
    fi

    # —————————–
    # 解析源站点数据库配置
    # —————————–
    echo "Parsing source wp-config.php for ${SRC_SITE}…"
    SRC_CFG_LINE=$(parse_wp_config "$SRC_PATH" || true)

    if [[ "$SRC_CFG_LINE" == "ERR::no-wp-config" || -z "$SRC_CFG_LINE" ]]; then
    echo "ERROR: cannot parse wp-config.php for source site ${SRC_PATH}"
    exit 1
    fi

    # 拆分数据库信息
    SRC_DB_NAME=$(echo "$SRC_CFG_LINE" | awk -F'|' '{print $1}')
    SRC_DB_USER=$(echo "$SRC_CFG_LINE" | awk -F'|' '{print $2}')
    SRC_DB_PASS=$(echo "$SRC_CFG_LINE" | awk -F'|' '{print $3}')
    SRC_DB_HOST=$(echo "$SRC_CFG_LINE" | awk -F'|' '{print $4}')
    SRC_TABLE_PREFIX=$(echo "$SRC_CFG_LINE" | awk -F'|' '{print $5}')

    # options 表名
    SRC_OPTIONS_TABLE="${SRC_TABLE_PREFIX}options"

    echo "Source DB: ${SRC_DB_NAME}@${SRC_DB_HOST} (user: ${SRC_DB_USER}), table_prefix: ${SRC_TABLE_PREFIX}"

    # —————————–
    # 从源数据库导出指定 option
    # —————————–
    TMP_DUMP="/tmp/woo_multi_currency_options_${SRC_SITE}_$$.sql"
    echo "Dumping options from source DB into $TMP_DUMP …"

    # 构造 WHERE 条件:option_name='a' OR option_name='b'
    WHERE_CLAUSE=""
    for opt in "${OPTIONS_TO_COPY[@]}"; do
    if [ -z "$WHERE_CLAUSE" ]; then
    WHERE_CLAUSE="option_name='${opt}'"
    else
    WHERE_CLAUSE="${WHERE_CLAUSE} OR option_name='${opt}'"
    fi
    done

    # 使用 mysqldump 只导出数据,不导出表结构
    mysqldump –skip-add-drop-table –no-create-info –complete-insert \\
    -h "${SRC_DB_HOST}" -u"${SRC_DB_USER}" -p"${SRC_DB_PASS}" "${SRC_DB_NAME}" \\
    "${SRC_OPTIONS_TABLE}" –where="$WHERE_CLAUSE" > "$TMP_DUMP"

    if [ ! -s "$TMP_DUMP" ]; then
    echo "WARNING: dump file is empty. Source options may not exist or mysqldump failed."
    fi

    # —————————–
    # 逐个处理目标站点
    # —————————–
    for site in "${TARGET_SITES[@]}"; do
    echo "——"
    echo "Processing target site: ${site}"

    TGT_PATH="${WWW_ROOT}/${site}"

    if [ ! -d "$TGT_PATH" ]; then
    echo "ERROR: target path not found: ${TGT_PATH} — skipping"
    continue
    fi

    # 使用 rsync 同步插件文件(保持权限,删除多余文件)
    echo "Copying plugin to ${TGT_PATH}/${PLUGIN_DIR_REL} …"
    mkdir -p "${TGT_PATH}/wp-content/plugins"
    rsync -a –delete "${PLUGIN_SRC%/}/" "${TGT_PATH}/wp-content/plugins/woo-multi-currency/"

    # 如果插件在 uploads 中有数据,也一并同步
    SRC_UPLOAD_DIR="${SRC_PATH}/wp-content/uploads/woo-multi-currency"
    if [ -d "$SRC_UPLOAD_DIR" ]; then
    echo "Copying uploads data…"
    mkdir -p "${TGT_PATH}/wp-content/uploads"
    rsync -a "${SRC_UPLOAD_DIR%/}/" "${TGT_PATH}/wp-content/uploads/woo-multi-currency/"
    else
    echo "No source uploads directory found: ${SRC_UPLOAD_DIR} (skipping uploads copy)."
    fi

    # 解析目标站点数据库配置
    echo "Parsing wp-config.php for target site…"
    TGT_CFG_LINE=$(parse_wp_config "$TGT_PATH" || true)

    if [[ "$TGT_CFG_LINE" == "ERR::no-wp-config" || -z "$TGT_CFG_LINE" ]]; then
    echo "ERROR: cannot parse wp-config.php for target site ${TGT_PATH} — skipping DB sync"
    continue
    fi

    TGT_DB_NAME=$(echo "$TGT_CFG_LINE" | awk -F'|' '{print $1}')
    TGT_DB_USER=$(echo "$TGT_CFG_LINE" | awk -F'|' '{print $2}')
    TGT_DB_PASS=$(echo "$TGT_CFG_LINE" | awk -F'|' '{print $3}')
    TGT_DB_HOST=$(echo "$TGT_CFG_LINE" | awk -F'|' '{print $4}')
    TGT_TABLE_PREFIX=$(echo "$TGT_CFG_LINE" | awk -F'|' '{print $5}')
    TGT_OPTIONS_TABLE="${TGT_TABLE_PREFIX}options"

    echo "Target DB: ${TGT_DB_NAME}@${TGT_DB_HOST} (user: ${TGT_DB_USER}), table_prefix: ${TGT_TABLE_PREFIX}"

    # 备份目标站点中已有的对应 option
    BACKUP_SQL="/root/backup_${site}_woo_options_$(date '+%F_%T').sql"
    echo "Backing up existing target options (if any) to ${BACKUP_SQL} …"
    mysqldump –skip-add-drop-table –no-create-info –complete-insert \\
    -h "${TGT_DB_HOST}" -u"${TGT_DB_USER}" -p"${TGT_DB_PASS}" "${TGT_DB_NAME}" \\
    "${TGT_OPTIONS_TABLE}" –where="$WHERE_CLAUSE" > "${BACKUP_SQL}" || true

    # 删除目标数据库中旧的 option,避免主键冲突
    echo "Deleting existing matching options from target DB…"
    DELETE_SQL="DELETE FROM \\`${TGT_OPTIONS_TABLE}\\` WHERE ${WHERE_CLAUSE};"
    mysql -h "${TGT_DB_HOST}" -u"${TGT_DB_USER}" -p"${TGT_DB_PASS}" "${TGT_DB_NAME}" -e "$DELETE_SQL"

    # 准备导入 SQL:替换表名和数据库名
    if [ -s "$TMP_DUMP" ]; then
    TMP_IMPORT="/tmp/woo_multi_currency_import_${site}_$$.sql"

    SRC_TABLE_ESCAPED="\\`${SRC_OPTIONS_TABLE}\\`"
    TGT_TABLE_ESCAPED="\\`${TGT_OPTIONS_TABLE}\\`"
    sed "s/${SRC_TABLE_ESCAPED}/${TGT_TABLE_ESCAPED}/g" "$TMP_DUMP" > "$TMP_IMPORT"

    sed -i "s/\\`${SRC_DB_NAME}\\`/\\`${TGT_DB_NAME}\\`/g" "$TMP_IMPORT" || true

    echo "Importing plugin options into target DB…"
    mysql -h "${TGT_DB_HOST}" -u"${TGT_DB_USER}" -p"${TGT_DB_PASS}" "${TGT_DB_NAME}" < "$TMP_IMPORT" || {
    echo "ERROR: import to target DB failed for ${site}. See log."
    }

    rm -f "$TMP_IMPORT"
    echo "Imported options for ${site}."
    else
    echo "WARNING: source dump file ${TMP_DUMP} empty; nothing to import for ${site}."
    fi

    echo "Completed site: ${site}"
    done

    # 清理临时文件
    rm -f "$TMP_DUMP"

    echo "===== $(date '+%F %T') FINISH copy_woo_multi_currency ====="

    如何部署与执行(快速步骤)

    1.将上面脚本完整粘贴到 /root/copy_woo_multi_currency.sh:

    sudo nano /root/copy_woo_multi_currency.sh
    # 粘贴并保存
    sudo chmod +x /root/copy_woo_multi_currency.sh

    2.执行(宝塔Linux面板终端中执行):

    nohup bash /root/copy_woo_multi_currency.sh > /root/copy_woo_multi_currency.log 2>&1 &

    3.查看日志(实时):

    tail -f /root/copy_woo_multi_currency.log

    所有过程都有日志,可回放、可排错。


    八、最终结论

    通过这次实践,我总结出几个非常通用的经验:

  • 不要从“插件”出发,而要从 WordPress 的运行模型出发

  • 大多数插件:

    • 核心逻辑在代码;
    • 核心配置在 wp_options;
  • 源码被修改过的插件:

    • ❌ 不适合后台安装;
    • ✅ 适合文件级复制 + 精确配置同步;
  • Bash + wp-config.php 自动解析,是多站点运维的利器;

  • 把一次性操作做成脚本,是对未来自己最友好的做法。


  • 九、适用场景总结

    本方案适用于:

    • 宝塔 / 纯 Linux 管理 WordPress;
    • 多个独立站点(非 WordPress Multisite);
    • 插件被二次开发或修改源码;
    • 希望可重复、可审计、可回滚的部署方式。

    赞(0)
    未经允许不得转载:171主机测评 » 实战经验:在服务器层面复制 Woo Multi Currency 插件并同步配置(WordPress / 宝塔 / Bash)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址