Files
go-skeleton/cmd/migrate/main.go
T

209 lines
6.2 KiB
Go

package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"go.uber.org/zap"
"skeleton/config"
"skeleton/database"
"skeleton/middlewares"
)
func main() {
var (
env = flag.String("env", "", "Atlas 环境,默认根据应用环境和数据库驱动推断")
action = flag.String("action", "", "操作类型: status, diff, apply, validate, reset, baseline")
name = flag.String("name", "", "迁移名称 (仅用于 diff 操作)")
dryRun = flag.Bool("dry-run", false, "模拟执行,不实际应用迁移 (仅用于 apply 操作)")
)
flag.Parse()
if *action == "" {
printUsage()
os.Exit(1)
}
if err := database.EnsureAtlasInstalled(); err != nil {
log.Fatal("Atlas CLI 未安装:", err)
}
cfg, err := config.Load()
if err != nil {
log.Fatal("加载配置失败:", err)
}
if err := middlewares.InitLogger(&cfg.Logger); err != nil {
log.Fatal("初始化日志系统失败:", err)
}
defer middlewares.Sync()
driver := normalizeDriver(cfg.Database.Driver)
if driver != "postgres" && driver != "mysql" && driver != "sqlite" {
middlewares.Logger.Fatal("不支持的数据库驱动", zap.String("driver", cfg.Database.Driver))
}
if *env == "" {
*env = atlasEnvironment(cfg.App.Environment, driver)
}
migrationConfig := &database.MigrationConfig{
Environment: *env,
Directory: filepath.Join("migrations", driver),
Timeout: 240,
}
manager := database.NewMigrationManager(migrationConfig, middlewares.Logger)
if err := manager.InitMigrationDirectory(); err != nil {
middlewares.Logger.Fatal("初始化迁移目录失败", zap.Error(err))
}
switch *action {
case "status":
handleStatus(manager)
case "diff":
handleDiff(manager, *name)
case "apply":
handleApply(manager, *dryRun)
case "validate":
handleValidate(manager)
case "reset":
handleReset()
case "baseline":
handleBaseline(*env)
default:
fmt.Printf("未知操作: %s\n", *action)
printUsage()
os.Exit(1)
}
}
func normalizeDriver(driver string) string {
switch strings.ToLower(strings.TrimSpace(driver)) {
case "postgresql":
return "postgres"
case "sqlite3":
return "sqlite"
default:
return strings.ToLower(strings.TrimSpace(driver))
}
}
func atlasEnvironment(environment, driver string) string {
prefix := "local"
if strings.EqualFold(environment, "production") {
prefix = "production"
}
return prefix + "_" + driver
}
func handleStatus(manager *database.MigrationManager) {
fmt.Println("🔍 检查迁移状态...")
if err := manager.CheckMigrations(); err != nil {
middlewares.Logger.Fatal("检查迁移状态失败", zap.Error(err))
}
fmt.Println("✅ 迁移状态检查完成")
}
func handleDiff(manager *database.MigrationManager, name string) {
if name == "" {
fmt.Println("📝 生成迁移文件...")
} else {
fmt.Printf("📝 生成迁移文件: %s\n", name)
}
if err := manager.GenerateMigration(name); err != nil {
middlewares.Logger.Fatal("生成迁移失败", zap.Error(err))
}
fmt.Println("✅ 迁移文件生成完成")
}
func handleApply(manager *database.MigrationManager, dryRun bool) {
if dryRun {
fmt.Println("🧪 模拟应用迁移 (dry-run)...")
} else {
fmt.Println("🚀 应用迁移...")
}
if err := manager.ApplyMigrations(dryRun); err != nil {
middlewares.Logger.Fatal("应用迁移失败", zap.Error(err))
}
fmt.Println("✅ 迁移应用完成")
}
func handleValidate(manager *database.MigrationManager) {
fmt.Println("🔍 验证迁移文件...")
if err := manager.ValidateMigrations(); err != nil {
middlewares.Logger.Fatal("验证迁移失败", zap.Error(err))
}
fmt.Println("✅ 迁移验证通过")
}
func handleReset() {
fmt.Println("⚠️ 重置迁移历史是危险操作!")
fmt.Println("请手动执行: atlas migrate reset --env [环境名]")
fmt.Println("这将删除迁移历史表,请谨慎操作")
}
func handleBaseline(env string) {
fmt.Println("📍 基线迁移指导")
fmt.Println()
fmt.Println("基线迁移用于将现有数据库纳入迁移管理。请根据你的情况选择合适的方法:")
fmt.Println()
fmt.Println("🔄 情况1 - 全新数据库:")
fmt.Printf(" atlas migrate diff baseline --env %s\n", env)
fmt.Printf(" go run cmd/migrate/main.go -action apply -env %s\n", env)
fmt.Println()
fmt.Println("🔄 情况2 - 现有数据库(推荐):")
fmt.Printf(" atlas migrate diff baseline --env %s\n", env)
fmt.Printf(" atlas migrate hash --env %s\n", env)
fmt.Println(" # 然后应用后续新迁移")
fmt.Println()
fmt.Println("🔄 情况3 - 强制重建基线:")
fmt.Printf(" atlas migrate reset --env %s\n", env)
fmt.Printf(" go run cmd/migrate/main.go -action diff -name baseline -env %s\n", env)
fmt.Printf(" go run cmd/migrate/main.go -action apply -env %s\n", env)
fmt.Println()
fmt.Println("⚠️ 注意事项:")
fmt.Println(" - 在生产环境操作前请务必备份数据库")
fmt.Println(" - 确保团队成员都从同一基线开始")
fmt.Println(" - 建议先在开发环境测试迁移流程")
}
func printUsage() {
fmt.Println("数据库迁移管理工具")
fmt.Println()
fmt.Println("用法:")
fmt.Println(" go run cmd/migrate/main.go -action <操作> [选项]")
fmt.Println()
fmt.Println("操作:")
fmt.Println(" status 检查迁移状态")
fmt.Println(" diff 生成迁移文件 (可选: -name <迁移名称>)")
fmt.Println(" apply 应用迁移 (可选: -dry-run)")
fmt.Println(" validate 验证迁移文件")
fmt.Println(" reset 重置迁移历史 (仅显示提示)")
fmt.Println(" baseline 基线迁移指导 (显示基线创建命令)")
fmt.Println()
fmt.Println("选项:")
fmt.Println(" -env Atlas 环境 (默认: local_<driver> 或 production_<driver>)")
fmt.Println(" -name 迁移名称 (仅用于 diff)")
fmt.Println(" -dry-run 模拟执行 (仅用于 apply)")
fmt.Println()
fmt.Println("示例:")
fmt.Println(" go run cmd/migrate/main.go -action status")
fmt.Println(" go run cmd/migrate/main.go -action diff -name create_users")
fmt.Println(" go run cmd/migrate/main.go -action apply")
fmt.Println(" go run cmd/migrate/main.go -action apply -dry-run")
fmt.Println(" go run cmd/migrate/main.go -action validate")
fmt.Println(" go run cmd/migrate/main.go -action baseline")
}