feat: 完成数据库迁移系统开发
- 创建完整的迁移框架 (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%
This commit is contained in:
392
backend/scripts/init-production-db.sh
Executable file
392
backend/scripts/init-production-db.sh
Executable file
@ -0,0 +1,392 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 生产环境数据库初始化脚本
|
||||
# 用于全新的生产环境部署
|
||||
|
||||
set -e
|
||||
|
||||
# 默认配置
|
||||
CONFIG_FILE="etc/photographyapi-api.yaml"
|
||||
BACKUP_DIR="data/backups"
|
||||
LOG_FILE="init.log"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
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_banner() {
|
||||
cat << 'EOF'
|
||||
____ __ __ __
|
||||
/ __ \/ /_ ____ / /_____ ____ __________ _____/ /_
|
||||
/ /_/ / __ \/ __ \/ __/ __ \/ __ \/ ___/ __ \/ ___/ __ \
|
||||
/ ____/ / / / /_/ / /_/ /_/ / /_/ / / / /_/ / /__/ / / /
|
||||
/_/ /_/ /_/\____/\__/\____/\__, /_/ \__,_/\___/_/ /_/
|
||||
/____/
|
||||
|
||||
Production Database Initialization Script
|
||||
=========================================
|
||||
EOF
|
||||
}
|
||||
|
||||
# 检查环境
|
||||
check_environment() {
|
||||
log "检查生产环境..."
|
||||
|
||||
# 检查是否为 root 用户(生产环境建议)
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
warning "正在以 root 用户运行"
|
||||
fi
|
||||
|
||||
# 检查Go环境
|
||||
if ! command -v go &> /dev/null; then
|
||||
error "Go 未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local go_version=$(go version | cut -d' ' -f3)
|
||||
log "Go 版本: $go_version"
|
||||
|
||||
# 检查配置文件
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
error "配置文件不存在: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "环境检查通过"
|
||||
}
|
||||
|
||||
# 创建目录结构
|
||||
create_directories() {
|
||||
log "创建目录结构..."
|
||||
|
||||
local dirs=(
|
||||
"data"
|
||||
"data/backups"
|
||||
"uploads"
|
||||
"uploads/photos"
|
||||
"uploads/avatars"
|
||||
"uploads/thumbnails"
|
||||
"logs"
|
||||
"bin"
|
||||
)
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
mkdir -p "$dir"
|
||||
log "创建目录: $dir"
|
||||
else
|
||||
log "目录已存在: $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# 设置权限
|
||||
chmod 755 data uploads logs bin
|
||||
chmod 700 data/backups # 备份目录更严格的权限
|
||||
|
||||
success "目录结构创建完成"
|
||||
}
|
||||
|
||||
# 检查数据库状态
|
||||
check_database_status() {
|
||||
log "检查数据库状态..."
|
||||
|
||||
if [[ -f "data/photography.db" ]]; then
|
||||
warning "数据库文件已存在: data/photography.db"
|
||||
local db_size=$(du -h "data/photography.db" | cut -f1)
|
||||
log "数据库大小: $db_size"
|
||||
|
||||
echo -n "数据库已存在,是否继续初始化?这将覆盖现有数据 (y/N): "
|
||||
read -r confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
log "用户取消初始化"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 备份现有数据库
|
||||
local backup_name="photography_pre_init_$(date +%Y%m%d_%H%M%S).db"
|
||||
cp "data/photography.db" "data/backups/$backup_name"
|
||||
log "现有数据库已备份为: $backup_name"
|
||||
else
|
||||
log "数据库文件不存在,将创建新数据库"
|
||||
fi
|
||||
}
|
||||
|
||||
# 初始化数据库
|
||||
initialize_database() {
|
||||
log "初始化数据库..."
|
||||
|
||||
# 删除现有数据库(如果存在)
|
||||
if [[ -f "data/photography.db" ]]; then
|
||||
rm -f "data/photography.db"
|
||||
log "删除现有数据库文件"
|
||||
fi
|
||||
|
||||
# 运行迁移
|
||||
log "执行数据库迁移..."
|
||||
if go run cmd/migrate/main.go -f "$CONFIG_FILE" -c up; then
|
||||
success "数据库迁移完成"
|
||||
else
|
||||
error "数据库迁移失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证数据库
|
||||
if [[ -f "data/photography.db" ]]; then
|
||||
success "数据库创建成功"
|
||||
local db_size=$(du -h "data/photography.db" | cut -f1)
|
||||
log "数据库大小: $db_size"
|
||||
else
|
||||
error "数据库创建失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证数据库结构
|
||||
verify_database() {
|
||||
log "验证数据库结构..."
|
||||
|
||||
# 检查迁移状态
|
||||
log "检查迁移状态:"
|
||||
go run cmd/migrate/main.go -f "$CONFIG_FILE" -c status
|
||||
|
||||
# 检查表是否存在
|
||||
local tables=("user" "category" "photo" "schema_migrations")
|
||||
|
||||
for table in "${tables[@]}"; do
|
||||
if sqlite3 "data/photography.db" ".tables" | grep -q "$table"; then
|
||||
success "表 $table 存在"
|
||||
else
|
||||
error "表 $table 不存在"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查默认数据
|
||||
local user_count=$(sqlite3 "data/photography.db" "SELECT COUNT(*) FROM user;")
|
||||
local category_count=$(sqlite3 "data/photography.db" "SELECT COUNT(*) FROM category;")
|
||||
|
||||
log "默认用户数量: $user_count"
|
||||
log "默认分类数量: $category_count"
|
||||
|
||||
if [[ "$user_count" -gt 0 && "$category_count" -gt 0 ]]; then
|
||||
success "默认数据初始化成功"
|
||||
else
|
||||
warning "默认数据可能未正确初始化"
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建初始备份
|
||||
create_initial_backup() {
|
||||
log "创建初始备份..."
|
||||
|
||||
local backup_name="photography_initial_$(date +%Y%m%d_%H%M%S).db"
|
||||
local backup_path="data/backups/$backup_name"
|
||||
|
||||
cp "data/photography.db" "$backup_path"
|
||||
success "初始备份创建成功: $backup_name"
|
||||
|
||||
# 记录初始备份
|
||||
echo "$backup_path" > "data/.initial_backup"
|
||||
}
|
||||
|
||||
# 生成配置摘要
|
||||
generate_config_summary() {
|
||||
log "生成配置摘要..."
|
||||
|
||||
cat > "DEPLOYMENT_INFO.md" << EOF
|
||||
# Photography Backend Deployment Information
|
||||
|
||||
## 部署时间
|
||||
- 初始化时间: $(date '+%Y-%m-%d %H:%M:%S %Z')
|
||||
- 操作系统: $(uname -s) $(uname -r)
|
||||
- 主机名: $(hostname)
|
||||
|
||||
## 数据库信息
|
||||
- 数据库类型: SQLite
|
||||
- 数据库文件: data/photography.db
|
||||
- 初始备份: $(cat data/.initial_backup 2>/dev/null || echo "未找到")
|
||||
|
||||
## 目录结构
|
||||
\`\`\`
|
||||
$(tree -d -L 2 2>/dev/null || find . -type d -maxdepth 2 | head -20)
|
||||
\`\`\`
|
||||
|
||||
## 迁移状态
|
||||
\`\`\`
|
||||
$(go run cmd/migrate/main.go -f "$CONFIG_FILE" -c status 2>/dev/null || echo "无法获取迁移状态")
|
||||
\`\`\`
|
||||
|
||||
## 下一步操作
|
||||
1. 启动应用服务: \`make quick\`
|
||||
2. 检查服务状态: \`make status\`
|
||||
3. 查看迁移状态: \`make migrate-status\`
|
||||
4. 创建备份: \`make db-backup\`
|
||||
|
||||
## 管理命令
|
||||
- 查看帮助: \`make help\`
|
||||
- 迁移数据库: \`./scripts/production-migrate.sh migrate\`
|
||||
- 回滚迁移: \`./scripts/production-migrate.sh rollback 1\`
|
||||
- 系统检查: \`./scripts/production-migrate.sh check\`
|
||||
|
||||
## 注意事项
|
||||
- 定期备份数据库
|
||||
- 监控日志文件
|
||||
- 更新时先测试迁移
|
||||
- 保持配置文件安全
|
||||
EOF
|
||||
|
||||
success "配置摘要已生成: DEPLOYMENT_INFO.md"
|
||||
}
|
||||
|
||||
# 设置服务文件(可选)
|
||||
setup_systemd_service() {
|
||||
if [[ ! -f "/etc/systemd/system" ]]; then
|
||||
log "Systemd 不可用,跳过服务配置"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "配置 Systemd 服务..."
|
||||
|
||||
local service_file="/etc/systemd/system/photography-api.service"
|
||||
local current_dir=$(pwd)
|
||||
local user=$(whoami)
|
||||
|
||||
cat > "$service_file" << EOF
|
||||
[Unit]
|
||||
Description=Photography API Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$user
|
||||
WorkingDirectory=$current_dir
|
||||
ExecStart=$current_dir/bin/photography-api -f $current_dir/$CONFIG_FILE
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# 环境变量
|
||||
Environment=GO111MODULE=on
|
||||
Environment=GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
# 日志
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
success "Systemd 服务配置完成"
|
||||
log "使用以下命令管理服务:"
|
||||
log " sudo systemctl start photography-api"
|
||||
log " sudo systemctl enable photography-api"
|
||||
log " sudo systemctl status photography-api"
|
||||
}
|
||||
|
||||
# 最终检查
|
||||
final_check() {
|
||||
log "执行最终检查..."
|
||||
|
||||
# 检查应用是否可以构建
|
||||
if make build; then
|
||||
success "应用构建成功"
|
||||
else
|
||||
error "应用构建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查配置是否正确
|
||||
if go run cmd/api/main.go -f "$CONFIG_FILE" -test.timeout=1s 2>/dev/null; then
|
||||
success "配置文件验证通过"
|
||||
else
|
||||
warning "配置文件可能有问题,请检查"
|
||||
fi
|
||||
|
||||
success "最终检查完成"
|
||||
}
|
||||
|
||||
# 显示完成信息
|
||||
show_completion() {
|
||||
success "数据库初始化完成!"
|
||||
|
||||
cat << EOF
|
||||
|
||||
🎉 初始化成功!
|
||||
|
||||
接下来的步骤:
|
||||
1. 启动应用: make quick
|
||||
2. 访问应用: http://localhost:8080
|
||||
3. 查看状态: make status
|
||||
4. 查看帮助: make help
|
||||
|
||||
管理脚本:
|
||||
- 迁移管理: ./scripts/production-migrate.sh
|
||||
- 查看部署信息: cat DEPLOYMENT_INFO.md
|
||||
|
||||
默认管理员账户:
|
||||
- 用户名: admin
|
||||
- 密码: admin123
|
||||
- 邮箱: admin@example.com
|
||||
|
||||
⚠️ 重要提醒:
|
||||
- 请立即修改默认管理员密码
|
||||
- 定期备份数据库
|
||||
- 监控应用日志
|
||||
- 保持系统更新
|
||||
|
||||
📋 详细信息请查看: DEPLOYMENT_INFO.md
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
show_banner
|
||||
|
||||
log "开始生产环境数据库初始化..."
|
||||
|
||||
check_environment
|
||||
create_directories
|
||||
check_database_status
|
||||
initialize_database
|
||||
verify_database
|
||||
create_initial_backup
|
||||
generate_config_summary
|
||||
final_check
|
||||
|
||||
# 询问是否配置 Systemd 服务
|
||||
echo -n "是否配置 Systemd 服务? (y/N): "
|
||||
read -r setup_service
|
||||
if [[ "$setup_service" == "y" || "$setup_service" == "Y" ]]; then
|
||||
setup_systemd_service
|
||||
fi
|
||||
|
||||
show_completion
|
||||
|
||||
log "初始化脚本执行完成"
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
main "$@"
|
||||
416
backend/scripts/production-migrate.sh
Executable file
416
backend/scripts/production-migrate.sh
Executable file
@ -0,0 +1,416 @@
|
||||
#!/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 "$@"
|
||||
343
backend/scripts/test-migration.sh
Executable file
343
backend/scripts/test-migration.sh
Executable file
@ -0,0 +1,343 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 迁移系统测试脚本
|
||||
# 用于验证迁移系统的正确性
|
||||
|
||||
set -e
|
||||
|
||||
# 配置
|
||||
TEST_DB="test_migration.db"
|
||||
CONFIG_FILE="etc/photographyapi-api.yaml"
|
||||
TEMP_CONFIG="test_migration_config.yaml"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${BLUE}[TEST]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
log "清理测试环境..."
|
||||
rm -f "$TEST_DB" "$TEMP_CONFIG"
|
||||
rm -f migration_test.log
|
||||
}
|
||||
|
||||
# 创建测试配置
|
||||
create_test_config() {
|
||||
log "创建测试配置..."
|
||||
|
||||
cat > "$TEMP_CONFIG" << EOF
|
||||
Name: photography-api-test
|
||||
Host: 0.0.0.0
|
||||
Port: 8081
|
||||
|
||||
database:
|
||||
driver: sqlite
|
||||
file_path: ./$TEST_DB
|
||||
|
||||
auth:
|
||||
access_secret: test-secret
|
||||
access_expire: 3600
|
||||
|
||||
file_upload:
|
||||
max_size: 10485760
|
||||
upload_dir: ./test_uploads
|
||||
allowed_types:
|
||||
- image/jpeg
|
||||
- image/png
|
||||
EOF
|
||||
}
|
||||
|
||||
# 测试数据库连接
|
||||
test_database_connection() {
|
||||
log "测试数据库连接..."
|
||||
|
||||
# 创建简单的测试数据库
|
||||
sqlite3 "$TEST_DB" "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY);"
|
||||
|
||||
if [[ -f "$TEST_DB" ]]; then
|
||||
success "数据库连接正常"
|
||||
rm -f "$TEST_DB" # 清理测试数据库
|
||||
else
|
||||
error "数据库连接失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试迁移状态
|
||||
test_migration_status() {
|
||||
log "测试迁移状态..."
|
||||
|
||||
if go run cmd/migrate/main.go -f "$TEMP_CONFIG" -c status; then
|
||||
success "迁移状态命令正常"
|
||||
else
|
||||
error "迁移状态命令失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试迁移执行
|
||||
test_migration_up() {
|
||||
log "测试迁移执行..."
|
||||
|
||||
if go run cmd/migrate/main.go -f "$TEMP_CONFIG" -c up; then
|
||||
success "迁移执行成功"
|
||||
else
|
||||
error "迁移执行失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证表是否创建
|
||||
if sqlite3 "$TEST_DB" ".tables" | grep -q "user"; then
|
||||
success "用户表创建成功"
|
||||
else
|
||||
error "用户表创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if sqlite3 "$TEST_DB" ".tables" | grep -q "schema_migrations"; then
|
||||
success "迁移记录表创建成功"
|
||||
else
|
||||
error "迁移记录表创建失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试数据插入
|
||||
test_data_insertion() {
|
||||
log "测试数据插入..."
|
||||
|
||||
# 检查默认用户是否存在
|
||||
local user_count=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM user;")
|
||||
if [[ "$user_count" -gt 0 ]]; then
|
||||
success "默认用户数据插入成功 ($user_count 个用户)"
|
||||
else
|
||||
warning "没有找到默认用户数据"
|
||||
fi
|
||||
|
||||
# 检查默认分类是否存在
|
||||
local category_count=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM category;")
|
||||
if [[ "$category_count" -gt 0 ]]; then
|
||||
success "默认分类数据插入成功 ($category_count 个分类)"
|
||||
else
|
||||
warning "没有找到默认分类数据"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试迁移回滚
|
||||
test_migration_down() {
|
||||
log "测试迁移回滚..."
|
||||
|
||||
# 获取当前迁移数量
|
||||
local applied_count=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM schema_migrations WHERE applied = 1;")
|
||||
log "当前已应用迁移数量: $applied_count"
|
||||
|
||||
if [[ "$applied_count" -gt 0 ]]; then
|
||||
# 回滚一步
|
||||
if go run cmd/migrate/main.go -f "$TEMP_CONFIG" -c down -s 1; then
|
||||
success "迁移回滚成功"
|
||||
|
||||
# 验证回滚后的状态
|
||||
local new_applied_count=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM schema_migrations WHERE applied = 1;")
|
||||
log "回滚后已应用迁移数量: $new_applied_count"
|
||||
|
||||
if [[ "$new_applied_count" -eq $((applied_count - 1)) ]]; then
|
||||
success "回滚状态验证成功"
|
||||
else
|
||||
error "回滚状态验证失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
error "迁移回滚失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
warning "没有可回滚的迁移"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试迁移版本
|
||||
test_migration_version() {
|
||||
log "测试迁移版本..."
|
||||
|
||||
if go run cmd/migrate/main.go -f "$TEMP_CONFIG" -c version; then
|
||||
success "迁移版本命令正常"
|
||||
else
|
||||
error "迁移版本命令失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试 Makefile 集成
|
||||
test_makefile_integration() {
|
||||
log "测试 Makefile 集成..."
|
||||
|
||||
# 备份原配置
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
cp "$CONFIG_FILE" "${CONFIG_FILE}.backup"
|
||||
fi
|
||||
|
||||
# 使用测试配置
|
||||
cp "$TEMP_CONFIG" "$CONFIG_FILE"
|
||||
|
||||
# 测试 Makefile 命令
|
||||
if make migrate-status; then
|
||||
success "Makefile 迁移状态命令正常"
|
||||
else
|
||||
error "Makefile 迁移状态命令失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 恢复原配置
|
||||
if [[ -f "${CONFIG_FILE}.backup" ]]; then
|
||||
mv "${CONFIG_FILE}.backup" "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试生产脚本
|
||||
test_production_script() {
|
||||
log "测试生产脚本..."
|
||||
|
||||
# 测试状态检查
|
||||
if ./scripts/production-migrate.sh -c "$TEMP_CONFIG" status; then
|
||||
success "生产脚本状态检查正常"
|
||||
else
|
||||
error "生产脚本状态检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试系统检查
|
||||
if ./scripts/production-migrate.sh -c "$TEMP_CONFIG" check; then
|
||||
success "生产脚本系统检查正常"
|
||||
else
|
||||
warning "生产脚本系统检查有警告"
|
||||
fi
|
||||
}
|
||||
|
||||
# 性能测试
|
||||
performance_test() {
|
||||
log "执行性能测试..."
|
||||
|
||||
local start_time=$(date +%s)
|
||||
|
||||
# 重新运行迁移
|
||||
go run cmd/migrate/main.go -f "$TEMP_CONFIG" -c up > /dev/null 2>&1
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
log "迁移执行时间: ${duration}秒"
|
||||
|
||||
if [[ $duration -lt 10 ]]; then
|
||||
success "迁移性能正常"
|
||||
else
|
||||
warning "迁移执行时间较长: ${duration}秒"
|
||||
fi
|
||||
}
|
||||
|
||||
# 数据完整性测试
|
||||
data_integrity_test() {
|
||||
log "测试数据完整性..."
|
||||
|
||||
# 检查外键约束
|
||||
sqlite3 "$TEST_DB" "PRAGMA foreign_keys = ON;"
|
||||
|
||||
# 尝试插入测试数据
|
||||
if sqlite3 "$TEST_DB" "INSERT INTO photo (title, description, file_path, user_id, category_id) VALUES ('test', 'test', '/test.jpg', 1, 1);"; then
|
||||
success "外键约束正常"
|
||||
|
||||
# 清理测试数据
|
||||
sqlite3 "$TEST_DB" "DELETE FROM photo WHERE title = 'test';"
|
||||
else
|
||||
error "外键约束失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查索引
|
||||
local index_count=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%';")
|
||||
log "自定义索引数量: $index_count"
|
||||
|
||||
if [[ "$index_count" -gt 0 ]]; then
|
||||
success "索引创建正常"
|
||||
else
|
||||
warning "没有找到自定义索引"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主测试函数
|
||||
main() {
|
||||
log "开始迁移系统测试..."
|
||||
|
||||
# 清理之前的测试
|
||||
cleanup
|
||||
|
||||
# 创建测试环境
|
||||
create_test_config
|
||||
|
||||
# 执行测试
|
||||
test_database_connection
|
||||
test_migration_status
|
||||
test_migration_up
|
||||
test_data_insertion
|
||||
test_migration_version
|
||||
test_migration_down
|
||||
test_makefile_integration
|
||||
test_production_script
|
||||
performance_test
|
||||
data_integrity_test
|
||||
|
||||
# 清理测试环境
|
||||
cleanup
|
||||
|
||||
success "所有测试通过!"
|
||||
|
||||
cat << EOF
|
||||
|
||||
🎉 迁移系统测试完成!
|
||||
|
||||
测试结果:
|
||||
✅ 数据库连接正常
|
||||
✅ 迁移状态查询正常
|
||||
✅ 迁移执行成功
|
||||
✅ 数据插入正常
|
||||
✅ 迁移回滚成功
|
||||
✅ 版本管理正常
|
||||
✅ Makefile 集成正常
|
||||
✅ 生产脚本正常
|
||||
✅ 性能测试通过
|
||||
✅ 数据完整性验证通过
|
||||
|
||||
迁移系统已就绪,可以安全使用!
|
||||
|
||||
下一步:
|
||||
1. 在开发环境运行: make migrate-up
|
||||
2. 检查迁移状态: make migrate-status
|
||||
3. 阅读文档: docs/DATABASE_MIGRATION.md
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 错误处理
|
||||
trap cleanup EXIT
|
||||
|
||||
# 运行测试
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user