Gin 集成: 集成全功能 ORM 框架 -gorm-

  1. 介紹

gorm是一個使用Go語言編寫的ORM框架。它文檔齊全,對開發者友好,支持主流數據庫。具體使用可參考之前的文章 Go 常用包 (十九): 全功能 ORM 框架 (gorm)

1.1 集成流程

1.2 涉及目錄

  1. 配置

2.1 編輯主配置./config.yaml

mysql:
  host: 127.0.0.1
  port: 3306
  user: root
  password: root
  database: test
  charset: utf8mb4 # 要支持完整的UTF-8編碼,需設置成: utf8mb4
  parseTime: true # 解析time.Time類型
  timeZone: Local # 時區,若設置 Asia/Shanghai,需寫成: Asia%2fShanghai
  defaultStringSize: 255 # string 類型字段的默認長度
  disableDatetimePrecision: true # 禁用 datetime 精度,MySQL 5.6 之前的數據庫不支持
  skipInitializeWithVersion: false # 根據當前 MySQL 版本自動配置
  autoMigrate: true # 開啓時,每次服務啓動都會根據實體創建/更新表結構
  slowSql: 50ms # 慢sql時間。單位毫秒
  logLevel: info # error、info、warn
  ignoreRecordNotFoundError: true # 是否忽略ErrRecordNotFound(未查到記錄錯誤)
  gorm: # gorm配置項disableForeignKeyConstraintWhenMigrating
    skipDefaultTx: false # 是否跳過默認事務
    tablePrefix: "app_" #表前綴
    singularTable: true # 是否使用單數表名(默認複數),啓用後,User結構體表將是user
    coverLogger: true # 是否覆蓋默認logger
    prepareStmt: false # 執行任何SQL時都會創建一個prepared statement並將其緩存,以提高後續的效率
    disableForeignKeyConstraintWhenMigrating: true #在AutoMigrate 或 CreateTable 時,GORM 會自動創建外鍵約束,若要禁用該特性,可將其設置爲 true

2.2 編輯結構體

新增./config/mysql.go文件:

/**
 * @Description mysql配置信息
 **/
package config

import "time"

// MySQL信息
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"`                   //要支持完整的UTF-8編碼,需設置成: utf8mb4
 AutoMigrate               bool          `yaml:"autoMigrate"`               // 初始化時調用數據遷移
 ParseTime                 bool          `yaml:"parseTime"`                 //解析time.Time類型
 TimeZone                  string        `yaml:"timeZone"`                  // 時區,若設置 Asia/Shanghai,需寫成: Asia%2fShanghai
 DefaultStringSize         uint          `yaml:"defaultStringSize"`         // string 類型字段的默認長度
 DisableDatetimePrecision  bool          `yaml:"disableDatetimePrecision"`  // 禁用 datetime 精度
 SkipInitializeWithVersion bool          `yaml:"skipInitializeWithVersion"` // 根據當前 MySQL 版本自動配置
 Gorm                      gorm          `yaml:"gorm"`
 SlowSql                   time.Duration `yaml:"slowSql"`                   //慢SQL
 LogLevel                  string        `yaml:"logLevel"`                  // 日誌記錄級別
 IgnoreRecordNotFoundError bool          `yaml:"ignoreRecordNotFoundError"` // 是否忽略ErrRecordNotFound(未查到記錄錯誤)
}
// gorm 配置信息
type gorm struct {
 SkipDefaultTx   bool   `yaml:"skipDefaultTx"`                            //是否跳過默認事務
 CoverLogger     bool   `yaml:"coverLogger"`                              //是否覆蓋默認logger
 PreparedStmt    bool   `yaml:"prepareStmt"`                              // 設置SQL緩存
 CloseForeignKey bool   `yaml:"disableForeignKeyConstraintWhenMigrating"` // 禁用外鍵約束
 TablePrefix     string `yaml:"tablePrefix"`                              // 表前綴
 SingularTable   bool   `yaml:"singularTable"`                            //是否使用單數表名(默認複數),啓用後,User結構體表將是user
}

2.3 嵌入主配置

