migrator.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Package migrate 操作对象
  2. // 负责创建 migrations 数据表,以及执行迁移操作。
  3. package migrate
  4. import (
  5. "os"
  6. "gorm.io/gorm"
  7. "github.com/runningwater/gohub/pkg/console"
  8. "github.com/runningwater/gohub/pkg/database"
  9. "github.com/runningwater/gohub/pkg/file"
  10. )
  11. // Migrator 结构体用于存储迁移器的相关信息。
  12. type Migrator struct {
  13. Folder string // Folder 是存储迁移文件的目录名。
  14. DB *gorm.DB // Db 是数据库连接对象。
  15. Migrator gorm.Migrator // Migrator 是 GORM 的迁移器对象。
  16. }
  17. func (m *Migrator) createMigrationsTable() {
  18. migration := Migration{}
  19. if !m.Migrator.HasTable(&migration) {
  20. // 如果表不存在,则创建表
  21. if err := m.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci").Migrator().CreateTable(&migration); err != nil {
  22. console.ExitIf(err)
  23. return
  24. }
  25. }
  26. }
  27. // Migration 对应数据表 migrations 里的一条记录
  28. type Migration struct {
  29. ID uint64 `gorm:"primaryKey;autoIncrement;"`
  30. Migration string `gorm:"type:varchar(255);not null;unique;"`
  31. Batch int
  32. }
  33. func NewMigrator() *Migrator {
  34. migrator := &Migrator{
  35. Folder: "database/migrations",
  36. DB: database.DB,
  37. Migrator: database.DB.Migrator(),
  38. }
  39. migrator.createMigrationsTable()
  40. return migrator
  41. }
  42. // Up 执行所有未执行的迁移文件。
  43. func (m *Migrator) Up() {
  44. // 读取所有迁移文件, 确保按照时间排序
  45. migrateFiles := m.readAllMigrationFiles()
  46. batch := m.getBatch()
  47. // 获取所有迁移数据
  48. var migrations []Migration
  49. m.DB.Find(&migrations)
  50. // 可以通过此值来判断数据库是否已是最新
  51. runed := false
  52. // 遍历迁移文件,执行未执行的迁移文件
  53. for _, mfile := range migrateFiles {
  54. if mfile.isNotMigrated(migrations) {
  55. m.runUpMigration(mfile, batch)
  56. runed = true
  57. }
  58. }
  59. if !runed {
  60. console.Success("database is up to date")
  61. }
  62. }
  63. // Rollback 回滚上一次的迁移操作
  64. func (m *Migrator) Rollback() {
  65. // 获取最后一批次的迁移数据
  66. lastMigration := Migration{}
  67. m.DB.Order("id desc").First(&lastMigration)
  68. var migrations []Migration
  69. m.DB.Where("batch = ?", lastMigration.Batch).Order("id desc").Find(&migrations)
  70. // 回滚迁移操作
  71. if !m.rollbackMigrations(migrations) {
  72. console.Success("[migrations] table is empty, nothing to rollback")
  73. }
  74. }
  75. // Reset 回滚所有的迁移操作
  76. func (m *Migrator) Reset() {
  77. // 获取所有的迁移数据
  78. var migrations []Migration
  79. m.DB.Order("id desc").Find(&migrations)
  80. // 回滚迁移操作
  81. if !m.rollbackMigrations(migrations) {
  82. console.Success("[migrations] table is empty, nothing to rollback")
  83. }
  84. }
  85. // Refresh 回滚所有的迁移操作, 并重新执行所有的迁移操作
  86. func (m *Migrator) Refresh() {
  87. // 回滚所有的迁移操作
  88. m.Reset()
  89. // 重新执行所有的迁移操作
  90. m.Up()
  91. }
  92. // Fresh Drop 所有的表, 并重新执行所有的迁移操作
  93. func (m *Migrator) Fresh() {
  94. // 获取数据库名称,用以提示
  95. dbname := database.CurrentDatabase()
  96. // 删除所有表
  97. err := database.DeleteAllTables()
  98. console.ExitIf(err)
  99. console.Success("database " + dbname + " cleared")
  100. // 重新创建 migrates 表
  101. m.createMigrationsTable()
  102. console.Success("migrations table created")
  103. // 重新执行所有的迁移操作
  104. m.Up()
  105. }
  106. // 回滚迁移操作
  107. func (m *Migrator) rollbackMigrations(migrations []Migration) bool {
  108. // 标记是否真的有执行了迁移回退的操作
  109. runed := false
  110. // 遍历迁移数据,回滚迁移操作
  111. for _, migration := range migrations {
  112. // 友好提示
  113. console.Warning("rolling back " + migration.Migration + " ...")
  114. // 获取迁移文件
  115. mfile := getMigrationFile(migration.Migration)
  116. if mfile.Down != nil {
  117. // 执行迁移回退操作
  118. mfile.Down(database.DB.Migrator(), database.DB)
  119. }
  120. runed = true
  121. // 删除迁移数据
  122. m.DB.Delete(&migration)
  123. console.Success("rolled back " + migration.Migration + " finished")
  124. }
  125. return runed
  126. }
  127. // 获取当前这个批次的值
  128. func (m *Migrator) getBatch() int {
  129. batch := 1
  130. lastMigration := Migration{}
  131. m.DB.Order("id desc").First(&lastMigration)
  132. // 如果有值的话,加一
  133. if lastMigration.ID > 0 {
  134. batch = lastMigration.Batch + 1
  135. }
  136. return batch
  137. }
  138. // 从文件目录读取文件,保证正确的时间排序
  139. func (m *Migrator) readAllMigrationFiles() []MigrationFile {
  140. // 读取 database/migrations 目录下的所有迁移文件
  141. files, err := os.ReadDir(m.Folder)
  142. console.ExitIf(err)
  143. var migrateFiles []MigrationFile
  144. // 遍历所有迁移文件
  145. for _, f := range files {
  146. // 去除文件后缀 .go
  147. fileName := file.NameWithoutExtension(f.Name())
  148. // 通过迁移文件名称获取[MigrationFile]结构体
  149. mfile := getMigrationFile(fileName)
  150. if len(mfile.FileName) > 0 {
  151. migrateFiles = append(migrateFiles, mfile)
  152. }
  153. }
  154. return migrateFiles
  155. }
  156. // 插入一条记录到数据库,同时执行迁移操作
  157. // 此方法会在 Up 方法中被调用
  158. // mfile 是迁移文件的结构体,batch 是批次号,用于区分不同的迁移批次。
  159. // 此方法会执行迁移操作,同时插入一条记录到数据库。
  160. // 如果迁移操作成功,会输出一条成功信息;如果迁移操作失败,会输出一条错误信息,并退出程序。
  161. func (m *Migrator) runUpMigration(file MigrationFile, batch int) {
  162. if file.Up != nil {
  163. console.Warning("migrating " + file.FileName)
  164. // 执行迁移操作
  165. file.Up(database.DB.Migrator(), database.DB)
  166. console.Success("migrated " + file.FileName)
  167. }
  168. // 插入一条记录到数据库
  169. err := m.DB.Create(&Migration{
  170. Migration: file.FileName,
  171. Batch: batch,
  172. }).Error
  173. console.ExitIf(err)
  174. }