运维八一 运维八一
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)

运维八一

运维,运维!
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)
  • Go

    • 前言

    • Go基础知识

    • Go基本语法

    • 实战项目:简单web服务

    • 基本数据类型

    • 内置运算符

    • 分支和循环

    • 函数 function

    • 结构体 struct

    • 方法 method

    • 实战项目:跟踪函数调用链

    • 接口 interface

    • 并发 concurrency

    • 指针

    • 实战项目:实现轻量级线程池

    • 实战项目:实现TCP服务器

    • go常用包

    • Gin框架

      • gin入门
      • gin工作流程
      • gin中间件
      • gin路由
      • gin请求
      • 数据绑定和校验
        • 响应返回
        • 路由分发
        • Cookie和Session
        • gin项目结构
        • GORM入门
        • 一对多关联查询
      • go随记

    • Python

    • Shell

    • Java

    • Vue

    • 前端

    • 编程浅尝
    • Go
    • Gin框架
    lyndon
    2022-06-27
    目录

    数据绑定和校验

    # 3. 数据绑定和校验

    # 3.1 validator 介绍

    • 在web开发中一个不可避免的环节就是对请求参数进行校验,通常我们会在代码中定义与请求参数相对应的模型(结构体)
    • 借助模型绑定快捷地解析请求中的参数,例如 gin 框架中的Bind和ShouldBind系列方法。
    • 本文就以 gin 框架的请求参数校验为例,介绍一些validator库的实用技巧。

    # 3.2 Json 数据解析和绑定

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    // 定义接收数据的结构体
    type Login struct {
    	// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    	User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    	Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
    }
    
    func main() {
    	// 1.创建路由
    	// 默认使用了2个中间件Logger(), Recovery()
    	r := gin.Default()
    	// JSON绑定
    	r.POST("loginJSON", testHandler)
    	r.Run(":8000")
    }
    
    func testHandler(c *gin.Context) {
    	// 声明接收的变量
    	var json Login
    	// 将request的body中的数据,自动按照json格式解析到结构体
    	if err := c.ShouldBindJSON(&json); err != nil {
    		// 返回错误信息
    		// gin.H封装了生成json数据的工具
    		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    	// 判断用户名密码是否正确
    	if json.User != "root" || json.Pssword != "root" {
    		c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
    		return
    	}
    	c.JSON(http.StatusOK, gin.H{"status": "200"})
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    测试效果:http://localhost:8000/loginJSON/

    img

    # 3.3 表单数据解析和绑定

    package main
    
    import (
    	"net/http"
    	"github.com/gin-gonic/gin"
    )
    
    // 定义接收数据的结构体
    type Login struct {
    	// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    	User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    	Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
    }
    
    func main() {
    	// 1.创建路由
    	// 默认使用了2个中间件Logger(), Recovery()
    	r := gin.Default()
    	// JSON绑定
    	r.POST("/loginForm", testHandler)
    	r.Run(":8000")
    }
    func testHandler(c *gin.Context) {
    	// 声明接收的变量
    	var form Login
    	// Bind()默认解析并绑定form格式
    	// 根据请求头中content-type自动推断
    	if err := c.Bind(&form); err != nil {
    		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    	// 判断用户名密码是否正确
    	if form.User != "root" || form.Pssword != "root" {
    		c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
    		return
    	}
    	c.JSON(http.StatusOK, gin.H{"status": "200"})
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
    <form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
        用户名<input type="text" name="username"><br>
        密码<input type="password" name="password">
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # 3.4 URI数据解析和绑定

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    // 定义接收数据的结构体
    type Login struct {
    	// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    	User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    	Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
    }
    
    func main() {
    	// 1.创建路由
    	// 默认使用了2个中间件Logger(), Recovery()
    	r := gin.Default()
    	// JSON绑定
    	r.GET("/:user/:password", testHandler)
    	r.Run(":8000")
    }
    
    func testHandler(c *gin.Context) {
    	// 声明接收的变量
    	var login Login
    	// Bind()默认解析并绑定form格式
    	// 根据请求头中content-type自动推断
    	if err := c.ShouldBindUri(&login); err != nil {
    		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    	// 判断用户名密码是否正确
    	if login.User != "root" || login.Pssword != "root" {
    		c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
    		return
    	}
    	c.JSON(http.StatusOK, gin.H{"status": "200"})
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    效果演示:http://localhost:8000/root/root

    img

    # 3.5 结构体验证

    用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。

    main.go

    package main
    import (
        "fmt"
        "time"
        "github.com/gin-gonic/gin"
    )
    
    //Person ..
    type Person struct {
        //不能为空并且大于10
        Age      int       `form:"age" binding:"required,gt=10"`
        Name     string    `form:"name" binding:"required"`
        Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
    }
    
    func main() {
        r := gin.Default()
        r.GET("/5lmh", func(c *gin.Context) {
            var person Person
            if err := c.ShouldBind(&person); err != nil {
                c.String(500, fmt.Sprint(err))
                return
            }
            c.String(200, fmt.Sprintf("%#v", person))
        })
        r.Run()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    测试

    • 所有参数正常:http://localhost:8080/jx?age=11&name=zhangsan&birthday=2006-01-02

    img

    • 缺少age字段:http://localhost:8080/jx?name=zhangsan&birthday=2006-01-02

    img

    # 3.6 翻译校验错误提示信息

    validator库本身是支持国际化的,借助相应的语言包可以实现校验错误提示信息的自动翻译。

    下面的示例代码演示了如何将错误提示信息翻译成中文,翻译成其他语言的方法类似。

    package main
    
    import (
    	"fmt"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    	"github.com/gin-gonic/gin/binding"
    	"github.com/go-playground/locales/en"
    	"github.com/go-playground/locales/zh"
    	ut "github.com/go-playground/universal-translator"
    	"github.com/go-playground/validator/v10"
    	enTranslations "github.com/go-playground/validator/v10/translations/en"
    	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
    )
    
    // 定义一个全局翻译器T
    var trans ut.Translator
    
    // InitTrans 初始化翻译器
    func InitTrans(locale string) (err error) {
    	// 修改gin框架中的Validator引擎属性,实现自定制
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    
    		zhT := zh.New() // 中文翻译器
    		enT := en.New() // 英文翻译器
    
    		// 第一个参数是备用(fallback)的语言环境
    		// 后面的参数是应该支持的语言环境(支持多个)
    		// uni := ut.New(zhT, zhT) 也是可以的
    		uni := ut.New(enT, zhT, enT)
    
    		// locale 通常取决于 http 请求头的 'Accept-Language'
    		var ok bool
    		// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
    		trans, ok = uni.GetTranslator(locale)
    		if !ok {
    			return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
    		}
    
    		// 注册翻译器
    		switch locale {
    		case "en":
    			err = enTranslations.RegisterDefaultTranslations(v, trans)
    		case "zh":
    			err = zhTranslations.RegisterDefaultTranslations(v, trans)
    		default:
    			err = enTranslations.RegisterDefaultTranslations(v, trans)
    		}
    		return
    	}
    	return
    }
    
    type SignUpParam struct {
    	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
    	Name       string `json:"name" binding:"required"`
    	Email      string `json:"email" binding:"required,email"`
    	Password   string `json:"password" binding:"required"`
    	RePassword string `json:"re_password" binding:"required,eqfield=Password"`
    }
    
    func main() {
    	if err := InitTrans("zh"); err != nil {
    		fmt.Printf("init trans failed, err:%v\n", err)
    		return
    	}
    
    	r := gin.Default()
    
    	r.POST("/signup", func(c *gin.Context) {
    		var u SignUpParam
    		if err := c.ShouldBind(&u); err != nil {
    			// 获取validator.ValidationErrors类型的errors
    			errs, ok := err.(validator.ValidationErrors)
    			if !ok {
    				// 非validator.ValidationErrors类型错误直接返回
    				c.JSON(http.StatusOK, gin.H{
    					"msg": err.Error(),
    				})
    				return
    			}
    			// validator.ValidationErrors类型错误则进行翻译
    			c.JSON(http.StatusOK, gin.H{
    				"msg":errs.Translate(trans),
    			})
    			return
    		}
    		// 保存入库等具体业务逻辑代码...
    
    		c.JSON(http.StatusOK, "success")
    	})
    
    	_ = r.Run(":8999")
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95

    同样的请求再来一次:

    curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com"}' http://127.0.0.1:8999/signup
    
    1

    这一次的输出结果如下

    {"msg":{"SignUpParam.Email":"Email必须是一个有效的邮箱","SignUpParam.Password":"Password为必填字段","SignUpParam.RePassword":"RePassword为必填字段"}}
    
    1

    # 3.7 自定义验证

    更多参考 (opens new window)

    package main
    
    import (
        "net/http"
        "reflect"
        "github.com/gin-gonic/gin"
        "github.com/gin-gonic/gin/binding"
        "gopkg.in/go-playground/validator.v8"
    )
    
    /*
        对绑定解析到结构体上的参数,自定义验证功能
        比如我们要对 name 字段做校验,要不能为空,并且不等于 admin ,类似这种需求,就无法 binding 现成的方法
        需要我们自己验证方法才能实现 官网示例(https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Custom_Functions)
        这里需要下载引入下 gopkg.in/go-playground/validator.v8
    */
    type Person struct {
        Age int `form:"age" binding:"required,gt=10"`
        // 2、在参数 binding 上使用自定义的校验方法函数注册时候的名称
        Name    string `form:"name" binding:"NotNullAndAdmin"`
        Address string `form:"address" binding:"required"`
    }
    
    // 1、自定义的校验方法
    func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
        if value, ok := field.Interface().(string); ok {
            // 字段不能为空,并且不等于  admin
            return value != "" && !("5lmh" == value)
        }
        return true
    }
    
    func main() {
        r := gin.Default()
    
        // 3、将我们自定义的校验方法注册到 validator中
        if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
            // 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
            v.RegisterValidation("NotNullAndAdmin", nameNotNullAndAdmin)
        }
    
        /*
            curl -X GET "http://127.0.0.1:8080/testing?name=&age=12&address=beijing"
            curl -X GET "http://127.0.0.1:8080/testing?name=lmh&age=12&address=beijing"
            curl -X GET "http://127.0.0.1:8080/testing?name=adz&age=12&address=beijing"
        */
        r.GET("/5lmh", func(c *gin.Context) {
            var person Person
            if e := c.ShouldBind(&person); e == nil {
                c.String(http.StatusOK, "%v", person)
            } else {
                c.String(http.StatusOK, "person bind err:%v", e.Error())
            }
        })
        r.Run()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    上次更新: 2022/06/27, 00:59:36
    gin请求
    响应返回

    ← gin请求 响应返回→

    最近更新
    01
    ctr和crictl显示镜像不一致
    03-13
    02
    alpine镜像集成常用数据库客户端
    03-13
    03
    create-cluster
    02-26
    更多文章>
    Theme by Vdoing | Copyright © 2015-2024 op81.com
    苏ICP备18041258号-2
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式