
2.5 接口校验
在开发对应的业务模块时,第一步要考虑的问题就是如何进行入参校验。我们需要将整个项目,甚至整个团队的组件给定下来,形成一个通用规范。本节重点介绍这部分内容,并完成标签模块的接口的入参校验。
2.5.1 validator介绍
在本项目中,我们使用开源项目go-playground/validator作为本项目的基础库。该开源项目是一个基于标签对结构体和字段进行值验证的一个验证器。
我们需要单独引入这个库吗?当然不用,因为我们使用的是 gin 框架,其内部的模型绑定和验证默认使用的就是go-playground/validator,因而使用起来非常方便。
在项目根目录下执行如下安装命令:

2.5.2 业务接口校验
下面开始正式编写接口入参校验规则,即写在对应的校验结构体的字段标签上,常见的标签含义如表2-4所示。
表2-4

1.标签接口
回到项目 internal/service 目录下的 tag.go 文件,针对入参校验增加绑定和验证结构体,在路由方法前写入如下代码:

在上述代码中,对在业务接口中定义的增删改查和统计行为编写了 Request 结构体。在Request结构体中,应用了两个tag标签,分别是form和binding。它们分别代表着表单的映射字段名和入参校验的规则内容,其主要功能为实现参数绑定和参数检验。
2.文章接口
在项目internal/service目录下的article.go文件中,针对入参校验增加绑定和验证结构体。
这部分内容与标签模块的验证规则基本相同,主要是必填、长度最小限制、长度最大限制,以及要求参数值必须在某个集合内的其中之一,不再赘述。
2.5.3 国际化处理
1.编写中间件
go-playground/validator 默认的错误信息是英文,但我们的错误信息不一定要用英文,有可能用简体中文或其他文字。这时该如何处理呢?
如果是简单的国际化需求,则可以通过中间件配合语言包的方式来实现这个功能。在项目的internal/middleware目录下新建translations.go文件中,编写针对validator的语言包翻译的相关功能,新增代码如下:

在自定义中间件Translations中,针对i18n,用第三方开源库来实现相关功能,分别如下:
● go-playground/locales:多语言包,从CLDR项目(Unicode通用语言环境数据存储库)生成的一组多语言环境,主要在 i18n 软件包中使用。该库需要与 universal-translator配套使用。
● go-playground/universal-translator:通用翻译器,是一个使用CLDR数据+复数规则的Go语言i18n转换器。
● go-playground/validator/v10/translations:validator的翻译器。
在识别当前请求的语言类别时,我们通过GetHeader方法获取约定的header 参数locale,判别当前请求的语言类别是 en 还是 zh。如果有其他语言环境要求,也可以继续引入其他语言类别,因为go-playground/locales支持几乎所有语言类别。
在后续的注册步骤中,我们调用 RegisterDefaultTranslations 方法,将验证器和对应语言类型的Translator注册进来,实现验证器的多语言支持。同时将Translator存储到全局上下文中,以便在后续翻译时使用。
2.注册中间件
回到项目internal/routers目录下的router.go文件,新增中间件Translations的注册,新增代码如下:

至此,就完成了自定义验证器注册、验证器初始化、错误提示多语言等功能。
2.5.4 接口校验
在项目的pkg/app目录中新建form.go文件,写入如下代码:


在上述代码中,主要是对入参校验的方法进行了二次封装。在BindAndValid方法中,是通过 ShouldBind 进行参数绑定和入参校验的。当发生错误后,再通过在中间件 Translations 中设置的Translator对错误消息体进行具体的翻译。
另外,我们声明了ValidError相关的结构体和类型,为什么要实现其对应的Error方法呢?下面看看标准库中errors的相关代码:


标准库errors的New方法的实现非常简单。errorString是一个结构体,内含一个s字符串,其中只有一个Error方法,也可以认定为是error类型,这是为什么呢?这一切的关键在于error接口的定义,如下:

在Go 语言中,如果一个类型实现了某个interface中的所有方法,那么编译器就认为该类型实现了此interface,认为它们是“一样”的。
2.5.5 验证
回到项目 internal/routers/api/v1 下的 tag.go 文件,修改获取多个标签的 List 接口,验证validator是否正常,修改代码如下:

在命令行中利用CURL请求该接口,查看验证结果:

需要注意的是,在TagListRequest的校验规则中其实并没有required,因此它的校验规则是当有入参时,才进行校验;如果没有入参,则默认是无校验的。也就是说,即便没有state参数,也可以正常请求,命令如下:

在Response中,我们调用的是gin.H作为返回结果集,因此该输出结果正确。
2.5.6 小结
本节介绍了在gin框架中如何通过validator进行参数校验。在一些定制化场景中,常常需要自定义验证器,这时可以通过实现 binding.Validator 接口的方式,替换其自身的 validator:

也就是说,如果有定制化需求,则完全可以自己实现一个验证器,效仿前面的模式,完全替代gin框架中原本的validator。
另外,在本节的后半段进行了实战,即针对业务接口编写了入参校验规则,并对错误提示的多语言化问题(也可以理解为一个简单的国际化需求),通过中间件和多语言包的方式进行了实现。如果有更细致的国际化需求,也可以进一步的拓展。