共计 5538 个字符,预计需要花费 14 分钟才能阅读完成。
在上一节我们介绍到,gin 可以使用 ShouldBind 方法把参数绑定到结构体,但是没有介绍到参数校验的方式,这节我们来介绍参数校验和校验失败后转换成中文返回前端。
1. 数据校验
下面我们开始一个简单的例子:
- 在根目录的 requests 目录下新建一个 test_request.go
package requests
// 测试请求结构体 该结构体定义了请求的参数和校验规则
type TestRequest struct {Username string `form:"username" binding:"required"`}
- 在根目录的 api 目录下新建一个 test.go 的控制器,定义 test 控制器
package api
import (
"cn.sockstack/gin_demo/requests"
"github.com/gin-gonic/gin"
"net/http"
)
func Test(c *gin.Context) {
// 实例化一个 TestRequest 结构体,用于接收参数
testStruct := requests.TestRequest{}
// 接收请求参数
err := c.ShouldBind(&testStruct)
// 判断参数校验是否通过,如果不通过,把错误返回给前端
if err != nil {c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
// 校验通过,返回请求参数
c.JSON(http.StatusOK, gin.H{"params": testStruct})
}
- 在根目录的 routers 下定义 /test 路由,分别新建 init.go 和 test.go 文件初始化路由.
test.go
package routers
import (
"cn.sockstack/gin_demo/api"
"github.com/gin-gonic/gin"
)
func test(r *gin.Engine) {
// 定义 /test 路由
r.GET("/test", api.Test)
}
init.go
package routers
import "github.com/gin-gonic/gin"
func Init(r *gin.Engine) {
// 注册 test 路由
test(r)
}
- 在 main.go 中注册路由
package main
// 导入 gin 包
import (
"cn.sockstack/gin_demo/pkg/config"
"cn.sockstack/gin_demo/routers"
"fmt"
"github.com/gin-gonic/gin"
)
// 入口函数
func main() {
// 初始化一个 http 服务对象
r := gin.Default()
// 注册路由
routers.Init(r)
r.Run(fmt.Sprintf("%s:%d", config.Server.Address, config.Server.Port)) // 监听并在 0.0.0.0:8081 上启动服务
}
- 运行并访问 localhost:8081/test,不带参数会报错
{"error": "Key:'TestRequest.Username'Error:Field validation for'Username'failed on the'required'tag"}
- 运行并访问 localhost:8081/test?username=sockstack,则返回响应的参数。
{
"params": {"Username": "sockstack"}
}
上面的例子已经可以实现参数校验和接收参数了,但是校验不通过的时候返回的提示是英文的,下面我们介绍一下怎么把错误转成中文返回。
2. 校验参数失败提示自动翻译
通过查看代码我们发现 gin 默认的校验器使用的是 validator 包,并且查看文档发现 validator 是可以把英文错误翻译成中文的。
package main
import (
"fmt"
"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
)
// User contains user information
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
// use a single instance , it caches struct info
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func main() {
// NOTE: ommitting allot of error checking for brevity
en := en.New()
uni = ut.New(en, en)
// this is usually know or extracted from http 'Accept-Language' header
// also see uni.FindTranslator(...)
trans, _ := uni.GetTranslator("en")
validate = validator.New()
en_translations.RegisterDefaultTranslations(validate, trans)
translateAll(trans)
translateIndividual(trans)
translateOverride(trans) // yep you can specify your own in whatever locale you want!
}
func translateAll(trans ut.Translator) {
type User struct {
Username string `validate:"required"`
Tagline string `validate:"required,lt=10"`
Tagline2 string `validate:"required,gt=1"`
}
user := User{
Username: "Joeybloggs",
Tagline: "This tagline is way too long.",
Tagline2: "1",
}
err := validate.Struct(user)
if err != nil {
// translate all error at once
errs := err.(validator.ValidationErrors)
// returns a map with key = namespace & value = translated error
// NOTICE: 2 errors are returned and you'll see something surprising
// translations are i18n aware!!!!
// eg. '10 characters' vs '1 character'
fmt.Println(errs.Translate(trans))
}
}
func translateIndividual(trans ut.Translator) {
type User struct {Username string `validate:"required"`}
var user User
err := validate.Struct(user)
if err != nil {errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(trans))
}
}
}
func translateOverride(trans ut.Translator) {validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {t, _ := ut.T("required", fe.Field())
return t
})
type User struct {Username string `validate:"required"`}
var user User
err := validate.Struct(user)
if err != nil {errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(trans))
}
}
}
那么我们改造 gin 的校验提示。
- 在 requests 目录下新建 init.go 文件
package requests
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
var (
uni *ut.UniversalTranslator
validate *validator.Validate
trans ut.Translator
)
func init() {
// 注册翻译器
zh := zh.New()
uni = ut.New(zh, zh)
trans, _ = uni.GetTranslator("zh")
// 获取 gin 的校验器
validate := binding.Validator.Engine().(*validator.Validate)
// 注册翻译器
zh_translations.RegisterDefaultTranslations(validate, trans)
}
//Translate 翻译错误信息
func Translate(err error) map[string][]string {var result = make(map[string][]string)
errors := err.(validator.ValidationErrors)
for _, err := range errors{result[err.Field()] = append(result[err.Field()], err.Translate(trans))
}
return result
}
- 修改控制器
package api
import (
"cn.sockstack/gin_demo/requests"
"github.com/gin-gonic/gin"
"net/http"
)
func Test(c *gin.Context) {
// 实例化一个 TestRequest 结构体,用于接收参数
testStruct := requests.TestRequest{}
// 接收请求参数
err := c.ShouldBind(&testStruct)
// 判断参数校验是否通过,如果不通过,把错误返回给前端
if err != nil {c.JSON(http.StatusOK, gin.H{"error": requests.Translate(err)})
return
}
// 校验通过,返回请求参数
c.JSON(http.StatusOK, gin.H{"params": testStruct})
}
- 运行并访问 localhost:8081/test,不带参数会报错,但是报错信息已经翻译成中文了
{
"error": {
"Username": ["Username 为必填字段"]
}
}
出处 gin 从入门到实践更多精彩文章,请关注我的博客 SOCKSTACK,分享我的工作经验。
正文完