GORM自定義Gorm.Model實現自動添加時間戳

廢話不說直接開始mysql

官網(http://gorm.io)有給出一套默認的gorm.Model模型,定義以下git

package gorm

import "time"

// Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models
//    type User struct {
//      gorm.Model
//    }
type Model struct {
    ID        uint `gorm:"primary_key"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt *time.Time `sql:"index"`
}

包含四個屬性,ID,建立時間,更新時間,刪除時間,當操做數據時會自動更改相應的時間,刪除時會將刪除改爲軟刪除並添加刪除時間。github

爲何官網已經有了還要本身寫一套呢?理由有三:sql

1.我在作的是項目重構,原有數據庫裏已經有了GUID格式的主鍵,和此模型衝突(雖然不知爲啥但官方明確指出支持複合主鍵但不建議這樣作)數據庫

2.三個時間字段存儲的是時間,而原項目存儲的時間戳數組

3.裝逼,顯擺,閒的沒事app

既然決定重寫就得先搞懂他是如何運做的,源碼裏找到DB對象定義以下tcp

db相關信息,每次綁定不一樣的value,操做對象例如product{}
type DB struct {
    Value        interface{}
    Error        error
    RowsAffected int64

    // single db
    db                SQLCommon //原生db.sql對象,包含query相關的原生方法
    blockGlobalUpdate bool
    logMode           int
    logger            logger
    search            *search //保存搜索的條件where, limit, group,好比調用db.clone()時,會指定search
    values            map[string]interface{}

    // global db
    parent        *DB
    callbacks     *Callback //當前sql綁定的函數調用鏈
    dialect       Dialect //不一樣數據庫適配註冊sql.db
    singularTable bool
}

// 保存當前sql執行相關信息
type Scope struct {
    Search          *search // 檢索條件
    Value           interface{}
    SQL             string //sql
    SQLVars         []interface{}
    db              *DB //sql.db
    instanceID      string
    primaryKeyField *Field
    skipLeft        bool
    fields          *[]*Field //字段
    selectAttrs     *[]string
}

// 保存各類操做須要執行的調用鏈,例如create函數,須要調用creates數組中全部的函數
type Callback struct {
    creates    []*func(scope *Scope)
    updates    []*func(scope *Scope)
    deletes    []*func(scope *Scope)
    queries    []*func(scope *Scope)
    rowQueries []*func(scope *Scope)
    processors []*CallbackProcessor
}

代碼引用自: https://blog.csdn.net/qq_17612199/article/details/79437795函數

(官方源碼裏沒有註釋)flex

好如今知道了這些鉤子方法是經過回調DB對象的Callback實現的而Callback下又是一個個的切片說明回調並不僅是一個,找到官方的Creates回調

// Define callbacks for creating
func init() {
    DefaultCallback.Create().Register("gorm:begin_transaction", beginTransactionCallback)
    DefaultCallback.Create().Register("gorm:before_create", beforeCreateCallback)
    DefaultCallback.Create().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
    DefaultCallback.Create().Register("gorm:update_time_stamp", updateTimeStampForCreateCallback)
    DefaultCallback.Create().Register("gorm:create", createCallback)
    DefaultCallback.Create().Register("gorm:force_reload_after_create", forceReloadAfterCreateCallback)
    DefaultCallback.Create().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
    DefaultCallback.Create().Register("gorm:after_create", afterCreateCallback)
    DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}

找到‘update_time_stamp’對應的方法‘updateTimeStampForCreateCallback’

// updateTimeStampForCreateCallback will set `CreatedAt`, `UpdatedAt` when creating
func updateTimeStampForCreateCallback(scope *Scope) {
    if !scope.HasError() {
        now := NowFunc()

        if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
            if createdAtField.IsBlank {
                createdAtField.Set(now)
            }
        }

        if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok {
            if updatedAtField.IsBlank {
                updatedAtField.Set(now)
            }
        }
    }
}
// NowFunc returns current time, this function is exported in order to be able
// to give the flexibility to the developer to customize it according to their
// needs, e.g:
// gorm.NowFunc = func() time.Time {
// return time.Now().UTC()
// }
var NowFunc = func() time.Time {
    return time.Now()
}
 

好了,到了這一步相信各位已經知道該如何重寫了,須要注意的是上邊DefaultCallback.Create()用到的是Register()方法,Create()定義以下

// Register a new callback, refer `Callbacks.Create`
func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) {
    if cp.kind == "row_query" {
        if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" {
            log.Printf("Registing RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName)
            cp.before = "gorm:row_query"
        }
    }

    cp.name = callbackName
    cp.processor = &callback
    cp.parent.processors = append(cp.parent.processors, cp)
    cp.parent.reorder()
}

既然已經註冊過了那重寫的話就不能用Register了,源碼中找到一個Replace方法用於替換原有的回調,其定義以下

// Replace a registered callback with new callback
//     db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) {
//           scope.SetColumn("Created", now)
//           scope.SetColumn("Updated", now)
//     })
func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) {
    log.Printf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum())
    cp.name = callbackName
    cp.processor = &callback
    cp.replace = true
    cp.parent.processors = append(cp.parent.processors, cp)
    cp.parent.reorder()
}

好了,該掌握的都掌握瞭如今開始實際搞一下試一試

項目中model文件夾用於存放映射數據庫模型,新建changemodel.go文件

package model

type ChangeModel struct{
    CreatedTime int32 
    UpdatedTime int32
    DeletedTime int32
}

在須要用到事件記錄的模型裏引入ChangeModel如

package model

//User
type User struct{
    ChangeModel
    UserID string `gorm:"primary_key;size:36"`
    Name string `gorm:"size:20"`
    PassWord string `gorm:"size:40"`
}

好了到此爲止自定義的公共字段已經弄好了,接下來是最重要的重寫回調並注入

打開本身的數據庫鏈接文件

package mysqltools

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "log"
    "sync"
    "time"
)

type MysqlConnectiPool struct {
}

var instance *MysqlConnectiPool
var once sync.Once

var db *gorm.DB
var err_db error

func GetInstance() *MysqlConnectiPool {
    once.Do(func() {
        instance = &MysqlConnectiPool{}
    })
    return instance
}

/*
* @fuc 初始化數據庫鏈接(可在mail()適當位置調用)
*/
func (m *MysqlConnectiPool) InitDataPool() (issucc bool) {
    db, err_db = gorm.Open("mysql", "name:password@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
    fmt.Println(err_db)
    if err_db != nil {
        log.Fatal(err_db)
        return false
    }
    db.DB().SetMaxIdleConns(10)
    db.DB().SetMaxOpenConns(100)
    db.DB().SetConnMaxLifetime(time.Hour)
    db.Callback().Create().Replace("gorm:update_time_stamp",updateTimeStampForCreateCallback)
    db.Callback().Update().Replace("gorm:update_time_stamp",updateTimeStampForUpdateCallback)
    db.Callback().Delete().Replace("gorm:delete", deleteCallback)
    return true
}
func (m *MysqlConnectiPool) GetMysqlDB() (db_con *gorm.DB) {
    return db
}


// // 註冊新建鉤子在持久化以前
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
   
    if !scope.HasError() {
        nowTime := time.Now().Unix()
        if createTimeField, ok := scope.FieldByName("CreatedTime"); ok {
            if createTimeField.IsBlank {
                createTimeField.Set(nowTime)
            }
        }

        if modifyTimeField, ok := scope.FieldByName("UpdatedTime"); ok {
            if modifyTimeField.IsBlank {
                modifyTimeField.Set(nowTime)
            }
        }
    }
}
// 註冊更新鉤子在持久化以前
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
    if _, ok := scope.Get("gorm:update_column"); !ok {
        scope.SetColumn("UpdatedTime", time.Now().Unix())
    }
}
// 註冊刪除鉤子在刪除以前
func deleteCallback(scope *gorm.Scope) {
    if !scope.HasError() {
        var extraOption string
        if str, ok := scope.Get("gorm:delete_option"); ok {
            extraOption = fmt.Sprint(str)
        }

        deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedTime")

        if !scope.Search.Unscoped && hasDeletedOnField {
            scope.Raw(fmt.Sprintf(
                "UPDATE %v SET %v=%v%v%v",
                scope.QuotedTableName(),
                scope.Quote(deletedOnField.DBName),
                scope.AddToVars(time.Now().Unix()),
                addExtraSpaceIfExist(scope.CombinedConditionSql()),
                addExtraSpaceIfExist(extraOption),
            )).Exec()
        } else {
            scope.Raw(fmt.Sprintf(
                "DELETE FROM %v%v%v",
                scope.QuotedTableName(),
                addExtraSpaceIfExist(scope.CombinedConditionSql()),
                addExtraSpaceIfExist(extraOption),
            )).Exec()
        }
    }
}
func addExtraSpaceIfExist(str string) string {
    if str != "" {
        return " " + str
    }
    return ""
}

能夠看到三個對應的回調已經寫好並替換掉默認的鉤子回調

    db.Callback().Create().Replace("gorm:update_time_stamp",updateTimeStampForCreateCallback)
    db.Callback().Update().Replace("gorm:update_time_stamp",updateTimeStampForUpdateCallback)
    db.Callback().Delete().Replace("gorm:delete", deleteCallback)

注意

鉤子並非萬能的,當使用Raw()寫原生SQL時鉤子會失效,批量操做會致使鉤子無效,另一些特殊的方法也會,因此重要數據千萬不要偷懶用鉤子操做

相關文章
相關標籤/搜索