diff --git a/scripts/migrate-prod-to-dev.sh b/scripts/migrate-prod-to-dev.sh new file mode 100755 index 0000000..09871e3 --- /dev/null +++ b/scripts/migrate-prod-to-dev.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# =========================================== +# 数据迁移脚本: milkydata → milkydata_dev +# 用途: 将生产库 milkydata 的数据迁移到 milkydata_dev +# 自动处理列名差异 (is_paid → is_member, paid_expires_at → membership_expires_at) +# 注意: 目标库 milkydata_dev 中已存在的数据会被清空 +# =========================================== + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +DB_CONTAINER="1Panel-postgresql-FtMo" +DB_USER="milkydata" +SRC_DB="milkydata" +DST_DB="milkydata_dev" +BACKUP_DIR="${PROJECT_ROOT}/backups" + +mkdir -p "$BACKUP_DIR" +LOG_FILE="${BACKUP_DIR}/migration_$(date +%Y%m%d_%H%M%S).log" + +log() { echo "[$(date '+%H:%M:%S')] $1" | tee -a "$LOG_FILE"; } + +echo "═══════════════════════════════════════" +echo " 数据迁移: ${SRC_DB} → ${DST_DB}" +echo "═══════════════════════════════════════" + +# ---------- 步骤 0: 确认 ---------- +echo "" +echo "源库: ${SRC_DB}" +docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$SRC_DB" -tAc " +SELECT CONCAT(' users: ', COUNT(*), ' 行') FROM users; +SELECT CONCAT(' weather_data: ', COUNT(*), ' 行') FROM weather_data; +SELECT CONCAT(' payment_orders: ', COUNT(*), ' 行') FROM payment_orders; +" +echo "" +echo "目标库: ${DST_DB}(数据将被清空并重新导入)" +echo "" +read -p "确认迁移? [y/N] " -n 1 -r; echo +[[ ! $REPLY =~ ^[Yy]$ ]] && { echo "已取消"; exit 0; } + +# ---------- 步骤 1: 备份目标库 ---------- +log "备份目标库 ${DST_DB}..." +docker exec "$DB_CONTAINER" pg_dump -U "$DB_USER" -d "$DST_DB" -Fc \ + > "${BACKUP_DIR}/${DST_DB}_pre_migration_$(date +%Y%m%d_%H%M%S).dump" 2>> "$LOG_FILE" +log " 目标库备份完成 ✅" + +# ---------- 步骤 2: 清空目标库 ---------- +log "清空目标库 ${DST_DB}..." +docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DST_DB" -tAc " +TRUNCATE weather_data CASCADE; +TRUNCATE payment_orders CASCADE; +TRUNCATE notifications CASCADE; +TRUNCATE payment_audit_log CASCADE; +DELETE FROM users CASCADE; +" 2>> "$LOG_FILE" +log " 清空完成 ✅" + +# ---------- 步骤 3: 导出源库数据到 SQL 文件 ---------- +log "导出源库数据..." +TMP_SQL="/tmp/migrate_$$.sql" + +# users 表(列名转换: is_paid → is_member, paid_expires_at → membership_expires_at) +docker exec "$DB_CONTAINER" pg_dump -U "$DB_USER" -d "$SRC_DB" \ + --data-only --table=users --column-inserts 2>/dev/null \ + | grep -v '^--' | grep -v '^$' \ + | sed 's/ is_paid/ is_member/g' \ + | sed 's/ paid_expires_at/ membership_expires_at/g' \ + > "$TMP_SQL" +log " users 导出并转换列名 ✅" + +# weather_data +docker exec "$DB_CONTAINER" pg_dump -U "$DB_USER" -d "$SRC_DB" \ + --data-only --table=weather_data --column-inserts 2>/dev/null \ + | grep -v '^--' | grep -v '^$' >> "$TMP_SQL" +log " weather_data 导出 ✅" + +# payment_orders +docker exec "$DB_CONTAINER" pg_dump -U "$DB_USER" -d "$SRC_DB" \ + --data-only --table=payment_orders --column-inserts 2>/dev/null \ + | grep -v '^--' | grep -v '^$' >> "$TMP_SQL" +log " payment_orders 导出 ✅" + +log " 共 $(wc -l < "$TMP_SQL") 条 SQL 语句" + +# ---------- 步骤 4: 导入到目标库 ---------- +log "导入数据到 ${DST_DB}..." +docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DST_DB" < "$TMP_SQL" 2>> "$LOG_FILE" +log " 导入完成 ✅" + +# ---------- 清理 ---------- +rm -f "$TMP_SQL" + +# ---------- 验证 ---------- +echo "" +log "═════════════════════════════════" +log "迁移完成!源库 vs 目标库数据量:" +log "═════════════════════════════════" + +for table in users weather_data payment_orders; do + src=$(docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$SRC_DB" -tAc "SELECT COUNT(*) FROM ${table};" 2>/dev/null) + dst=$(docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DST_DB" -tAc "SELECT COUNT(*) FROM ${table};" 2>/dev/null) + status="✅" + [ "$src" != "$dst" ] && status="❌ 不匹配!" + log " ${table}: ${src} → ${dst} ${status}" +done + +echo "" +echo "日志: ${LOG_FILE}" +echo "备份: ${BACKUP_DIR}/${DST_DB}_pre_migration_*.dump" +echo "" +echo "⚠️ 如需回滚:gunzip -c ${BACKUP_DIR}/*.dump.gz | docker exec -i ... pg_restore"