#!/bin/bash # CCDI 数据库迁移自动化脚本 # 功能:数据库导出和导入自动化 set -e # 遇到错误立即退出 # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # 日志函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 脚本目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/db_config.conf" # 检查配置文件 check_config() { if [ ! -f "$CONFIG_FILE" ]; then log_error "配置文件不存在: $CONFIG_FILE" log_info "请先复制配置模板: cp db_config.conf.template db_config.conf" log_info "然后编辑 db_config.conf 填写实际数据库连接信息" exit 1 fi # 加载配置文件 source "$CONFIG_FILE" log_info "配置文件加载成功" } # 检查 mysqldump 命令 check_mysqldump() { if ! command -v mysqldump &> /dev/null; then log_error "mysqldump 命令未找到" log_info "请安装 MySQL 客户端工具" exit 1 fi log_info "mysqldump 命令检查通过" } # 创建备份目录 create_backup_dir() { if [ ! -d "$BACKUP_DIR" ]; then mkdir -p "$BACKUP_DIR" log_info "创建备份目录: $BACKUP_DIR" fi } # 导出表结构 export_structure() { log_info "开始导出表结构..." local output_file="${BACKUP_DIR}/${STRUCTURE_FILE}" # 创建临时文件 local temp_file=$(mktemp) # 导出表结构到临时文件 mysqldump -h "$SOURCE_DB_HOST" \ -P "$SOURCE_DB_PORT" \ -u "$SOURCE_DB_USER" \ -p"$SOURCE_DB_PASS" \ --no-data \ --skip-triggers \ --skip-add-drop-table \ --default-character-set=$CHARACTER_SET \ --single-transaction \ --max_allowed_packet=$MAX_ALLOWED_PACKET \ "$SOURCE_DB_NAME" > "$temp_file" 2>/dev/null if [ $? -eq 0 ]; then # 添加字符集声明到文件头部 { echo "-- CCDI 数据库表结构" echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" echo "-- 源数据库: ${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}" echo "-- 字符集: ${CHARACTER_SET}" echo "" echo "SET NAMES utf8mb4;" echo "SET CHARACTER SET utf8mb4;" echo "SET GLOBAL character_set_client=utf8mb4;" echo "SET GLOBAL character_set_connection=utf8mb4;" echo "SET GLOBAL character_set_results=utf8mb4;" echo "" cat "$temp_file" } > "$output_file" rm -f "$temp_file" log_info "表结构导出成功: $output_file" log_info "文件大小: $(du -h "$output_file" | cut -f1)" else rm -f "$temp_file" log_error "表结构导出失败" exit 1 fi } # 导出数据 export_data() { log_info "开始导出数据..." local output_file="${BACKUP_DIR}/${DATA_FILE}" # 创建临时文件 local temp_file=$(mktemp) # 导出数据到临时文件 mysqldump -h "$SOURCE_DB_HOST" \ -P "$SOURCE_DB_PORT" \ -u "$SOURCE_DB_USER" \ -p"$SOURCE_DB_PASS" \ --no-create-info \ --skip-triggers \ --default-character-set=$CHARACTER_SET \ --single-transaction \ --complete-insert \ --extended-insert \ --max_allowed_packet=$MAX_ALLOWED_PACKET \ "$SOURCE_DB_NAME" > "$temp_file" 2>/dev/null if [ $? -eq 0 ]; then # 添加字符集声明到文件头部 { echo "-- CCDI 数据库数据" echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" echo "-- 源数据库: ${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}" echo "-- 字符集: ${CHARACTER_SET}" echo "" echo "SET NAMES utf8mb4;" echo "SET CHARACTER SET utf8mb4;" echo "SET GLOBAL character_set_client=utf8mb4;" echo "SET GLOBAL character_set_connection=utf8mb4;" echo "SET GLOBAL character_set_results=utf8mb4;" echo "SET FOREIGN_KEY_CHECKS=0;" echo "" cat "$temp_file" echo "" echo "SET FOREIGN_KEY_CHECKS=1;" } > "$output_file" rm -f "$temp_file" log_info "数据导出成功: $output_file" log_info "文件大小: $(du -h "$output_file" | cut -f1)" else rm -f "$temp_file" log_error "数据导出失败" exit 1 fi } # 验证导出文件 verify_export() { log_info "验证导出文件..." local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" local data_file="${BACKUP_DIR}/${DATA_FILE}" # 检查文件是否存在 if [ ! -f "$structure_file" ]; then log_error "表结构文件不存在: $structure_file" exit 1 fi if [ ! -f "$data_file" ]; then log_error "数据文件不存在: $data_file" exit 1 fi # 检查字符集声明 if ! grep -q "SET NAMES utf8mb4" "$structure_file"; then log_error "表结构文件缺少字符集声明" exit 1 fi if ! grep -q "SET NAMES utf8mb4" "$data_file"; then log_error "数据文件缺少字符集声明" exit 1 fi log_info "导出文件验证通过" log_info "表结构文件: $structure_file ($(du -h "$structure_file" | cut -f1))" log_info "数据文件: $data_file ($(du -h "$data_file" | cut -f1))" } # 导入表结构 import_structure() { local env_type=$1 local db_host db_port db_user db_pass db_name case "$env_type" in production|prod) db_host="$PROD_DB_HOST" db_port="$PROD_DB_PORT" db_user="$PROD_DB_USER" db_pass="$PROD_DB_PASS" db_name="$PROD_DB_NAME" ;; test) db_host="$TEST_DB_HOST" db_port="$TEST_DB_PORT" db_user="$TEST_DB_USER" db_pass="$TEST_DB_PASS" db_name="$TEST_DB_NAME" ;; *) log_error "未知的环境类型: $env_type" exit 1 ;; esac log_info "导入表结构到 ${env_type} 环境: ${db_host}:${db_port}/${db_name}" local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" if [ ! -f "$structure_file" ]; then log_error "表结构文件不存在: $structure_file" log_info "请先执行导出: ./export_database.sh export" exit 1 fi # 导入表结构 mysql -h "$db_host" \ -P "$db_port" \ -u "$db_user" \ -p"$db_pass" \ --default-character-set=$CHARACTER_SET \ "$db_name" < "$structure_file" 2>/dev/null if [ $? -eq 0 ]; then log_info "表结构导入成功" else log_error "表结构导入失败" exit 1 fi } # 导入数据 import_data() { local env_type=$1 local db_host db_port db_user db_pass db_name case "$env_type" in production|prod) db_host="$PROD_DB_HOST" db_port="$PROD_DB_PORT" db_user="$PROD_DB_USER" db_pass="$PROD_DB_PASS" db_name="$PROD_DB_NAME" ;; test) db_host="$TEST_DB_HOST" db_port="$TEST_DB_PORT" db_user="$TEST_DB_USER" db_pass="$TEST_DB_PASS" db_name="$TEST_DB_NAME" ;; *) log_error "未知的环境类型: $env_type" exit 1 ;; esac log_info "导入数据到 ${env_type} 环境: ${db_host}:${db_port}/${db_name}" local data_file="${BACKUP_DIR}/${DATA_FILE}" if [ ! -f "$data_file" ]; then log_error "数据文件不存在: $data_file" log_info "请先执行导出: ./export_database.sh export" exit 1 fi # 导入数据 mysql -h "$db_host" \ -P "$db_port" \ -u "$db_user" \ -p"$db_pass" \ --default-character-set=$CHARACTER_SET \ "$db_name" < "$data_file" 2>/dev/null if [ $? -eq 0 ]; then log_info "数据导入成功" else log_error "数据导入失败" exit 1 fi } # 验证导入结果 verify_import() { local env_type=$1 local db_host db_port db_user db_pass db_name case "$env_type" in production|prod) db_host="$PROD_DB_HOST" db_port="$PROD_DB_PORT" db_user="$PROD_DB_USER" db_pass="$PROD_DB_PASS" db_name="$PROD_DB_NAME" ;; test) db_host="$TEST_DB_HOST" db_port="$TEST_DB_PORT" db_user="$TEST_DB_USER" db_pass="$TEST_DB_PASS" db_name="$TEST_DB_NAME" ;; *) log_error "未知的环境类型: $env_type" exit 1 ;; esac log_info "验证导入结果..." # 查询表数量 local table_count=$(mysql -h "$db_host" \ -P "$db_port" \ -u "$db_user" \ -p"$db_pass" \ --default-character-set=$CHARACTER_SET \ -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$db_name';" "$db_name" 2>/dev/null) log_info "目标数据库表数量: $table_count" # 查询关键表行数(示例:sys_user 表) local user_count=$(mysql -h "$db_host" \ -P "$db_port" \ -u "$db_user" \ -p"$db_pass" \ --default-character-set=$CHARACTER_SET \ -N -e "SELECT COUNT(*) FROM sys_user;" "$db_name" 2>/dev/null) log_info "sys_user 表数据行数: $user_count" # 检查数据库字符集 local db_charset=$(mysql -h "$db_host" \ -P "$db_port" \ -u "$db_user" \ -p"$db_pass" \ --default-character-set=$CHARACTER_SET \ -N -e "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.schemata WHERE schema_name='$db_name';" 2>/dev/null) log_info "数据库字符集: $db_charset" } # 导入数据库 import_database() { local env_type=$1 if [ -z "$env_type" ]; then log_error "请指定目标环境: production 或 test" log_info "用法: $0 import [production|test]" exit 1 fi log_info "========== 开始导入数据库到 ${env_type} 环境 ==========" check_config import_structure "$env_type" import_data "$env_type" verify_import "$env_type" log_info "========== 数据库导入完成 ==========" } # 导出数据库 export_database() { log_info "========== 开始导出数据库 ==========" check_config check_mysqldump create_backup_dir export_structure export_data verify_export log_info "========== 数据库导出完成 ==========" } # 使用帮助 show_usage() { echo "用法: $0 [options]" echo "" echo "命令:" echo " export 导出数据库" echo " import 导入数据库到指定环境" echo " help 显示帮助信息" echo "" echo "环境:" echo " production, prod 生产环境" echo " test 测试环境" echo "" echo "示例:" echo " $0 export # 导出数据库到 doc/database/backup/ 目录" echo " $0 import test # 导入数据库到测试环境" echo " $0 import prod # 导入数据库到生产环境" } # 主函数 main() { case "$1" in export) export_database ;; import) import_database "$2" ;; help|--help|-h) show_usage ;; *) log_error "未知命令: $1" show_usage exit 1 ;; esac } # 执行主函数 main "$@"