扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
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元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流