当前位置:首页 > 文章列表 > 数据库 > MySQL > Gin实践 连载十 定制 GORM Callbacks

Gin实践 连载十 定制 GORM Callbacks

来源:SegmentFault 2023-01-24 16:03:21 0浏览 收藏

本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《Gin实践 连载十 定制 GORM Callbacks》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~

GORM itself is powered by Callbacks, so you could fully customize GORM as you want

原文地址:定制 GORM Callbacks
项目地址:https://github.com/EDDYCJY/go...

GORM 本身是由回调驱动的,所以我们可以根据需要完全定制 GORM,以此达到我们的目的

  • 注册一个新的回调
  • 删除现有的回调
  • 替换现有的回调
  • 注册回调的顺序

在 GORM 中包含以上四类 Callbacks,我们结合项目选用 “替换现有的回调” 来解决一个小痛点

问题

在 models 目录下,我们包含 tag.go 和 article.go 两个文件,他们有一个问题,就是 BeforeCreate、BeforeUpdate 重复出现了,那难道 100 个文件,就要写一百次吗?

1、tag.go

image

2、article.go

image

显然这是不可能的,如果先前你已经意识到这个问题,那挺OK,但没有的话,现在开始就要改

解决

在这里我们通过 Callbacks 来实现功能,不需要一个个文件去编写

实现Callbacks

打开 models 目录下的 models.go 文件,实现以下两个方法:

1、updateTimeStampForCreateCallback

// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
    if !scope.HasError() {
        nowTime := time.Now().Unix()
        if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
            if createTimeField.IsBlank {
                createTimeField.Set(nowTime)
            }
        }

        if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
            if modifyTimeField.IsBlank {
                modifyTimeField.Set(nowTime)
            }
        }
    }
}

在这段方法中,会完成以下功能

  • 检查是否有含有错误(db.Error)
  • scope.FieldByName
    通过
    scope.Fields()
    获取所有字段,判断当前是否包含所需字段

for _, field := range scope.Fields() {
    if field.Name == name || field.DBName == name {
        return field, true
    }
    if field.DBName == dbName {
        mostMatchedField = field
    }
}
  • field.IsBlank
    可判断该字段的值是否为空

func isBlank(value reflect.Value) bool {
    switch value.Kind() {
    case reflect.String:
        return value.Len() == 0
    case reflect.Bool:
        return !value.Bool()
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return value.Int() == 0
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return value.Uint() == 0
    case reflect.Float32, reflect.Float64:
        return value.Float() == 0
    case reflect.Interface, reflect.Ptr:
        return value.IsNil()
    }

    return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
}
  • 若为空则
    field.Set
    用于给该字段设置值,参数为
    interface{}

2、updateTimeStampForUpdateCallback

// updateTimeStampForUpdateCallback will set `ModifyTime` when updating
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
    if _, ok := scope.Get("gorm:update_column"); !ok {
        scope.SetColumn("ModifiedOn", time.Now().Unix())
    }
}
  • scope.Get(...)
    根据入参获取设置了字面值的参数,例如本文中是
    gorm:update_column
    ,它会去查找含这个字面值的字段属性
  • scope.SetColumn(...)
    假设没有指定
    update_column
    的字段,我们默认在更新回调设置
    ModifiedOn
    的值

注册Callbacks

在上面小节我已经把回调方法编写好了,接下来需要将其注册进 GORM 的钩子里,但其本身自带 Create 和 Update 回调,因此调用替换即可

在 models.go 的 init 函数中,增加以下语句

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

验证

访问 AddTag 接口,成功后检查数据库,可发现

created_on
modified_on
字段都为当前执行时间

访问 EditTag 接口,可发现

modified_on
为最后一次执行更新的时间

拓展

我们想到,在实际项目中硬删除是较少存在的,那么是否可以通过 Callbacks 来完成这个功能呢?

答案是可以的,我们在先前

Model struct
增加
DeletedOn
变量

type Model struct {
    ID int `gorm:"primary_key" json:"id"`
    CreatedOn int `json:"created_on"`
    ModifiedOn int `json:"modified_on"`
    DeletedOn int `json:"deleted_on"`
}

实现Callbacks

打开 models 目录下的 models.go 文件,实现以下方法:

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("DeletedOn")

        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 ""
}
  • scope.Get("gorm:delete_option")
    检查是否手动指定了delete_option
  • scope.FieldByName("DeletedOn")
    获取我们约定的删除字段,若存在则
    UPDATE
    软删除,若不存在则
    DELETE
    硬删除
  • scope.QuotedTableName()
    返回引用的表名,这个方法 GORM 会根据自身逻辑对表名进行一些处理
  • scope.CombinedConditionSql()
    返回组合好的条件SQL,看一下方法原型很明了

func (scope *Scope) CombinedConditionSql() string {
    joinSQL := scope.joinsSQL()
    whereSQL := scope.whereSQL()
    if scope.Search.raw {
        whereSQL = strings.TrimSuffix(strings.TrimPrefix(whereSQL, "WHERE ("), ")")
    }
    return joinSQL + whereSQL + scope.groupSQL() +
        scope.havingSQL() + scope.orderSQL() + scope.limitAndOffsetSQL()
}
  • scope.AddToVars
    该方法可以添加值作为SQL的参数,也可用于防范SQL注入

func (scope *Scope) AddToVars(value interface{}) string {
    _, skipBindVar := scope.InstanceGet("skip_bindvar")

    if expr, ok := value.(*expr); ok {
        exp := expr.expr
        for _, arg := range expr.args {
            if skipBindVar {
                scope.AddToVars(arg)
            } else {
                exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
            }
        }
        return exp
    }

    scope.SQLVars = append(scope.SQLVars, value)

    if skipBindVar {
        return "?"
    }
    return scope.Dialect().BindVar(len(scope.SQLVars))
}

注册Callbacks

在 models.go 的 init 函数中,增加以下删除的回调

db.Callback().Delete().Replace("gorm:delete", deleteCallback)

验证

重启服务,访问 DeleteTag 接口,成功后即可发现 deleted_on 字段有值

小结

在这一章节中,我们结合 GORM 完成了新增、更新、查询的 Callbacks,在实际项目中常常也是这么使用

毕竟,一个钩子的事,就没有必要自己手写过多不必要的代码了

(注意,增加了软删除后,先前的代码需要增加

deleted_on
的判断)

参考

本系列示例代码

本系列目录

文档

文中关于mysql的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Gin实践 连载十 定制 GORM Callbacks》文章吧,也可关注golang学习网公众号了解相关技术文章。

版本声明
本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
前大众点评资深研发专家对Mysql索引的解析与底层数据结构的解刨前大众点评资深研发专家对Mysql索引的解析与底层数据结构的解刨
上一篇
前大众点评资深研发专家对Mysql索引的解析与底层数据结构的解刨
针对使用非块运行和块运行并发压测对比
下一篇
针对使用非块运行和块运行并发压测对比
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 茅茅虫AIGC检测:精准识别AI生成内容,保障学术诚信
    茅茅虫AIGC检测
    茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
    19次使用
  • 赛林匹克平台:科技赛事聚合,赋能AI、算力、量子计算创新
    赛林匹克平台(Challympics)
    探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
    50次使用
  • SEO  笔格AIPPT:AI智能PPT制作,免费生成,高效演示
    笔格AIPPT
    SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
    58次使用
  • 稿定PPT:在线AI演示设计,高效PPT制作工具
    稿定PPT
    告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
    53次使用
  • Suno苏诺中文版:AI音乐创作平台,人人都是音乐家
    Suno苏诺中文版
    探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
    57次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码