feat: 完成数据库迁移自动化工具
实现功能: - 创建自动化导出脚本 export_database.sh - 支持表结构和数据分离导出 - 添加 utf8mb4 字符集支持避免乱码 - 支持导入到生产和测试环境 - 创建配置文件模板和安全措施 - 添加详细的操作指南文档 文件说明: - db_config.conf.template: 配置文件模板 - export_database.sh: 自动化迁移脚本 - doc/database/backup/export_guide.md: 操作指南 - doc/database/backup/ccdi_structure.sql: 表结构(42个表) - doc/database/backup/ccdi_data.sql: 数据文件(5.7MB) 使用方法: 1. cp db_config.conf.template db_config.conf 2. 编辑 db_config.conf 填写数据库信息 3. ./export_database.sh export # 导出数据库 4. ./export_database.sh import test # 导入到测试环境 5. ./export_database.sh import prod # 导入到生产环境
This commit is contained in:
436
export_database.sh
Normal file
436
export_database.sh
Normal file
@@ -0,0 +1,436 @@
|
||||
#!/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 <command> [options]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " export 导出数据库"
|
||||
echo " import <env> 导入数据库到指定环境"
|
||||
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 "$@"
|
||||
Reference in New Issue
Block a user