- 创建完整的迁移框架 (pkg/migration/) - 版本管理系统,时间戳版本号 (YYYYMMDD_HHMMSS) - 事务安全的上下迁移机制 (Up/Down) - 迁移状态跟踪和记录 (migration_records 表) - 命令行迁移工具 (cmd/migrate/main.go) - 生产环境迁移脚本 (scripts/production-migrate.sh) - 生产环境初始化脚本 (scripts/init-production-db.sh) - 迁移测试脚本 (scripts/test-migration.sh) - Makefile 集成 (migrate-up, migrate-down, migrate-status) - 5个预定义迁移 (基础表、默认数据、元数据、收藏、用户资料) - 自动备份机制、预览模式、详细日志 - 完整文档 (docs/DATABASE_MIGRATION.md) 任务13完成,项目完成率达到42.5%
416 lines
9.6 KiB
Bash
Executable File
416 lines
9.6 KiB
Bash
Executable File
#!/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 "$@" |