添加 lib/common.sh
This commit is contained in:
367
lib/common.sh
Normal file
367
lib/common.sh
Normal file
@@ -0,0 +1,367 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# MySQL 8.0.24 备份恢复工具 - 公共函数库
|
||||
# ============================================================================
|
||||
# 说明: 此文件包含所有脚本共用的函数
|
||||
# ============================================================================
|
||||
|
||||
# 颜色定义 (用于终端输出)
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# ============================================================================
|
||||
# 日志函数
|
||||
# ============================================================================
|
||||
|
||||
# 获取当前时间戳
|
||||
get_timestamp() {
|
||||
date +"%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
# 打印信息日志
|
||||
log_info() {
|
||||
local message="$1"
|
||||
echo -e "${GREEN}[INFO]${NC} [$(get_timestamp)] $message"
|
||||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||||
echo "[INFO] [$(get_timestamp)] $message" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 打印警告日志
|
||||
log_warn() {
|
||||
local message="$1"
|
||||
echo -e "${YELLOW}[WARN]${NC} [$(get_timestamp)] $message" >&2
|
||||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||||
echo "[WARN] [$(get_timestamp)] $message" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 打印错误日志
|
||||
log_error() {
|
||||
local message="$1"
|
||||
echo -e "${RED}[ERROR]${NC} [$(get_timestamp)] $message" >&2
|
||||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||||
echo "[ERROR] [$(get_timestamp)] $message" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 打印调试日志 (仅在 VERBOSE=true 时输出)
|
||||
log_debug() {
|
||||
local message="$1"
|
||||
if [[ "${VERBOSE:-false}" == "true" ]]; then
|
||||
echo -e "${BLUE}[DEBUG]${NC} [$(get_timestamp)] $message"
|
||||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||||
echo "[DEBUG] [$(get_timestamp)] $message" >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 错误处理函数
|
||||
# ============================================================================
|
||||
|
||||
# 错误退出函数
|
||||
die() {
|
||||
local message="$1"
|
||||
local exit_code="${2:-1}"
|
||||
log_error "$message"
|
||||
log_error "脚本异常退出,退出码: $exit_code"
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
# 设置错误处理 trap
|
||||
setup_error_trap() {
|
||||
# 捕获错误并输出详细信息
|
||||
trap 'error_handler $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]:-})' ERR
|
||||
}
|
||||
|
||||
# 错误处理器
|
||||
error_handler() {
|
||||
local exit_code=$1
|
||||
local line_no=$2
|
||||
local bash_lineno=$3
|
||||
local last_command=$4
|
||||
local func_trace=$5
|
||||
|
||||
log_error "============================================================"
|
||||
log_error "脚本执行出错!"
|
||||
log_error "============================================================"
|
||||
log_error "退出码: $exit_code"
|
||||
log_error "错误行号: $line_no"
|
||||
log_error "失败命令: $last_command"
|
||||
log_error "函数调用栈: $func_trace"
|
||||
log_error "============================================================"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 验证函数
|
||||
# ============================================================================
|
||||
|
||||
# 检查是否以 root 用户运行
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
die "此脚本需要以 root 权限运行,请使用 sudo"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查必要的命令是否存在
|
||||
check_commands() {
|
||||
local commands=("$@")
|
||||
local missing=()
|
||||
|
||||
for cmd in "${commands[@]}"; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
missing+=("$cmd")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
die "缺少必要的命令: ${missing[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查 MySQL 连接
|
||||
check_mysql_connection() {
|
||||
log_info "检查 MySQL 连接..."
|
||||
|
||||
local mysql_cmd="$MYSQL_PATH"
|
||||
local connect_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}"
|
||||
|
||||
if [[ -n "$MYSQL_PASSWORD" ]]; then
|
||||
connect_args="$connect_args -p${MYSQL_PASSWORD}"
|
||||
fi
|
||||
|
||||
if ! $mysql_cmd $connect_args -e "SELECT 1;" &> /dev/null; then
|
||||
die "无法连接到 MySQL 服务器 (${MYSQL_HOST}:${MYSQL_PORT})"
|
||||
fi
|
||||
|
||||
log_info "MySQL 连接成功"
|
||||
}
|
||||
|
||||
# 检查目录是否存在,不存在则创建
|
||||
ensure_dir() {
|
||||
local dir="$1"
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
log_info "创建目录: $dir"
|
||||
mkdir -p "$dir" || die "无法创建目录: $dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查磁盘空间
|
||||
check_disk_space() {
|
||||
local target_dir="$1"
|
||||
local required_mb="${2:-1024}" # 默认需要 1GB
|
||||
|
||||
# 获取目标目录所在分区的可用空间 (MB)
|
||||
local available_mb=$(df -m "$target_dir" | awk 'NR==2 {print $4}')
|
||||
|
||||
if [[ $available_mb -lt $required_mb ]]; then
|
||||
die "磁盘空间不足! 需要: ${required_mb}MB, 可用: ${available_mb}MB"
|
||||
fi
|
||||
|
||||
log_debug "磁盘空间检查通过: 需要 ${required_mb}MB, 可用 ${available_mb}MB"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MySQL 操作函数
|
||||
# ============================================================================
|
||||
|
||||
# 执行 MySQL 命令
|
||||
execute_mysql() {
|
||||
local sql="$1"
|
||||
local mysql_cmd="$MYSQL_PATH"
|
||||
local connect_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}"
|
||||
|
||||
if [[ -n "$MYSQL_PASSWORD" ]]; then
|
||||
connect_args="$connect_args -p${MYSQL_PASSWORD}"
|
||||
fi
|
||||
|
||||
$mysql_cmd $connect_args -N -e "$sql" 2>&1
|
||||
}
|
||||
|
||||
# 获取当前 binlog 位置
|
||||
get_binlog_position() {
|
||||
local result=$(execute_mysql "SHOW MASTER STATUS\G")
|
||||
|
||||
local binlog_file=$(echo "$result" | grep "File:" | awk '{print $2}')
|
||||
local binlog_pos=$(echo "$result" | grep "Position:" | awk '{print $2}')
|
||||
|
||||
if [[ -z "$binlog_file" || -z "$binlog_pos" ]]; then
|
||||
die "无法获取 binlog 位置,请确保 MySQL 已启用 binlog"
|
||||
fi
|
||||
|
||||
echo "${binlog_file}:${binlog_pos}"
|
||||
}
|
||||
|
||||
# 获取所有数据库列表
|
||||
get_databases() {
|
||||
local exclude_regex=""
|
||||
|
||||
# 构建排除正则表达式
|
||||
for db in $EXCLUDE_DATABASES; do
|
||||
if [[ -n "$exclude_regex" ]]; then
|
||||
exclude_regex="${exclude_regex}|"
|
||||
fi
|
||||
exclude_regex="${exclude_regex}^${db}$"
|
||||
done
|
||||
|
||||
# 获取数据库列表
|
||||
local databases=$(execute_mysql "SHOW DATABASES;")
|
||||
|
||||
# 过滤排除的数据库
|
||||
if [[ -n "$exclude_regex" ]]; then
|
||||
databases=$(echo "$databases" | grep -vE "$exclude_regex")
|
||||
fi
|
||||
|
||||
echo "$databases"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 文件操作函数
|
||||
# ============================================================================
|
||||
|
||||
# 压缩文件
|
||||
compress_file() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
log_info "压缩文件: $input_file -> $output_file"
|
||||
|
||||
case "$COMPRESS_TOOL" in
|
||||
gzip)
|
||||
gzip -c "$input_file" > "$output_file" || die "压缩失败"
|
||||
;;
|
||||
pigz)
|
||||
pigz -p "$COMPRESS_THREADS" -c "$input_file" > "$output_file" || die "压缩失败"
|
||||
;;
|
||||
lz4)
|
||||
lz4 -c "$input_file" > "$output_file" || die "压缩失败"
|
||||
;;
|
||||
*)
|
||||
die "不支持的压缩工具: $COMPRESS_TOOL"
|
||||
;;
|
||||
esac
|
||||
|
||||
log_info "压缩完成"
|
||||
}
|
||||
|
||||
# 解压文件
|
||||
decompress_file() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
log_info "解压文件: $input_file -> $output_file"
|
||||
|
||||
case "$input_file" in
|
||||
*.gz)
|
||||
gunzip -c "$input_file" > "$output_file" || die "解压失败"
|
||||
;;
|
||||
*.lz4)
|
||||
lz4 -d -c "$input_file" > "$output_file" || die "解压失败"
|
||||
;;
|
||||
*)
|
||||
# 假设是未压缩文件,直接复制
|
||||
cp "$input_file" "$output_file" || die "复制失败"
|
||||
;;
|
||||
esac
|
||||
|
||||
log_info "解压完成"
|
||||
}
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_old_backups() {
|
||||
local backup_dir="$1"
|
||||
local retention_days="$2"
|
||||
|
||||
log_info "清理 $retention_days 天前的备份: $backup_dir"
|
||||
|
||||
local count=$(find "$backup_dir" -maxdepth 1 -type d -mtime +$retention_days 2>/dev/null | wc -l)
|
||||
|
||||
if [[ $count -gt 0 ]]; then
|
||||
find "$backup_dir" -maxdepth 1 -type d -mtime +$retention_days -exec rm -rf {} \;
|
||||
log_info "已清理 $count 个过期备份目录"
|
||||
else
|
||||
log_info "没有需要清理的过期备份"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 通知函数
|
||||
# ============================================================================
|
||||
|
||||
# 发送通知
|
||||
send_notification() {
|
||||
local subject="$1"
|
||||
local message="$2"
|
||||
|
||||
if [[ "${ENABLE_EMAIL_NOTIFICATION:-false}" == "true" && -n "${NOTIFICATION_EMAIL:-}" ]]; then
|
||||
log_info "发送邮件通知到: $NOTIFICATION_EMAIL"
|
||||
echo "$message" | mail -s "$subject" "$NOTIFICATION_EMAIL" || log_warn "邮件发送失败"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 锁文件函数 (防止并发执行)
|
||||
# ============================================================================
|
||||
|
||||
# 获取锁
|
||||
acquire_lock() {
|
||||
local lock_file="$1"
|
||||
local lock_name="${2:-backup}"
|
||||
|
||||
if [[ -f "$lock_file" ]]; then
|
||||
local pid=$(cat "$lock_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
die "${lock_name} 进程已在运行 (PID: $pid)"
|
||||
else
|
||||
log_warn "发现过期的锁文件,正在清理..."
|
||||
rm -f "$lock_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo $$ > "$lock_file" || die "无法创建锁文件"
|
||||
log_debug "已获取锁: $lock_file (PID: $$)"
|
||||
}
|
||||
|
||||
# 释放锁
|
||||
release_lock() {
|
||||
local lock_file="$1"
|
||||
|
||||
if [[ -f "$lock_file" ]]; then
|
||||
rm -f "$lock_file"
|
||||
log_debug "已释放锁: $lock_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 时间计算函数
|
||||
# ============================================================================
|
||||
|
||||
# 计算执行时间
|
||||
calculate_duration() {
|
||||
local start_time=$1
|
||||
local end_time=$2
|
||||
|
||||
local duration=$((end_time - start_time))
|
||||
local hours=$((duration / 3600))
|
||||
local minutes=$(((duration % 3600) / 60))
|
||||
local seconds=$((duration % 60))
|
||||
|
||||
printf "%02d:%02d:%02d" $hours $minutes $seconds
|
||||
}
|
||||
|
||||
# 获取文件大小 (人类可读格式)
|
||||
get_file_size() {
|
||||
local file="$1"
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
ls -lh "$file" | awk '{print $5}'
|
||||
elif [[ -d "$file" ]]; then
|
||||
du -sh "$file" | awk '{print $1}'
|
||||
else
|
||||
echo "N/A"
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user