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数据库迁移代码分析-创新互联
转载来源:https://www.cdcxhl.com/article6/cochog.html

成都网站建设公司_创新互联,为您提供企业建站网站营销软件开发网站内链企业网站制作定制网站

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

网站优化排名