| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- // Package migrate 操作对象
- // 负责创建 migrations 数据表,以及执行迁移操作。
- package migrate
- import (
- "os"
- "gorm.io/gorm"
- "github.com/runningwater/gohub/pkg/console"
- "github.com/runningwater/gohub/pkg/database"
- "github.com/runningwater/gohub/pkg/file"
- )
- // Migrator 结构体用于存储迁移器的相关信息。
- type Migrator struct {
- Folder string // Folder 是存储迁移文件的目录名。
- DB *gorm.DB // Db 是数据库连接对象。
- Migrator gorm.Migrator // Migrator 是 GORM 的迁移器对象。
- }
- func (m *Migrator) createMigrationsTable() {
- migration := Migration{}
- if !m.Migrator.HasTable(&migration) {
- // 如果表不存在,则创建表
- if err := m.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci").Migrator().CreateTable(&migration); err != nil {
- console.ExitIf(err)
- return
- }
- }
- }
- // Migration 对应数据表 migrations 里的一条记录
- type Migration struct {
- ID uint64 `gorm:"primaryKey;autoIncrement;"`
- Migration string `gorm:"type:varchar(255);not null;unique;"`
- Batch int
- }
- func NewMigrator() *Migrator {
- migrator := &Migrator{
- Folder: "database/migrations",
- DB: database.DB,
- Migrator: database.DB.Migrator(),
- }
- migrator.createMigrationsTable()
- return migrator
- }
- // Up 执行所有未执行的迁移文件。
- func (m *Migrator) Up() {
- // 读取所有迁移文件, 确保按照时间排序
- migrateFiles := m.readAllMigrationFiles()
- batch := m.getBatch()
- // 获取所有迁移数据
- var 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")
- }
- }
- // Rollback 回滚上一次的迁移操作
- func (m *Migrator) Rollback() {
- // 获取最后一批次的迁移数据
- lastMigration := Migration{}
- m.DB.Order("id desc").First(&lastMigration)
- var migrations []Migration
- m.DB.Where("batch = ?", lastMigration.Batch).Order("id desc").Find(&migrations)
- // 回滚迁移操作
- if !m.rollbackMigrations(migrations) {
- console.Success("[migrations] table is empty, nothing to rollback")
- }
- }
- // Reset 回滚所有的迁移操作
- func (m *Migrator) Reset() {
- // 获取所有的迁移数据
- var migrations []Migration
- m.DB.Order("id desc").Find(&migrations)
- // 回滚迁移操作
- if !m.rollbackMigrations(migrations) {
- console.Success("[migrations] table is empty, nothing to rollback")
- }
- }
- // Refresh 回滚所有的迁移操作, 并重新执行所有的迁移操作
- func (m *Migrator) Refresh() {
- // 回滚所有的迁移操作
- m.Reset()
- // 重新执行所有的迁移操作
- m.Up()
- }
- // Fresh Drop 所有的表, 并重新执行所有的迁移操作
- func (m *Migrator) Fresh() {
- // 获取数据库名称,用以提示
- dbname := database.CurrentDatabase()
- // 删除所有表
- err := database.DeleteAllTables()
- console.ExitIf(err)
- console.Success("database " + dbname + " cleared")
- // 重新创建 migrates 表
- m.createMigrationsTable()
- console.Success("migrations table created")
- // 重新执行所有的迁移操作
- m.Up()
- }
- // 回滚迁移操作
- func (m *Migrator) rollbackMigrations(migrations []Migration) bool {
- // 标记是否真的有执行了迁移回退的操作
- runed := false
- // 遍历迁移数据,回滚迁移操作
- for _, migration := range migrations {
- // 友好提示
- console.Warning("rolling back " + migration.Migration + " ...")
- // 获取迁移文件
- mfile := getMigrationFile(migration.Migration)
- if mfile.Down != nil {
- // 执行迁移回退操作
- mfile.Down(database.DB.Migrator(), database.DB)
- }
- runed = true
- // 删除迁移数据
- m.DB.Delete(&migration)
- console.Success("rolled back " + migration.Migration + " finished")
- }
- return runed
- }
- // 获取当前这个批次的值
- 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(file MigrationFile, batch int) {
- if file.Up != nil {
- console.Warning("migrating " + file.FileName)
- // 执行迁移操作
- file.Up(database.DB.Migrator(), database.DB)
- console.Success("migrated " + file.FileName)
- }
- // 插入一条记录到数据库
- err := m.DB.Create(&Migration{
- Migration: file.FileName,
- Batch: batch,
- }).Error
- console.ExitIf(err)
- }
|