废话不说直接开始

官网(http://gorm.io)有给出一套默认的gorm.Model模型,定义如下

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,创建时间,更新时间,删除时间,当操作数据时会自动更改相应的时间,删除时会将删除改成软删除并添加删除时间。

为什么官网已经有了还要自己写一套呢?理由有二:

1.我在做的是项目重构,原有数据库里已经有了GUID格式的主键,和此模型冲突(虽然不知为啥但官方明确指出支持复合主键但不建议这样做)

2.三个时间字段存储的是时间,而原项目存储的时间戳

既然决定重写就得先搞懂他是如何运作的,源码里找到DB对象定义如下

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

(官方源码里没有注释)

好现在知道了这些钩子方法是通过回调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 初始化数据库连接(可在main()适当位置调用)
*/
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时钩子会失效,批量操作会导致钩子无效,另外一些特殊的方法也会,所以重要数据千万不要偷懒用钩子操作

GORM自定义Gorm.Model实现自动添加时间戳的更多相关文章

  1. 【Eclipse】如何在Eclipse中如何自动添加注释和自定义注释风格

    背景简介 丰富的注释和良好的代码规范,对于代码的阅读性和可维护性起着至关重要的作用.几乎每个公司对这的要求还是比较严格的,往往会形成自己的一套编码规范.但是再实施过程中,如果全靠手动完成,不仅效率低下 ...

  2. 如何在Eclipse中如何自动添加注释和自定义注释风格

    1. 如何自动添加注释 可通过如下三种方法自动添加注释: (1)输入“/**”并回车. (2)用快捷键 Alt+Shift+J(先选中某个方法.类名或变量名). (3)在右键菜单中选择“Source ...

  3. WordPress发布文章/页面时自动添加默认的自定义字段

    如果你每篇文章或页面都需要插入同一个自定义字段和值,可以考虑在WordPress发布文章/页面时,自动添加默认的自定义字段.将下面的代码添加到当前主题的 functions.php 即可: 1 2 3 ...

  4. 打造强大的BaseModel(2):让Model实现自动映射,将字典转化成Model

    打造强大的BaseModel(1):让Model自我描述 这篇文章将讲述Model一项更高级也最常用的功能,让Model实现自动映射–将字典转化成Model(所有代码全由Swift实现) 将JSON转 ...

  5. ffmpeg为视频添加时间戳 - 手动编译ffmpeg

    FFMPEG给视频加时间戳水印 项目中需要给视频添加时间戳,理所当然最好用的办法是ffmpeg.在找到正确的做法前,还被网上的答案timecode给水了一下(水的不轻,在这里转了2天),大概是这样写的 ...

  6. 前端自动化工具gulp自动添加版本号

    之前,我介绍了学习安装并配置前端自动化工具Gulp,觉得gulp确实比grunt的配置简单很多,于是我决定再深入学习一下gulp,就去网上查了资料,发现gulp还可以自动添加版本号,这个功能就为我平时 ...

  7. Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片

    Android中仿淘宝首页顶部滚动自定义HorizontalScrollView定时水平自动切换图片 自定义ADPager 自定义水平滚动的ScrollView效仿ViewPager 当遇到要在Vie ...

  8. VS 自动添加注释

    现在大多数公司都规定程序员在程序文件的头部加上版权信息,这样每个人写的文件都可以区分开来,如果某个文件出现问题就可以快速的找到文件的创建人,用最短的时间来解决问题,常常是以下格式: //======= ...

  9. XsdGen:通过自定义Attribute与反射自动生成XSD

    前言 系统之间的数据交互往往需要事先定义一些契约,在WCF中我们需要先编写XSD文件,然后通过自动代码生成工具自动生成C#对象.对于刚刚接触契约的人来说,掌握XMLSpy之类的软件之后确实比手写XML ...

  10. 为js和css文件自动添加版本号

    web应用必然要面对缓存问题,无论前台后台都会涉足缓存.特别是对于前端而言,缓存利用的是否得当直接关系到应用的性能. 通常情况下,我们会倾向于使用缓存,因为缓存一方面可以减少网络开销,一方面可以减轻服 ...

随机推荐

  1. go语言中如何实现同步操作呢

    1. 简介 本文探讨了并发编程中的同步操作,讲述了为何需要同步以及两种常见的实现方式:sync.Cond和通道.通过比较它们的适用场景,读者可以更好地了解何时选择使用不同的同步方式.本文旨在帮助读者理 ...

  2. MYSQL数据库的创建和删除

    打开Windows命令行,输入登录用户和密码 mysql -h localhost -u root -p 创建新数据 CREATE DATABASE zoo; 查看系统中的数据库 SHOW DATAB ...

  3. python 学习之----time模块

    # timeimport time# # #1 获取时间戳# # print(time.time())# # #2 获取格式化时间对象# # #获取默认参数是当前系统时间戳# # print(time ...

  4. 【HMS Core】Health Kit查询历史数据查询数据和返回数据不一致

    [问题描述] 查询一个月运动记录,只能查询到最早5月26的数据,但是华为健康app里的数据最早为5月8日,为什么会查询不到? [解决方案] 1.需要检查是否申请了历史数据权限,查询数据时,出于对用户的 ...

  5. Elasticsearch 6.x 配置search-guard 插件

    前言   es之前版本一直无用户验证功能,不过官方有提供一x-pack,但是问题是付费.在es的6.3.2版本中,已经集成了x-pack,虽然es团队已经对x-pack开源,但是在该版本中如果需要使用 ...

  6. ARC118E Avoid Permutations

    题意 给定一个长度为 \(n\) 的排列 \(p\),在一个 \((n + 2)\times(n + 2)\) 的网格上,禁止通过 \((i, p_i)\) 这些点,每次只能向上或右走一格,从 \(( ...

  7. 如何扩展及优化CI/CD流水线?

    如今应用程序的开发通常由多个开发人员组成的团队完成.每个人或团队在项目中发挥自己的作用,然后我们发现在项目的末尾总是有几段代码需要编译,根据每个人的工作方法,管理这种集成可能会浪费很多时间.持续集成和 ...

  8. JS逆向实战19——通杀webpack逆向

    声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 网站 aHR0cHM6Ly ...

  9. Blazor前后端框架Known-V1.2.4

    V1.2.4 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. Gitee: https://gitee.com/known/Known Gith ...

  10. STM32软件I2C驱动MPU6050

    STM32软件I2C驱动MPU6050 STM32F103C8T6基于Keil MDK标准库 硬件接线 这里没有什么复杂的地方,采用MPU6050的现成模块.模块的SCL接B10,SDA接B11,这里 ...