Go语言编程之旅:一起用Go做项目
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.6 模块开发:标签管理

在初步完成业务接口的入参校验的逻辑处理后,接下来进入业务模块的业务逻辑开发,本节我们编写标签模块的接口代码,涉及的接口如表2-5所示。

表2-5

2.6.1 新建model方法

首先针对标签表进行处理,在项目的internal/model目录下新建tag.go文件,然后对标签模块的模型操作进行封装,并且只与实体产生关系,代码如下:

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方法后:

1.新增行为的回调

通过调用scope.FieldByName方法,获取当前是否包含所需的字段。

通过判断Field.IsBlank的值,可以得知该字段的值是否为空。

若为空,则调用Field.Set方法给该字段设置值。入参类型为interface{},即内部是通过反射进行一系列操作赋值的。

2.更新行为的回调

通 过 调 用 scope.Get("gorm:update_column") 来 获 取 当 前 设 置 的 标 识gorm:update_column的字段属性。

若不存在,即没有自定义设置 update_column,则在更新回调内设置默认字段ModifiedOn的值为当前的时间戳。

3.删除行为的回调

通过调用scope.Get("gorm:delete_option")来获取当前设置的标识gorm:delete_option的字段属性。

判断是否存在DeletedOn和IsDel字段。若存在,则执行UPDATE操作进行软删除(修改DeletedOn和IsDel的值),否则执行DELETE操作进行硬删除。

调用scope.QuotedTableName方法获取当前引用的表名,并调用一系列方法对SQL语句的组成部分进行处理和转移。在完成一些所需参数设置后,调用scope.CombinedConditionSql方法完成SQL语句的组装。

4.注册回调行为

回到 NewDBEngine 方法中,对上述的三个 Callback 方法进行回调注册,只有这样才能让我们的应用程序可以真正地使用。至此,公共字段的处理就完成了。

2.6.3 新建dao方法

在项目的internal/dao目录下新建dao.go文件,写入如下代码:

在同层级下新建tag.go文件,用于处理标签模块的dao操作,写入如下代码:

在上述代码中,主要是在dao层进行了数据访问对象的封装,并对业务所需的字段进行了处理。

2.6.4 新建service方法

在项目的internal/service目录下新建service.go文件,写入如下代码:

在同层级下新建tag.go文件,用于处理标签模块的业务逻辑,写入如下代码:

在上述代码中,主要定义了 Request 结构体作为接口入参的基准。由于本项目并不复杂,所以直接把Request结构体放在了service层中以便使用。若后续业务不断增长,程序越来越复杂,service层也变得冗杂,则可以考虑抽离一层接口校验层,以便解耦逻辑。

另外,我们还在service层中进行了一些简单的逻辑封装。在应用分层中,service层主要是对业务逻辑进行封装,如果有一些业务聚合和处理可以在该层进行编码,则可以较好地隔离上下两层的逻辑。

2.6.5 新增业务错误码

在项目的pkg/errcode下新建module_code.go文件,针对标签模块,写入如下错误代码:

2.6.6 新增路由方法

打开internal/routers/api/v1项目目录下的tag.go文件,写入如下代码:

在上述代码中,我们完成了获取标签列表接口的处理方法,在方法中完成了入参校验和绑定、获取标签总数、获取标签列表、序列化结果集四大功能板块的逻辑串联和日志错误处理。

需要注意的是,入参校验和绑定的处理代码基本相同,因此在后续代码中不再重复。继续写入创建标签、更新标签、删除标签的接口处理方法,代码如下:

2.6.7 验证接口

重新启动服务,即再次执行go run main.go,待启动信息正常后,对标签模块的接口进行验证。注意,验证示例中的{id}指占位符,因而填写实际调用中希望处理的标签ID即可。

(1)新增标签。

(2)获取标签列表。

(3)修改标签。

(4)删除标签。

2.6.8 发现问题

在完成接口检验后,还需确定数据库内的数据变更是否正确。在经过一系列的对比后发现,在调用修改标签的接口时,通过接口入参,我们希望将id为1的标签状态修改为0,但是经对比后发现,在数据库内它的状态值仍然还是1,而且SQL语句内也没有出现state字段的设置,控制台输出的SQL语句如下:

甚至在我们做其他类似的验证时,发现只要字段的值为零值,GORM就不会对该字段进行变更,这是为什么呢?

实际上,我们先入为主地认为它一定会变更是不对的。在我们的程序中,是使用 struct 进行更新操作的。而在GORM中,当使用struct进行更新操作时,GORM是不会对值为零值的字段进行变更的。其根本原因在于,在识别这个结构体中的字段值时,很难判定是真的为零值,还是外部传入的该类型的值恰好为零值。对此,GORM并没有过多地去做特殊识别。

2.6.9 解决问题

修改项目internal/model目录下的tag.go文件中的Update方法,代码如下:

修改项目internal/dao目录下的tag.go文件中的UpdateTag方法,代码如下:

重新运行程序,请求修改标签接口,命令如下:

检查数据是否被正常修改,在正确的情况下,该id为1的标签,modified_by为eddycjy,modified_on应修改为当前时间戳,state为0。

2.6.10 小结

本节我们针对 “标签管理”模块 进行了具体的开发,其中涉及model、dao、service、router的相关方法,以及业务错误码的编写和处理。接下来是开发“文章管理”模块,请读者根据本节的经验和文章数据库结构,自行构思设计思路,然后进行实践,在书中不再赘述,若有疑惑可参考源码。