Forráskód Böngészése

feat(command): migrate up 命令

runningwater 7 hónapja
szülő
commit
215a0fcf66

+ 2 - 0
.gitignore

@@ -64,6 +64,8 @@ Thumbs.db
 .fseventsd
 .VolumeIcon.icns
 .com.apple.timemachine.donotpresent
+# 其他 #
+*.rdb
 
 # IDE 和编辑器 #
 ######################

+ 1 - 0
README.md

@@ -72,6 +72,7 @@ UNIQUE KEY `migration` (`migration`)
 
 #### 🚀 新功能
 
+- *(command)* Migrate up 命令
 - *(middlewares)* Cors 中间件
 - Migrate 包
 - *(command)* Make request 命令

+ 37 - 0
app/cmd/migrate.go

@@ -0,0 +1,37 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/5/28 16:26
+// Desc:
+
+package cmd
+
+import (
+    "github.com/spf13/cobra"
+
+    "github.com/runningwater/gohub/database/migrations"
+    "github.com/runningwater/gohub/pkg/migrate"
+)
+
+var CmdMigrate = &cobra.Command{
+    Use:   "migrate",
+    Short: "Run database migrations",
+}
+var CmdMigrateUp = &cobra.Command{
+    Use:   "up",
+    Short: "Run up migrations",
+    Run:   runUp,
+}
+
+func init() {
+    CmdMigrate.AddCommand(CmdMigrateUp)
+}
+
+func runUp(cmd *cobra.Command, args []string) {
+    migrator().Up()
+}
+
+func migrator() *migrate.Migrator {
+    // 注册 database/migrations 目录下的所有迁移文件
+    migrations.Initialize()
+
+    return migrate.NewMigrator()
+}

+ 9 - 0
database/migrations/migrations.go

@@ -0,0 +1,9 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/5/28 16:37
+// Desc: 存放所有的数据库迁移文件
+
+package migrations
+
+func Initialize() {
+	// 触发加载本目录下其他文件中的 Init 方法
+}

+ 1 - 0
main.go

@@ -49,6 +49,7 @@ func main() {
 		cmd.CmdServe,
 		cmd.CmdKey,
 		cmd.CmdPlay,
+		cmd.CmdMigrate,
 		// cmd.CmdTestCommand,
 
 		make.CmdMake,

+ 9 - 1
pkg/file/file.go

@@ -1,6 +1,10 @@
 package file
 
-import "os"
+import (
+	"os"
+	"path/filepath"
+	"strings"
+)
 
 // Put 将数据写入文件
 // 如果文件不存在,则创建文件
@@ -26,3 +30,7 @@ func Exists(path string) bool {
 	}
 	return true
 }
+
+func NameWithoutExtension(fileName string) string {
+	return strings.TrimSuffix(fileName, filepath.Ext(fileName))
+}

+ 20 - 0
pkg/migrate/migration_file.go

@@ -29,3 +29,23 @@ func Add(up, down migrationFunc, fileName string) {
 		FileName: fileName,
 	})
 }
+
+// getMigrationFile 用于根据迁移文件的名称获取对应的 MigrationFile 结构体。
+func getMigrationFile(name string) MigrationFile {
+	for _, mfile := range migrationFiles {
+		if mfile.FileName == name {
+			return mfile
+		}
+	}
+	return MigrationFile{}
+}
+
+func (mfile MigrationFile) isNotMigrated(migrations []Migration) bool {
+	for _, migration := range migrations {
+		if migration.Migration == mfile.FileName {
+			return false
+		}
+	}
+	return true
+
+}

+ 97 - 3
pkg/migrate/migrator.go

@@ -1,10 +1,15 @@
-// Package: migrate 操作对象
+// Package migrate  操作对象
 // 负责创建 migrations 数据表,以及执行迁移操作。
 package migrate
 
 import (
-	"github.com/runningwater/gohub/pkg/database"
+	"os"
+
 	"gorm.io/gorm"
+
+	"github.com/runningwater/gohub/pkg/console"
+	"github.com/runningwater/gohub/pkg/database"
+	"github.com/runningwater/gohub/pkg/file"
 )
 
 // Migrator 结构体用于存储迁移器的相关信息。
@@ -19,7 +24,10 @@ func (m *Migrator) createMigrationsTable() {
 
 	if !m.Migrator.HasTable(&migration) {
 		// 如果表不存在,则创建表
-		m.Migrator.CreateTable(&migration)
+		if err := m.Migrator.CreateTable(&migration); err != nil {
+			console.ExitIf(err)
+			return
+		}
 	}
 }
 
@@ -41,3 +49,89 @@ func NewMigrator() *Migrator {
 
 	return migrator
 }
+
+// Up 执行所有未执行的迁移文件。
+func (m *Migrator) Up() {
+
+	// 读取所有迁移文件, 确保按照时间排序
+	migrateFiles := m.readAllMigrationFiles()
+	batch := m.getBatch()
+
+	// 获取所有迁移数据
+	migrations := []Migration{}
+	m.DB.Find(&migrations)
+
+	// 可以通过此值来判断数据库是否已是最新
+	runed := false
+
+	// 遍历迁移文件,执行未执行的迁移文件
+	for _, mfile := range migrateFiles {
+
+		if mfile.isNotMigrated(migrations) {
+			m.runUpMigration(mfile, batch)
+			runed = true
+		}
+	}
+
+	if !runed {
+		console.Success("database is up to date")
+	}
+}
+
+// 获取当前这个批次的值
+func (m *Migrator) getBatch() int {
+
+	batch := 1
+
+	lastMigration := Migration{}
+	m.DB.Order("id desc").First(&lastMigration)
+
+	// 如果有值的话,加一
+	if lastMigration.ID > 0 {
+		batch = lastMigration.Batch + 1
+	}
+
+	return batch
+}
+
+// 从文件目录读取文件,保证正确的时间排序
+func (m *Migrator) readAllMigrationFiles() []MigrationFile {
+	// 读取 database/migrations 目录下的所有迁移文件
+	files, err := os.ReadDir(m.Folder)
+	console.ExitIf(err)
+
+	var migrateFiles []MigrationFile
+	// 遍历所有迁移文件
+	for _, f := range files {
+
+		// 去除文件后缀 .go
+		fileName := file.NameWithoutExtension(f.Name())
+		// 通过迁移文件名称获取[MigrationFile]结构体
+		mfile := getMigrationFile(fileName)
+		if len(mfile.FileName) > 0 {
+			migrateFiles = append(migrateFiles, mfile)
+		}
+	}
+	return migrateFiles
+}
+
+// 插入一条记录到数据库,同时执行迁移操作
+// 此方法会在 Up 方法中被调用
+// mfile 是迁移文件的结构体,batch 是批次号,用于区分不同的迁移批次。
+// 此方法会执行迁移操作,同时插入一条记录到数据库。
+// 如果迁移操作成功,会输出一条成功信息;如果迁移操作失败,会输出一条错误信息,并退出程序。
+func (m *Migrator) runUpMigration(mfile MigrationFile, batch int) {
+
+	if mfile.Up != nil {
+		console.Warning("migrating " + mfile.FileName)
+		// 执行迁移操作
+		mfile.Up(database.DB.Migrator(), database.SQLDB)
+		console.Success("migrated " + mfile.FileName)
+	}
+	// 插入一条记录到数据库
+	err := m.DB.Create(&Migration{
+		Migration: mfile.FileName,
+		Batch:     batch,
+	}).Error
+	console.ExitIf(err)
+}