数据库版本管理优化
This commit is contained in:
+124
-11
@@ -3,14 +3,22 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"skeleton/config"
|
||||||
|
"skeleton/database"
|
||||||
|
"skeleton/middlewares"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
env = flag.String("env", "local", "环境配置 (local/production)")
|
env = flag.String("env", "local", "环境配置 (local/production)")
|
||||||
action = flag.String("action", "", "操作类型: status, diff, apply, validate, reset")
|
action = flag.String("action", "", "操作类型: status, diff, apply, validate, reset, baseline")
|
||||||
name = flag.String("name", "", "迁移名称 (仅用于 diff 操作)")
|
name = flag.String("name", "", "迁移名称 (仅用于 diff 操作)")
|
||||||
|
dryRun = flag.Bool("dry-run", false, "模拟执行,不实际应用迁移 (仅用于 apply 操作)")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -19,21 +27,44 @@ func main() {
|
|||||||
os.Exit(1)
|
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()
|
||||||
|
|
||||||
|
migrationConfig := &database.MigrationConfig{
|
||||||
|
Environment: *env,
|
||||||
|
Timeout: 240,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := database.NewMigrationManager(migrationConfig, middlewares.Logger)
|
||||||
|
|
||||||
|
if err := manager.InitMigrationDirectory(); err != nil {
|
||||||
|
middlewares.Logger.Fatal("初始化迁移目录失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
switch *action {
|
switch *action {
|
||||||
case "status":
|
case "status":
|
||||||
fmt.Printf("atlas migrate status --env %s\n", *env)
|
handleStatus(manager)
|
||||||
case "diff":
|
case "diff":
|
||||||
if *name != "" {
|
handleDiff(manager, *name)
|
||||||
fmt.Printf("atlas migrate diff %s --env %s\n", *name, *env)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("atlas migrate diff --env %s\n", *env)
|
|
||||||
}
|
|
||||||
case "apply":
|
case "apply":
|
||||||
fmt.Printf("atlas migrate apply --env %s\n", *env)
|
handleApply(manager, *dryRun)
|
||||||
case "validate":
|
case "validate":
|
||||||
fmt.Printf("atlas migrate validate --env %s\n", *env)
|
handleValidate(manager)
|
||||||
case "reset":
|
case "reset":
|
||||||
fmt.Printf("atlas migrate reset --env %s\n", *env)
|
handleReset()
|
||||||
|
case "baseline":
|
||||||
|
handleBaseline(*env)
|
||||||
default:
|
default:
|
||||||
fmt.Printf("未知操作: %s\n", *action)
|
fmt.Printf("未知操作: %s\n", *action)
|
||||||
printUsage()
|
printUsage()
|
||||||
@@ -41,6 +72,84 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)...")
|
||||||
|
fmt.Println("注意: dry-run 功能需要在 Atlas 命令中添加 --dry-run 参数")
|
||||||
|
} else {
|
||||||
|
fmt.Println("🚀 应用迁移...")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.ApplyMigrations(); 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() {
|
func printUsage() {
|
||||||
fmt.Println("数据库迁移管理工具")
|
fmt.Println("数据库迁移管理工具")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -50,17 +159,21 @@ func printUsage() {
|
|||||||
fmt.Println("操作:")
|
fmt.Println("操作:")
|
||||||
fmt.Println(" status 检查迁移状态")
|
fmt.Println(" status 检查迁移状态")
|
||||||
fmt.Println(" diff 生成迁移文件 (可选: -name <迁移名称>)")
|
fmt.Println(" diff 生成迁移文件 (可选: -name <迁移名称>)")
|
||||||
fmt.Println(" apply 应用迁移")
|
fmt.Println(" apply 应用迁移 (可选: -dry-run)")
|
||||||
fmt.Println(" validate 验证迁移文件")
|
fmt.Println(" validate 验证迁移文件")
|
||||||
fmt.Println(" reset 重置迁移历史 (仅显示提示)")
|
fmt.Println(" reset 重置迁移历史 (仅显示提示)")
|
||||||
|
fmt.Println(" baseline 基线迁移指导 (显示基线创建命令)")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("选项:")
|
fmt.Println("选项:")
|
||||||
fmt.Println(" -env 环境配置 (默认: local)")
|
fmt.Println(" -env 环境配置 (默认: local)")
|
||||||
fmt.Println(" -name 迁移名称 (仅用于 diff)")
|
fmt.Println(" -name 迁移名称 (仅用于 diff)")
|
||||||
|
fmt.Println(" -dry-run 模拟执行 (仅用于 apply)")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("示例:")
|
fmt.Println("示例:")
|
||||||
fmt.Println(" go run cmd/migrate/main.go -action status")
|
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 diff -name create_users")
|
||||||
fmt.Println(" go run cmd/migrate/main.go -action apply")
|
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 validate")
|
||||||
|
fmt.Println(" go run cmd/migrate/main.go -action baseline")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MigrationConfig 迁移配置
|
||||||
|
type MigrationConfig struct {
|
||||||
|
Environment string // local, production
|
||||||
|
Timeout int // 超时时间(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrationManager 迁移管理器
|
||||||
|
type MigrationManager struct {
|
||||||
|
config *MigrationConfig
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMigrationManager 创建迁移管理器
|
||||||
|
func NewMigrationManager(config *MigrationConfig, logger *zap.Logger) *MigrationManager {
|
||||||
|
return &MigrationManager{
|
||||||
|
config: config,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMigrations 检查是否有未应用的迁移
|
||||||
|
func (m *MigrationManager) CheckMigrations() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(m.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "atlas", "migrate", "status", "--env", m.config.Environment)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("检查迁移状态失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
return fmt.Errorf("检查迁移状态失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.logger.Info("迁移状态检查完成", zap.String("output", string(output)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateMigration 生成迁移文件
|
||||||
|
func (m *MigrationManager) GenerateMigration(name string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(m.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if name != "" {
|
||||||
|
cmd = exec.CommandContext(ctx, "atlas", "migrate", "diff", name, "--env", m.config.Environment)
|
||||||
|
} else {
|
||||||
|
cmd = exec.CommandContext(ctx, "atlas", "migrate", "diff", "--env", m.config.Environment)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("生成迁移失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
return fmt.Errorf("生成迁移失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.logger.Info("迁移文件生成成功",
|
||||||
|
zap.String("name", name),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyMigrations 应用迁移
|
||||||
|
func (m *MigrationManager) ApplyMigrations() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(m.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "atlas", "migrate", "apply", "--env", m.config.Environment)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("应用迁移失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
return fmt.Errorf("应用迁移失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.logger.Info("迁移应用成功", zap.String("output", string(output)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateMigrations 验证迁移
|
||||||
|
func (m *MigrationManager) ValidateMigrations() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(m.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "atlas", "migrate", "validate", "--env", m.config.Environment)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("验证迁移失败",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
return fmt.Errorf("验证迁移失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.logger.Info("迁移验证成功", zap.String("output", string(output)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureAtlasInstalled 确保Atlas已安装
|
||||||
|
func EnsureAtlasInstalled() error {
|
||||||
|
cmd := exec.Command("atlas", "version")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Atlas CLI 未安装或不在 PATH 中,请安装: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitMigrationDirectory 初始化迁移目录
|
||||||
|
func (m *MigrationManager) InitMigrationDirectory() error {
|
||||||
|
migrationDir := "migrations"
|
||||||
|
|
||||||
|
// 检查目录是否存在
|
||||||
|
if _, err := os.Stat(migrationDir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(migrationDir, 0755); err != nil {
|
||||||
|
m.logger.Error("创建迁移目录失败", zap.Error(err))
|
||||||
|
return fmt.Errorf("创建迁移目录失败: %w", err)
|
||||||
|
}
|
||||||
|
m.logger.Info("迁移目录创建成功", zap.String("dir", migrationDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user