![Go语言编程之旅:一起用Go做项目](https://wfqqreader-1252317822.image.myqcloud.com/cover/485/32441485/b_32441485.jpg)
2.6 模块开发:标签管理
在初步完成业务接口的入参校验的逻辑处理后,接下来进入业务模块的业务逻辑开发,本节我们编写标签模块的接口代码,涉及的接口如表2-5所示。
表2-5
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_99_3.jpg?sign=1739701829-4OCmHWFXcSw99kvKo99ikeb4mFv41h5j-0-058253667f29c2159c4f40dccc41464d)
2.6.1 新建model方法
首先针对标签表进行处理,在项目的internal/model目录下新建tag.go文件,然后对标签模块的模型操作进行封装,并且只与实体产生关系,代码如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_100_1.jpg?sign=1739701829-mrw4N0EDXk34tufaeFLSdPGjp5efrhPk-0-fe82858431500c394923e5ce3b648d99)
● Model:指定运行 DB 操作的模型实例,默认解析该结构体的名字为表名,格式为大写驼峰转小写下划线驼峰。若情况特殊,也可以编写该结构体的TableName方法,用于指定其对应返回的表名。
● Where:设置筛选条件,接受map、struct或string作为条件。
● Offset:偏移量,用于指定开始返回记录之前要跳过的记录数。
● Limit:限制检索的记录数。
● Find:查找符合筛选条件的记录。
● Updates:更新所选字段。
● Delete:删除数据。
● Count:统计行为,用于统计模型的记录数。
需要注意的是,在上述代码中,我们采取的是将 db*gorm.DB 作为函数首参数传入的方式,而在业界中也有另外一种方式,是基于结构体传入的,两者本质上都可以实现目的,读者根据实际情况(使用习惯、项目规范等)进行选用即可,其各有利弊。
2.6.2 处理model回调
在编写model代码时,并没有对公共字段CreatedOn、ModifiedOn、deletedOn和IsDel进行处理,难道不是在每一个DB操作中进行设置和修改吗?
显然,在通用场景下这并不是最好的方案。如果对每一个 DB 操作都设置公共字段值,那么不仅多了很多重复的代码,而且在调整公共字段时工作量也会翻倍。
我们可以采用设置model callback的方式实现对公共字段的处理。本项目使用的ORM库是GORM,GORM 本身是提供回调支持的,因此我们可以根据自己的需要自定义 GORM 的回调操作。在GORM中,可以进行的回调行为如下:
● 注册一个新的回调。
● 删除现有的回调。
● 替换现有的回调。
● 注册回调的先后顺序。
在本项目中使用的是“替换现有的回调”这一行为。打开项目 internal/model 目录下的model.go文件,开始编写model的回调代码,下述新增的回调代码均写在NewDBEngine方法后:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_101_1.jpg?sign=1739701829-TrEWd5RNH7MBea0zEYAmuiYJCgJQY1dx-0-8f2b2c89221082bd4f4d921d342ec977)
1.新增行为的回调
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_102_1.jpg?sign=1739701829-ir2UrzldqpOBGotY79RN913KSph8vOYh-0-ec0d2bab284dda719e70e52d079dfd9c)
● 通过调用scope.FieldByName方法,获取当前是否包含所需的字段。
● 通过判断Field.IsBlank的值,可以得知该字段的值是否为空。
● 若为空,则调用Field.Set方法给该字段设置值。入参类型为interface{},即内部是通过反射进行一系列操作赋值的。
2.更新行为的回调
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_102_2.jpg?sign=1739701829-sMjWonIVZU5doHASgjmThuyRTrQ76D9B-0-e278425aa63e2fde521acb0142de9de6)
● 通 过 调 用 scope.Get("gorm:update_column") 来 获 取 当 前 设 置 的 标 识gorm:update_column的字段属性。
● 若不存在,即没有自定义设置 update_column,则在更新回调内设置默认字段ModifiedOn的值为当前的时间戳。
3.删除行为的回调
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_102_3.jpg?sign=1739701829-dgaNehu7eTRwJUcCwrS6ppTTlKYMb6yk-0-813ee6c2b2d9752d58ae12683cc8c0b7)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_103_1.jpg?sign=1739701829-eyZXBaJvmGOIANrj6v57Es3rVgPY7hAT-0-be7e86008304223b5db397415075414f)
● 通过调用scope.Get("gorm:delete_option")来获取当前设置的标识gorm:delete_option的字段属性。
● 判断是否存在DeletedOn和IsDel字段。若存在,则执行UPDATE操作进行软删除(修改DeletedOn和IsDel的值),否则执行DELETE操作进行硬删除。
● 调用scope.QuotedTableName方法获取当前引用的表名,并调用一系列方法对SQL语句的组成部分进行处理和转移。在完成一些所需参数设置后,调用scope.CombinedConditionSql方法完成SQL语句的组装。
4.注册回调行为
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_103_2.jpg?sign=1739701829-svdyWGcNA4lqGjBtJmsdVevEtPsHY61x-0-e2cc9c6e46d46d6c2a2d1747f3df3aff)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_104_1.jpg?sign=1739701829-YmnsWSyS0z0WWFZkCFZ9e2shXJNa1PRz-0-ebaecefb7b52a02bd3540b144e2f070e)
回到 NewDBEngine 方法中,对上述的三个 Callback 方法进行回调注册,只有这样才能让我们的应用程序可以真正地使用。至此,公共字段的处理就完成了。
2.6.3 新建dao方法
在项目的internal/dao目录下新建dao.go文件,写入如下代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_104_2.jpg?sign=1739701829-mpWi4S1HgU9VPsVXYjILRjcAm4liXL3l-0-88a8ff8db91b470d99f19f5e8ad4b230)
在同层级下新建tag.go文件,用于处理标签模块的dao操作,写入如下代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_104_3.jpg?sign=1739701829-om4REHs7T7SDgHbkUR4sVGoqcV7fMFFf-0-23e3ab3d03ccdcf7a2c9a20eedd285f2)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_105_1.jpg?sign=1739701829-Ccxas0XRt89HNlYJFTBip58ZLIb45t2T-0-2ab24719cdc3f925566e22c4e70b60bd)
在上述代码中,主要是在dao层进行了数据访问对象的封装,并对业务所需的字段进行了处理。
2.6.4 新建service方法
在项目的internal/service目录下新建service.go文件,写入如下代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_105_2.jpg?sign=1739701829-54UwaNNDHm83j5KHhj2VnFwfXHFb4QYM-0-d0b577a056bb8e9334c53202e834c84b)
在同层级下新建tag.go文件,用于处理标签模块的业务逻辑,写入如下代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_105_3.jpg?sign=1739701829-w22oapYIPYlz5XgpRCDn3hyXYuRYBkqQ-0-826431a25f9c5b89bdc123445f829362)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_106_1.jpg?sign=1739701829-LKkMpFuvdRfAHNtTUZRpvCJeRUX249zP-0-f0992e982474824ca333001f3bf4e590)
在上述代码中,主要定义了 Request 结构体作为接口入参的基准。由于本项目并不复杂,所以直接把Request结构体放在了service层中以便使用。若后续业务不断增长,程序越来越复杂,service层也变得冗杂,则可以考虑抽离一层接口校验层,以便解耦逻辑。
另外,我们还在service层中进行了一些简单的逻辑封装。在应用分层中,service层主要是对业务逻辑进行封装,如果有一些业务聚合和处理可以在该层进行编码,则可以较好地隔离上下两层的逻辑。
2.6.5 新增业务错误码
在项目的pkg/errcode下新建module_code.go文件,针对标签模块,写入如下错误代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_106_2.jpg?sign=1739701829-cbScvsOcPvwWlq0BdwW9EQPW455NvxBR-0-05229758c4ce30f748a2be1ead54efb6)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_107_1.jpg?sign=1739701829-57CExfPckdY91UFFpxbY81Jc1chtjZPm-0-4f207f7cfa944e28bde8f2d387c4263a)
2.6.6 新增路由方法
打开internal/routers/api/v1项目目录下的tag.go文件,写入如下代码:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_107_2.jpg?sign=1739701829-X1UQVgjULlX9yTgkEGf8mtNRQNu4TyFK-0-142e5ba2d83745c6fbed1a30c3d17dd0)
在上述代码中,我们完成了获取标签列表接口的处理方法,在方法中完成了入参校验和绑定、获取标签总数、获取标签列表、序列化结果集四大功能板块的逻辑串联和日志错误处理。
需要注意的是,入参校验和绑定的处理代码基本相同,因此在后续代码中不再重复。继续写入创建标签、更新标签、删除标签的接口处理方法,代码如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_108_1.jpg?sign=1739701829-htWoW3daMauJG14xaA0DnQSa9dPZSWkP-0-f61163e2e2815a28028d2c310cb36a9e)
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_109_1.jpg?sign=1739701829-m2wYPgF44XJJTr0GriMEa77nVaxjLyrv-0-81e451d0987d4d1ac2f007e710174867)
2.6.7 验证接口
重新启动服务,即再次执行go run main.go,待启动信息正常后,对标签模块的接口进行验证。注意,验证示例中的{id}指占位符,因而填写实际调用中希望处理的标签ID即可。
(1)新增标签。
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_109_2.jpg?sign=1739701829-WnOkleIco9ycHBO9jmy1rX8HgisfYNKu-0-abee4ca991ff92db821974a0b8e5162f)
(2)获取标签列表。
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_109_3.jpg?sign=1739701829-zsSacM921aOFAiir2mUe4VcouaU1qYTi-0-76098a2748c05dfd20248a2e99a694d7)
(3)修改标签。
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_109_4.jpg?sign=1739701829-3QYqFcB2XHIu3GljCeWxhHWNnjjZ4bzc-0-c5697a2780cf3509a002f29a7b09baf2)
(4)删除标签。
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_109_5.jpg?sign=1739701829-Wzc2rTGErFX7H32qDnQzoYQ8o6wyCOVK-0-f12ba2e91eff8847d54c426cf6dbd859)
2.6.8 发现问题
在完成接口检验后,还需确定数据库内的数据变更是否正确。在经过一系列的对比后发现,在调用修改标签的接口时,通过接口入参,我们希望将id为1的标签状态修改为0,但是经对比后发现,在数据库内它的状态值仍然还是1,而且SQL语句内也没有出现state字段的设置,控制台输出的SQL语句如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_110_1.jpg?sign=1739701829-8t4eQUAcUn69TNQVEy6dg1VNuYnFHkgx-0-954b0eb88f3297277d389cedb73c14a6)
甚至在我们做其他类似的验证时,发现只要字段的值为零值,GORM就不会对该字段进行变更,这是为什么呢?
实际上,我们先入为主地认为它一定会变更是不对的。在我们的程序中,是使用 struct 进行更新操作的。而在GORM中,当使用struct进行更新操作时,GORM是不会对值为零值的字段进行变更的。其根本原因在于,在识别这个结构体中的字段值时,很难判定是真的为零值,还是外部传入的该类型的值恰好为零值。对此,GORM并没有过多地去做特殊识别。
2.6.9 解决问题
修改项目internal/model目录下的tag.go文件中的Update方法,代码如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_110_2.jpg?sign=1739701829-G857vRzTVOGwjjE86vrtpMzOSemtSAKE-0-dd1529a4f99e90b8e346925962674389)
修改项目internal/dao目录下的tag.go文件中的UpdateTag方法,代码如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_110_3.jpg?sign=1739701829-iqeUjCe5gwyrhKfOaqoRg3nmXvtKPPj9-0-61d071e031819a09130e955eedeee1b6)
重新运行程序,请求修改标签接口,命令如下:
![](https://epubservercos.yuewen.com/4AF54D/17518673407513106/epubprivate/OEBPS/Images/39074_110_4.jpg?sign=1739701829-cFPcjfTdfzbbRnLtwg9Mzu6skgxQOaDu-0-05a30604054bf81336320afa2fe13869)
检查数据是否被正常修改,在正确的情况下,该id为1的标签,modified_by为eddycjy,modified_on应修改为当前时间戳,state为0。
2.6.10 小结
本节我们针对 “标签管理”模块 进行了具体的开发,其中涉及model、dao、service、router的相关方法,以及业务错误码的编写和处理。接下来是开发“文章管理”模块,请读者根据本节的经验和文章数据库结构,自行构思设计思路,然后进行实践,在书中不再赘述,若有疑惑可参考源码。