成都网站建设设计

将想法与焦点和您一起共享

go-admin数据库迁移代码分析-创新互联

1,仓库地址:https://github.com/go-admin-team/go-admin.git 我拉取的是master分支,commit:c973d6819ceb008e0dfa97173ca3c69ce1cfd49a

创新互联建站-专业网站定制、快速模板网站建设、高性价比官渡网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式官渡网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖官渡地区。费用合理售后完善,10多年实体公司更值得信赖。

2,这里只分析migrate,go-admin的启动命令使用cobra封装(k8s也在用),那第一步就是定义migrate(迁移)指令并且挂载到根Command

import (
    ...
	"go-admin/cmd/migrate"
    ...
)

func init() {
	rootCmd.AddCommand(migrate.StartCmd)
    ...
}

3,migrate.StartCmd定义在cmd/migrate/server.go,因为go-admin是后台脚手架,自带了权限,菜单的管理,作者在定义迁移时,划分两种,你可以直接修改go-admin自带的数据模型,也可以自定义模型,详见代码注释

// 我只保留核心逻辑的代码
import (
	...
	_ "go-admin/cmd/migrate/migration/version" // 系统自带的迁移目录
	_ "go-admin/cmd/migrate/migration/version-local" // 自定义迁移目录
	...
)

var (
	configYml string // 配置文件
	// ./go-admin migrate -g=true -a=false
	generate  bool // 生成自定义迁移文件
	goAdmin   bool // 生成go-admin初始迁移
	host      string // 域, 为什么会有一个域?一个域代表的是一个key,作者是希望支持多库的,目前 我对多库的处理是在 ApplicationConfig中新增了一个Database2
    ...
)

...

func run() {

	if !generate { // 默认false,先生成后执行
		fmt.Println(`start init`)
		//1. 读取配置
		config.Setup(
			file.NewSource(file.WithPath(configYml)),
			initDB,
		)
	} else {
		fmt.Println(`generate migration file`)
		_ = genFile() // 生成配置文件
	}
}

func migrateModel() error {
	if host == "" {
		host = "*"
	}
	db := sdk.Runtime.GetDbByKey(host) // 取库
	if config.DatabasesConfig[host].Driver == "mysql" {
		//初始化数据库时候用
		db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4")
	}
	err := db.Debug().AutoMigrate(&models.Migration{}) // 迁移库,维护了一个迁移库来记录迁移,根据版本信息,来确定是否执行这次迁移
	if err != nil {
		return err
	}
	migration.Migrate.SetDb(db.Debug()) // 设置 DB
	migration.Migrate.Migrate() // 执行迁移
	return err
}
func initDB() {
	//3. 初始化数据库链接
	database.Setup()
	//4. 数据库迁移
	fmt.Println("数据库迁移开始")
	_ = migrateModel()
	fmt.Println(`数据库基础数据初始化成功`)
}

func genFile() error {
	t1, err := template.ParseFiles("template/migrate.template") // 解读模版文件
	if err != nil {
		return err
	}
	m := map[string]string{}
	m["GenerateTime"] = strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
	m["Package"] = "version_local"
    // goAdmin 区分是 系统级迁移还是自定义的迁移
	if goAdmin {
		m["Package"] = "version"
	}
	var b1 bytes.Buffer
	err = t1.Execute(&b1, m) // map 映射到模版文件并写入buffer
	if goAdmin {
		pkg.FileCreate(b1, "./cmd/migrate/migration/version/"+m["GenerateTime"]+"_migrate.go")
	} else {
		pkg.FileCreate(b1, "./cmd/migrate/migration/version-local/"+m["GenerateTime"]+"_migrate.go")
	}
	return nil
}

3,再有的核心就是在version和version-local了

... 
// version, version-local 是一样的逻辑,都是用init触发的

func init() {
	_, fileName, _, _ := runtime.Caller(0)
	migration.Migrate.SetVersion(migration.GetFilename(fileName), _1599190683659Tables) // SetVersion是将当前版本加入到迁移数组当中
}

// 迁移函数
func _1599190683659Tables(db *gorm.DB, version string) error {
	return db.Transaction(func(tx *gorm.DB) error {
		if config.DatabaseConfig.Driver == "mysql" {
			tx = tx.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
		}
		err := tx.Migrator().AutoMigrate(
			new(models.SysDept)
            ...
		)
		if err != nil {
			return err
		}
		if err := models.InitDb(tx); err != nil {
			return err
		}
		return tx.Create(&common.Migration{
			Version: version, // 这里就是要增加当前迁移的版本信息了
		}).Error
	})
}

4,上面是定义好了迁移的实体方法,我们来看一下调用方式

package migration

import (
	"log"
	"path/filepath"
	"sort"
	"sync"

	"gorm.io/gorm"
)

var Migrate = &Migration{
	version: make(map[string]func(db *gorm.DB, version string) error),
}
// 迁移的必要元素
type Migration struct {
	db      *gorm.DB // 指定的库
	version map[string]func(db *gorm.DB, version string) error // 每个版本信息对应的迁移函数,就是上面那串时间戳函数
	mutex   sync.Mutex 
}

...

// 这就是上面的代码init中,将version信息和迁移函数做了绑定
func (e *Migration) SetVersion(k string, f func(db *gorm.DB, version string) error) {
	e.mutex.Lock()
	defer e.mutex.Unlock()
	e.version[k] = f // 给指定版本绑定迁移函数
}

// 真正执行迁移的地方
func (e *Migration) Migrate() {
	versions := make([]string, 0)
	for k := range e.version {
		versions = append(versions, k)
	}
	if !sort.StringsAreSorted(versions) {
		sort.Strings(versions)
	}
	var err error
	var count int64
    
    // versions 是什么意思?只要是包含在version和version-local目录的init方法内的,都会加入到versions
    // 然后去库里匹配version,匹配不到即将迁移的版本信息的,则开始执行对应的迁移函数
	for _, v := range versions {
		err = e.db.Table("sys_migration").Where("version = ?", v).Count(&count).Error
		if err != nil {
			log.Fatalln(err)
		}
		if count >0 { // 有这个版本跳出
			log.Println(count)
			count = 0
			continue
		}
		err = (e.version[v])(e.db.Debug(), v) // 这是迁移函数
		if err != nil {
			log.Fatalln(err)
		}
	}
}

func GetFilename(s string) string {
	s = filepath.Base(s)
	return s[:13] // 取名字,这也是setVersion的第一个参数
}

5,有一张记录迁移信息的表

只有version和apply_time两个字段

6,完。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章标题:go-admin数据库迁移代码分析-创新互联
链接分享:http://chengdu.cdxwcx.cn/article/cochog.html