参数校验错误信息中文处理

Bash
• 阅读 7582

在上一节我们介绍到,gin可以使用ShouldBind方法把参数绑定到结构体,但是没有介绍到参数校验的方式,这节我们来介绍参数校验和校验失败后转换成中文返回前端。

1.数据校验

下面我们开始一个简单的例子:

  1. 在根目录的requests目录下新建一个test_request.go
package requests

//测试请求结构体 该结构体定义了请求的参数和校验规则
type TestRequest struct {
    Username string `form:"username" binding:"required"`
}
  1. 在根目录的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})
}
  1. 在根目录的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)
}
  1. 在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 上启动服务
}
  1. 运行并访问localhost:8081/test,不带参数会报错
{
    "error": "Key: 'TestRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag"
}
  1. 运行并访问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的校验提示。

  1. 在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
}
  1. 修改控制器
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})
}
  1. 运行并访问localhost:8081/test,不带参数会报错,但是报错信息已经翻译成中文了
{
    "error": {
        "Username": [
            "Username为必填字段"
        ]
    }
}
出处gin从入门到实践更多精彩文章,请关注我的博客SOCKSTACK,分享我的工作经验。
点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
Spring校验@RequestParams和@PathVariables参数
我们在写RestAPI接口时候会用到很多的@RequestParam和@PathVariable进行参数的传递,但是在校验的时候,不像使用@RequestBody那样的直接写在实体类中,我们这篇文章讲解一下如何去校验这些参数。依赖配置要使用JavaValidationAPI,我们必须添加validationap
Wesley13 Wesley13
3年前
mysql中时间比较的实现
MySql中时间比较的实现unix\_timestamp()unix\_timestamp函数可以接受一个参数,也可以不使用参数。它的返回值是一个无符号的整数。不使用参数,它返回自1970年1月1日0时0分0秒到现在所经过的秒数,如果使用参数,参数的类型为时间类型或者时间类型的字符串表示,则是从1970010100:00:0
Wesley13 Wesley13
3年前
MySQL 字符集和校验规则工作流程
MySQL字符集和校验规则工作原理_字符编码相关参数__数据流中的转码过程__校验规则_Tips:字符集和校验规则总是相伴的一从简单的建库语句开始CREATEDATABASEIFNOTEXISTS<db_nameDEFAU
Wesley13 Wesley13
3年前
Java中的参数验证(非Spring版)
1\.Java中的参数验证(非Spring版)1.1.前言为什么我总遇到这种非正常问题,我们知道很多时候我们的参数校验都是放在controller层的传入参数进行校验,我们常用的校验方式就是引入下列的jar包,在参数中添加@Validated,并对Bean对象的参数做不
Easter79 Easter79
3年前
SpringBoot校验参数
前言做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验非空校验都是必不可少的。如果参数比较少的话还是容易处理的一但参数比较多了的话代码中就会出现大量的IFELSE就比如下面这样:!(https://oscimg.oschina.net/oscnet/up5b6e114567bc70667
Stella981 Stella981
3年前
SpringBoot校验参数
前言做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验非空校验都是必不可少的。如果参数比较少的话还是容易处理的一但参数比较多了的话代码中就会出现大量的IFELSE就比如下面这样:!(https://oscimg.oschina.net/oscnet/up5b6e114567bc70667
Stella981 Stella981
3年前
SpringBoot中的异常处理与参数校验
兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容。异常处理说到异常处理,我不知道大家有没有写过或者遇到过如下的写法。publicvoidsaveUser(){try{//所有的业务内容,目测几百
Easter79 Easter79
3年前
SpringBoot中的异常处理与参数校验
兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容。异常处理说到异常处理,我不知道大家有没有写过或者遇到过如下的写法。publicvoidsaveUser(){try{//所有的业务内容,目测几百
SpringBoot如何优雅的进行参数校验(一)
SpringBoot如何优雅的进行参数校验一.为什么要进行参数校验在日常的开发过程中,我们常常需要对传入的参数进行校验,比如在web前后端分离项目中,参数校验有两个方面:前端进行参数校验后端进行参数校验那这两种有什么区别呢?只完成一个可不可以呢?答案是不可