#!/bin/bash # 生产环境数据库迁移脚本 # 使用方法: ./scripts/production-migrate.sh [options] set -e # 遇到错误立即退出 # 默认配置 CONFIG_FILE="etc/photographyapi-api.yaml" BACKUP_DIR="data/backups" LOG_FILE="migration.log" DRY_RUN=false VERBOSE=false FORCE=false # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 日志函数 log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" } error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" } # 显示帮助 show_help() { cat << EOF 生产环境数据库迁移脚本 使用方法: $0 [选项] [命令] 选项: -c, --config FILE 配置文件路径 (默认: etc/photographyapi-api.yaml) -b, --backup-dir DIR 备份目录 (默认: data/backups) -l, --log-file FILE 日志文件 (默认: migration.log) -d, --dry-run 预览模式,不执行实际操作 -v, --verbose 详细输出 -f, --force 强制执行,跳过确认 -h, --help 显示帮助信息 命令: status 显示迁移状态 migrate 执行待处理的迁移 rollback STEPS 回滚指定步数的迁移 backup 创建数据库备份 restore BACKUP 恢复数据库备份 check 检查系统状态 示例: $0 status $0 migrate $0 rollback 1 $0 backup $0 restore photography_20250711_120000.db $0 -d migrate # 预览模式 注意: - 生产环境操作前会自动创建备份 - 建议先使用 -d 选项进行预览 - 所有操作都会记录到日志文件 EOF } # 解析命令行参数 parse_args() { while [[ $# -gt 0 ]]; do case $1 in -c|--config) CONFIG_FILE="$2" shift 2 ;; -b|--backup-dir) BACKUP_DIR="$2" shift 2 ;; -l|--log-file) LOG_FILE="$2" shift 2 ;; -d|--dry-run) DRY_RUN=true shift ;; -v|--verbose) VERBOSE=true shift ;; -f|--force) FORCE=true shift ;; -h|--help) show_help exit 0 ;; -*) error "未知选项: $1" exit 1 ;; *) COMMAND="$1" shift break ;; esac done # 保存剩余参数 ARGS=("$@") } # 检查依赖 check_dependencies() { log "检查系统依赖..." # 检查 Go if ! command -v go &> /dev/null; then error "Go 未安装或不在 PATH 中" exit 1 fi # 检查配置文件 if [[ ! -f "$CONFIG_FILE" ]]; then error "配置文件不存在: $CONFIG_FILE" exit 1 fi # 检查迁移工具 if [[ ! -f "cmd/migrate/main.go" ]]; then error "迁移工具不存在: cmd/migrate/main.go" exit 1 fi # 创建必要目录 mkdir -p "$BACKUP_DIR" success "依赖检查通过" } # 创建备份 create_backup() { log "创建数据库备份..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将创建备份到: $BACKUP_DIR" return 0 fi local backup_name="photography_$(date +%Y%m%d_%H%M%S).db" local backup_path="$BACKUP_DIR/$backup_name" if [[ -f "data/photography.db" ]]; then cp "data/photography.db" "$backup_path" success "备份创建成功: $backup_path" echo "$backup_path" > ".last_backup" else warning "数据库文件不存在,跳过备份" fi } # 检查迁移状态 check_migration_status() { log "检查迁移状态..." if [[ "$VERBOSE" == true ]]; then go run cmd/migrate/main.go -f "$CONFIG_FILE" -c status else go run cmd/migrate/main.go -f "$CONFIG_FILE" -c status | grep -E "(Applied|Pending)" || true fi } # 获取待处理迁移数量 get_pending_count() { go run cmd/migrate/main.go -f "$CONFIG_FILE" -c status | grep -c "Pending" || echo "0" } # 执行迁移 execute_migration() { log "执行数据库迁移..." local pending_count=$(get_pending_count) if [[ "$pending_count" -eq 0 ]]; then success "没有待处理的迁移" return 0 fi log "发现 $pending_count 个待处理迁移" if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将执行 $pending_count 个迁移" check_migration_status return 0 fi # 确认执行 if [[ "$FORCE" != true ]]; then warning "即将执行 $pending_count 个数据库迁移" echo -n "确认继续? (y/N): " read -r confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then log "用户取消操作" exit 0 fi fi # 创建备份 create_backup # 执行迁移 log "开始执行迁移..." if go run cmd/migrate/main.go -f "$CONFIG_FILE" -c up; then success "迁移执行成功" else error "迁移执行失败" # 提供回滚选项 if [[ -f ".last_backup" ]]; then local last_backup=$(cat ".last_backup") warning "可以使用以下命令回滚到备份:" echo " $0 restore $(basename "$last_backup")" fi exit 1 fi } # 执行回滚 execute_rollback() { local steps="${ARGS[0]}" if [[ -z "$steps" ]]; then error "请指定回滚步数" exit 1 fi if ! [[ "$steps" =~ ^[0-9]+$ ]]; then error "回滚步数必须是正整数" exit 1 fi log "准备回滚 $steps 步迁移..." if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将回滚 $steps 步迁移" return 0 fi # 确认回滚 if [[ "$FORCE" != true ]]; then warning "即将回滚 $steps 步数据库迁移" echo -n "确认继续? (y/N): " read -r confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then log "用户取消操作" exit 0 fi fi # 创建备份 create_backup # 执行回滚 log "开始执行回滚..." if go run cmd/migrate/main.go -f "$CONFIG_FILE" -c down -s "$steps"; then success "回滚执行成功" else error "回滚执行失败" exit 1 fi } # 恢复备份 restore_backup() { local backup_file="${ARGS[0]}" if [[ -z "$backup_file" ]]; then error "请指定备份文件名" echo "可用备份:" ls -la "$BACKUP_DIR"/ || true exit 1 fi local backup_path="$BACKUP_DIR/$backup_file" if [[ ! -f "$backup_path" ]]; then error "备份文件不存在: $backup_path" exit 1 fi log "准备恢复备份: $backup_file" if [[ "$DRY_RUN" == true ]]; then log "[DRY RUN] 将恢复备份: $backup_path" return 0 fi # 确认恢复 if [[ "$FORCE" != true ]]; then warning "即将恢复数据库备份,当前数据将被覆盖" echo -n "确认继续? (y/N): " read -r confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then log "用户取消操作" exit 0 fi fi # 执行恢复 log "开始恢复备份..." if cp "$backup_path" "data/photography.db"; then success "备份恢复成功" else error "备份恢复失败" exit 1 fi } # 系统检查 system_check() { log "执行系统检查..." # 检查配置文件 if [[ -f "$CONFIG_FILE" ]]; then success "配置文件存在: $CONFIG_FILE" else error "配置文件不存在: $CONFIG_FILE" fi # 检查数据库文件 if [[ -f "data/photography.db" ]]; then success "数据库文件存在: data/photography.db" local db_size=$(du -h "data/photography.db" | cut -f1) log "数据库大小: $db_size" else warning "数据库文件不存在: data/photography.db" fi # 检查备份目录 if [[ -d "$BACKUP_DIR" ]]; then success "备份目录存在: $BACKUP_DIR" local backup_count=$(ls -1 "$BACKUP_DIR"/ | wc -l) log "备份数量: $backup_count" else warning "备份目录不存在: $BACKUP_DIR" fi # 检查迁移状态 check_migration_status } # 主函数 main() { parse_args "$@" # 初始化日志 log "开始执行生产环境数据库迁移脚本" log "配置文件: $CONFIG_FILE" log "备份目录: $BACKUP_DIR" log "日志文件: $LOG_FILE" if [[ "$DRY_RUN" == true ]]; then log "运行模式: 预览 (DRY RUN)" fi # 检查依赖 check_dependencies # 执行命令 case "${COMMAND:-status}" in status) check_migration_status ;; migrate) execute_migration ;; rollback) execute_rollback ;; backup) create_backup ;; restore) restore_backup ;; check) system_check ;; *) error "未知命令: $COMMAND" show_help exit 1 ;; esac log "脚本执行完成" } # 运行主函数 main "$@"