废话不说直接开始

官网(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. 代码随想录算法训练营Day49 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day49 动态规划|  121. 买卖股票的最佳时机 122.买卖股票的最佳时机II 121. 买卖股票的最佳时机 题目链接:121. 买卖股票的最佳时机 ...

  2. STL-priority_queue(ACM)

    1.无法访问v.front().v.back() 2.是一个 堆,默认为大根堆,改造后为小根堆 大根堆 重构函数(默认)(大根堆) priority_queue<int> v; 基本操作 ...

  3. Atcoder-AGC033C

    看到这道题,是个博弈论,没见过树上的,于是想到在数列里的博弈论,又联想到树的特殊形式----链. 于是我们来讨论一下链的情况(对于没有硬币的点,我们就视为它被删掉了): 讨论链的情况 发现若是选择两端 ...

  4. 全同态(Fully Homomorphic Encryption, FHE)和半同态(Partially Homomorphic Encryption, PHE)介绍

    全同态(Fully Homomorphic Encryption, FHE)和半同态(Partially Homomorphic Encryption, PHE) 全同态加密(FHE)是指一种加密方案 ...

  5. AI与健康管理:趋势与未来

    目录 引言 随着人工智能技术的不断发展,健康管理也逐渐成为了一个新的研究领域.AI技术可以为健康管理提供智能化.个性化.高效的支持,使得健康管理更加人性化和科学.本文将介绍AI与健康管理的技术原理.实 ...

  6. FPGA加速技术:在数据中心和云计算中的应用

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成与测试 4. 应用示例与代码实现讲解 4.1. 应用场景介绍 4. ...

  7. XHbuilder 需要的 ipa 签名,超详细的教程,你不看吃亏的是自己!

    今天使用 hbuilder 运行到 ios 真机的时候,突然发现还需要 ipa 签名,这是什么东东呢? 1.IPA 签名是什么? 因苹果公司禁止企业证书用于非企业内部开发者.所以开发者无法再使用DCl ...

  8. 记一次Native memory leak排查过程

    1 问题现象 路由计算服务是路由系统的核心服务,负责运单路由计划的计算以及实操与计划的匹配.在运维过程中,发现在长期不重启的情况下,有TP99缓慢爬坡的现象.此外,在每周例行调度的试算过程中,能明显看 ...

  9. Python 一大坑,配置文件中字典引用问题(拷贝)。

    大坑 +1 python 配置文件中字典引用问题 最近在开发系统时发现一个传奇的BUG, 用户未登录就可进入系统内,而且含有真实身份信息. 此问题困扰多时,反复debug.由于找不到问题原因,复现具有 ...

  10. Jmeter学习之七_使用influxdb2.7和grafana10进行Jmeter测试结果展示的方法

    Jmeter学习之七_使用influxdb2.7和grafana10进行Jmeter测试结果展示的方法 摘要 前几天验证了 线程组内的-监听器 jp@gc 相关的组件 以及验证了 server-age ...