1. 介绍
gorm是一个使用Go语言编写的ORM框架。 它文档齐全,对开发者友好,支持主流数据库。具体使用可参考之前的文章Go常用包(十九):全功能ORM框架(gorm)
1.1 集成流程

1.2 涉及目录

2. 配置
2.1 编辑主配置./config.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | mysql:   host: 127.0.0.1   port: 3306   user: root   password: root   database: test   charset: utf8mb4    parseTime: true    timeZone: Local    defaultStringSize: 255    disableDatetimePrecision: true    skipInitializeWithVersion: false    autoMigrate: true    slowSql: 50ms    logLevel: info    ignoreRecordNotFoundError: true    gorm:      skipDefaultTx: false      tablePrefix: "app_"      singularTable: true      coverLogger: true      prepareStmt: false      disableForeignKeyConstraintWhenMigrating: true 
 
  | 
 
2.2 编辑结构体
新增./config/mysql.go文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | 
 
  package config
  import "time"
 
  type mysql struct { 	Host                      string        `yaml:"host"` 	Port                      string        `yaml:"port"` 	User                      string        `yaml:"user"` 	Password                  string        `yaml:"password"` 	Database                  string        `yaml:"database"` 	Charset                   string        `yaml:"charset"`                    	AutoMigrate               bool          `yaml:"autoMigrate"`                	ParseTime                 bool          `yaml:"parseTime"`                  	TimeZone                  string        `yaml:"timeZone"`                   	DefaultStringSize         uint          `yaml:"defaultStringSize"`          	DisableDatetimePrecision  bool          `yaml:"disableDatetimePrecision"`   	SkipInitializeWithVersion bool          `yaml:"skipInitializeWithVersion"`  	Gorm                      gorm          `yaml:"gorm"` 	SlowSql                   time.Duration `yaml:"slowSql"`                    	LogLevel                  string        `yaml:"logLevel"`                   	IgnoreRecordNotFoundError bool          `yaml:"ignoreRecordNotFoundError"`  }
  type gorm struct { 	SkipDefaultTx   bool   `yaml:"skipDefaultTx"`                             	CoverLogger     bool   `yaml:"coverLogger"`                               	PreparedStmt    bool   `yaml:"prepareStmt"`                               	CloseForeignKey bool   `yaml:"disableForeignKeyConstraintWhenMigrating"`  	TablePrefix     string `yaml:"tablePrefix"`                               	SingularTable   bool   `yaml:"singularTable"`                             }
 
  | 
 
2.3 嵌入主配置
编辑文件:./config/app.go
1 2 3 4 5 6
   | ...
  type ServerConfig struct {   .... 	Mysql mysql `yaml:"mysql"`  }
 
  | 
 
2.4 定义全局变量
编辑文件:./global/global.go
1 2 3 4 5
   |  var ( 	... 	GvaMysqlClient *gorm.DB             )
 
  | 
 
3.代码实现
3.1  集成入口
1. 编辑 main.go
1 2 3 4 5 6 7 8 9
   | func init() { 	... 	 	initialize.InitGorm() } func main() { 	 	... }
 
  | 
 