編輯文件:./config/app.go

...
// ServerConfig 配置信息
type ServerConfig struct {
  ....
 Mysql mysql `yaml:"mysql"` // 嵌入MySQL配置
}

2.4 定義全局變量

編輯文件:./global/global.go

// 變量
var (
 ...
 GvaMysqlClient *gorm.DB            //Mysql客戶端
)
  1. 代碼實現

3.1  集成入口

1. 編輯 main.go

func init() {
 ...
 // 初始化gorm
 initialize.InitGorm()
}
func main() {
 // 啓動服務
 ...
}

2. initialize.InitGorm()

新增文件:./initialize/gorm.go

// 初始化mysql客戶端
func InitGorm() {
 mysqlConfig := global.GvaConfig.Mysql
 // user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
 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)
 // 設置gorm配置
 gormConfig := &gorm.Config{
  SkipDefaultTransaction: mysqlConfig.Gorm.SkipDefaultTx, //是否跳過默認事務
  // 命名策略
  NamingStrategy: schema.NamingStrategy{
   TablePrefix:   mysqlConfig.Gorm.TablePrefix,
   SingularTable: mysqlConfig.Gorm.SingularTable,
  },
  // 執行任何SQL時都會創建一個prepared statement並將其緩存,以提高後續的效率
  PrepareStmt: mysqlConfig.Gorm.PreparedStmt,
  //在AutoMigrate 或 CreateTable 時,GORM 會自動創建外鍵約束,若要禁用該特性,可將其設置爲 true
  DisableForeignKeyConstraintWhenMigrating: mysqlConfig.Gorm.CloseForeignKey,
 }
 // 是否覆蓋默認sql配置
 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

// 設置新的Logger
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)
 // 日誌級別映射 error、info、warn
 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,                   //慢SQL時間
  LogLevel:                  logLevel,                                         // 記錄日誌級別
  IgnoreRecordNotFoundError: global.GvaConfig.Mysql.IgnoreRecordNotFoundError, // 是否忽略ErrRecordNotFound(未查到記錄錯誤)
  Colorful:                  false,                                            // 開關顏色
 })
 gConfig.Logger = newLogger
}

3.3 數據遷移AutoMigrate

1. 新增實體

新增文件:./model/entity/user.go

/**
 * @Description 用戶相關的實體
 **/
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

/**
 * @Description mysql遷移
 **/
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()
}
  1. 場景示例

下面以登錄和註冊場景,演示使用和請求流程。

4.1 調用流程

4.2 代碼實現

1. 編輯 api

新建./api/v1/user_api.go

/**
 * @Description 用戶相關接口
 **/
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"
)

/**
 * @description:  用戶註冊
 * @param ctx
 */
func Register(ctx *gin.Context) {
 // 綁定參數
 var registerParam request.RegisterParam
 _ = ctx.ShouldBindJSON(®isterParam)
 // todo 參數校驗
 // 調用註冊
 register, err := service.Register(registerParam)
 if err != nil {
  response.Fail(ctx,"註冊失敗!")
  return
 }
 response.OkWithData(ctx,register)
}

/**
 * @description:  用戶賬號密碼登錄
 * @param ctx
 */
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. 註冊路由

package router
import (
 v1 "52lu/go-import-template/api/v1"
 "github.com/gin-gonic/gin"
)
/**
 * @description: 用戶相關的路由
 * @param engine
 */
func InitUserRouter(engine *gin.Engine) {
 // 不需要登錄的路由
 noLoginGroup := engine.Group("v1/user")
 {
  // 登錄
  noLoginGroup.POST("login", v1.Login)
  // 註冊
  noLoginGroup.POST("register", v1.Register)
 }
}

3. 業務處理

新建:./service/user.go

/**
 * @Description TODO
 **/
package service

import (
 "52lu/go-import-template/global"
 "52lu/go-import-template/model/entity"
 "52lu/go-import-template/model/request"
 "gorm.io/gorm"
)

/**
 * @description: 賬戶密碼登錄
 * @param user
 */
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
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IM3bWTbfoqpF7AXkej0yRg