2. initialize.InitGorm()
新增文件:./initialize/gorm.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   |  func InitGorm() { 	mysqlConfig := global.GvaConfig.Mysql 	 	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=%t&loc=%s", 		mysqlConfig.User, mysqlConfig.Password, mysqlConfig.Host, mysqlConfig.Port, mysqlConfig.Database, mysqlConfig.Charset, 		mysqlConfig.ParseTime, mysqlConfig.TimeZone) 	 	gormConfig := &gorm.Config{ 		SkipDefaultTransaction: mysqlConfig.Gorm.SkipDefaultTx,  		 		NamingStrategy: schema.NamingStrategy{ 			TablePrefix:   mysqlConfig.Gorm.TablePrefix, 			SingularTable: mysqlConfig.Gorm.SingularTable, 		}, 		 		PrepareStmt: mysqlConfig.Gorm.PreparedStmt, 		 		DisableForeignKeyConstraintWhenMigrating: mysqlConfig.Gorm.CloseForeignKey, 	} 	 	if mysqlConfig.Gorm.CoverLogger { 		setNewLogger(gormConfig) 	} 	client, err := gorm.Open(mysql.New(mysql.Config{ 		DSN:                       dsn, 		DefaultStringSize:         mysqlConfig.DefaultStringSize, 		DisableDatetimePrecision:  mysqlConfig.DisableDatetimePrecision, 		SkipInitializeWithVersion: mysqlConfig.SkipInitializeWithVersion, 	}), gormConfig) 	if err != nil { 		panic(fmt.Sprintf("创建mysql客户端失败: %s", err)) 	} 	 	global.GvaMysqlClient = client 	 	if mysqlConfig.AutoMigrate { 		core.AutoMigrate() 	} }
 
  | 
 
3.2 重写默认Logger
Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误到控制台,也可以重写覆盖,实现写入到单独文件。
编辑文件:./initialize/gorm.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  func setNewLogger(gConfig *gorm.Config) { 	logPath := global.GvaConfig.Log.Path 	file, _ := os.OpenFile(logPath+"/sql.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) 	 	logLevelMap := map[string]logger.LogLevel{ 		"error": logger.Error, 		"info":  logger.Info, 		"warn":  logger.Warn, 	} 	var logLevel logger.LogLevel 	var ok bool 	if logLevel, ok = logLevelMap[global.GvaConfig.Mysql.LogLevel]; !ok { 		logLevel = logger.Error 	} 	newLogger := logger.New(log.New(file, "\r\n", log.LstdFlags), logger.Config{ 		SlowThreshold:             global.GvaConfig.Mysql.SlowSql,                    		LogLevel:                  logLevel,                                          		IgnoreRecordNotFoundError: global.GvaConfig.Mysql.IgnoreRecordNotFoundError,  		Colorful:                  false,                                             	}) 	gConfig.Logger = newLogger }
 
  | 
 
3.3 数据迁移AutoMigrate 
1. 新增实体
新增文件:./model/entity/user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | 
 
  package entity
  import ( 	"52lu/go-import-template/global" )
 
  type User struct { 	global.BaseModel 	NickName string   `json:"nickName" gorm:"type:varchar(20);not null;default:'';comment:昵称"` 	Phone    string   `json:"phone" gorm:"type:char(11);unique:un_phone;comment:手机号"` 	Password string   `json:"password" gorm:"type:varchar(20);comment:密码"` 	Status   int      `json:"status" gorm:"size:4;default:1;comment:状态 1:正常 2:白名单 3:黑名单"` 	UserInfo UserInfo `json:"userInfo" gorm:"-"` }
 
  type UserInfo struct { 	global.BaseModel 	Uid      uint   `json:"uid" gorm:"comment:用户id"` 	Birthday string `json:"birthday" gorm:"type:varchar(10);comment:生日"` 	Address  string `json:"address" gorm:"type:text;comment:地址"` }
 
  | 
 
2. 迁移代码
新增文件:./core/gorm_migrate.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | 
 
  package core
  import ( 	"52lu/go-import-template/global" 	"52lu/go-import-template/model/entity" 	"fmt" 	"gorm.io/gorm" )
  func setTableOption(tableComment string) *gorm.DB { 	value := fmt.Sprintf("ENGINE=InnoDB COMMENT='%s'", tableComment) 	return global.GvaMysqlClient.Set("gorm:table_options", value) }
  func userTable() { 	 	_ = setTableOption("用户表").AutoMigrate(&entity.User{}) 	 	_ = setTableOption("用户信息表").AutoMigrate(&entity.UserInfo{}) }
  func AutoMigrate() { 	 	userTable() }
 
  | 
 
4. 场景示例
下面以登录和注册场景,演示使用和请求流程。
4.1 调用流程

4.2 代码实现
1. 编辑api
新建./api/v1/user_api.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
   | 
 
  package v1
  import ( 	"52lu/go-import-template/global" 	"52lu/go-import-template/model/entity" 	"52lu/go-import-template/model/request" 	"52lu/go-import-template/model/response" 	"52lu/go-import-template/service" 	"fmt" 	"github.com/gin-gonic/gin" 	"go.uber.org/zap" )
 
 
 
 
  func Register(ctx *gin.Context) { 	 	var registerParam request.RegisterParam 	_ = ctx.ShouldBindJSON(®isterParam) 	 	 	register, err := service.Register(registerParam) 	if err != nil { 		response.Fail(ctx,"注册失败!") 		return 	} 	response.OkWithData(ctx,register) }
 
 
 
 
  func Login(ctx *gin.Context) { 	 	var loginParam request.LoginParam 	_ = ctx.ShouldBindJSON(&loginParam) 	fmt.Println("参数:", loginParam) 	if loginParam.Password == "" || loginParam.Phone == "" { 		response.Fail(ctx, "手机号和密码不能为空!") 		return 	} 	 	userRecord := entity.User{Phone: loginParam.Phone, Password: loginParam.Password} 	if err := service.LoginPwd(&userRecord);err != nil { 		global.GvaLogger.Error("登录失败:",zap.Any("user",userRecord)) 		response.Fail(ctx,"登录失败,账号或者密码错误!") 		return 	} 	response.OkWithData(ctx, userRecord) }
 
  | 
 
2. 注册路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | package router import ( 	v1 "52lu/go-import-template/api/v1" 	"github.com/gin-gonic/gin" )
 
 
 
  func InitUserRouter(engine *gin.Engine) { 	 	noLoginGroup := engine.Group("v1/user") 	{ 		 		noLoginGroup.POST("login", v1.Login) 		 		noLoginGroup.POST("register", v1.Register) 	} }
 
  | 
 
3.业务处理
新建:./service/user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   | 
 
  package service
  import ( 	"52lu/go-import-template/global" 	"52lu/go-import-template/model/entity" 	"52lu/go-import-template/model/request" 	"gorm.io/gorm" )
 
 
 
 
  func LoginPwd(user *entity.User) error { 	 	result := global.GvaMysqlClient.Where("phone=? and password=?", user.Phone, user.Password). 		First(user) 	return result.Error }
 
  func Register(param request.RegisterParam) (*entity.User, error) { 	user := entity.User{ 		NickName: param.NickName, 		Phone:    param.Phone, 		Password: param.Password, 	} 	_ = global.GvaMysqlClient.Transaction(func(tx *gorm.DB) error { 		if err := tx.Create(&user).Error; err != nil { 			global.GvaLogger.Sugar().Errorf("新增用户失败: %s", err) 			return err 		} 		userInfo := entity.UserInfo{ 			Uid:      user.ID, 			Birthday: param.Birthday, 			Address:  param.Address, 		} 		if err := tx.Create(&userInfo).Error; err != nil { 			global.GvaLogger.Sugar().Errorf("新增用户信息失败: %s", err) 			return err 		} 		user.UserInfo = userInfo 		return nil 	}) 	return &user, nil }
 
  |