gin全解

news2024/11/28 3:51:19

文章目录

  • 介绍
  • 安装
  • 快速开始(三种启动方式)
  • 参数
    • 获取querystring参数
      • 其他不常用方法
    • 表单参数(form参数)
      • 其他不常用方法
    • 获取path参数
    • 参数绑定
  • 文件上传
    • 单个文件
    • 多个文件
  • 请求(ctx.Request)
  • 响应
    • gin.H{}
    • 字符串响应
    • JSON/YAML/TOML/ProtoBuf响应
    • 重定向
      • http重定向
      • 路由重定向
    • 同步异步
    • 视图响应
    • 文件响应(静态文件+文件响应)
  • 路由
    • 普通路由
    • 路由组
    • 路由原理
  • Gin中间件
    • Next()
    • Abort()
    • 定义中间件
    • 注册中间件
      • 为全局路由注册
      • 为某个路由单独注册
      • 为路由组注册中间件
      • 小例子
      • 报错后的顺序
      • 提前返回的顺序
    • 中间件注意事项
      • gin默认中间件
      • gin中间件中使用goroutine
    • 中间件推荐
      • 跨域中间件
      • jwt中间件
  • 会话控制
    • Cookie
    • Session=Cookie+存储
    • Token
  • 参数验证
    • 自定义验证
    • 自定义验证v10
      • 变量验证
      • 结构体验证
      • 标签

介绍

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。

安装

要求:Go 1.13 及以上版本

go get -u github.com/gin-gonic/gin

快速开始(三种启动方式)

func main() {
	engine := gin.Default()
	engine.GET("/", func(context *gin.Context) {
		context.JSON(200, gin.H{"msg": "OK2"})
	})
	
	// 方法1
	//engine.Run(":8000")
	
	// 方法2
	//http.ListenAndServe(":8000", engine)
	
	// 方法3
	server := &http.Server{
		Addr: ":8000",
		Handler: engine,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	server.ListenAndServe()
}

参数

获取querystring参数

  1. querystring指的是URL中?后面携带的参数。
  2. URL参数可以通过DefaultQuery()Query()方法获取。
  3. DefaultQuery()若参数不存在,返回默认值,Query()若参数不存在,返回空串。
func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search", func(c *gin.Context) {
        // 可以添加默认值
		username := c.DefaultQuery("username", "Generalzy")
		//username := c.Query("username")
		
		// 获取address信息
		address := c.Query("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run()
}

http://localhost:8080/user/search?username=Generalzy&address=中国

其他不常用方法

  1. GetQueryArray()获取列表
func Index(ctx *gin.Context) {
	if val,ok:=ctx.GetQueryArray("name");ok{
		fmt.Println(val)
		ctx.JSON(http.StatusOK,gin.H{
			"code":0,
			"err":"",
			"data":val,
		})
	}else{
		ctx.JSON(http.StatusBadRequest,gin.H{
			"code":1,
			"err":"params error",
			"data":[]interface{}{},
		})
	}
}

// http://127.0.0.1:8080/index?name=1&name=2
{
    "code": 0,
    "data": [
        "1",
        "2"
    ],
    "err": ""
}

表单参数(form参数)

  1. 表单传输为post请求,http常见的传输格式为四种:
    1. application/json
    2. application/x-www-form-urlencoded
    3. application/xml
    4. multipart/form-data
  2. 表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded
    或from-data格式的参数
  3. 同样,PostForm()若参数不存在返回空串,DefaultPostForm()若参数不存在返回默认值
func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.POST("/user/search", func(c *gin.Context) {
		// DefaultPostForm取不到值时会返回指定的默认值
		//username := c.DefaultPostForm("username", "德玛西亚")
		username := c.PostForm("username")
		
		address := c.PostForm("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run(":8080")
}

其他不常用方法

  1. GetPostFormArray()与GetQueryArray()类型

在这里插入图片描述

获取path参数

请求的参数通过URL路径传递,例如:/user/search/德玛西亚/北京

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search/:username/:address", func(c *gin.Context) {
		username := c.Param("username")
		address := c.Param("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

	r.Run(":8080")
}

参数绑定

  1. 模型绑定可以将请求体绑定给一个类型,目前支持绑定的类型有 JSON, XML 和标准表单数据。
  2. 使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,可以不用自动推断,而用 BindWith(&login, binding.Form)方法。
// Binding from JSON
type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	router := gin.Default()

	// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var login Login

		if err := c.ShouldBind(&login); err == nil {
			fmt.Printf("login info:%#v\n", login)
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定form表单示例 (user=q1mi&password=123456)
	router.POST("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
	router.GET("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  2. 如果是 POST 请求,首先检查 content-type 是否为 JSONXML,然后再使用 Formform-data)。

文件上传

  1. multipart/form-data格式用于文件上传
  2. gin文件上传与原生的net/http方法类似,不同在于gin把原生的request封装到c.Request

单个文件

func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单个文件
		file, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
			return
		}
		// 打印文件名
		log.Println(file.Filename)
		// 文件存储位置
		dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
		// 上传文件到指定的目录
		c.SaveUploadedFile(file, dst)
		
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})
	router.Run()
}

多个文件

func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["file"]

		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
			// 上传文件到指定的目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})
	router.Run()
}

请求(ctx.Request)

type Context struct {
	// 封装了htpp的Request
	Request   *http.Request
	// 继承了http的ResponseWriter接口
	Writer    ResponseWriter
	...
}
  1. 请求头

    ctx.Request.Header.Get()
    ctx.GetHeader()
    
  2. 请求参数

  3. cookies

  4. 上传文件

响应

  1. 响应头
  2. 附加cookie
  3. 字符串响应

gin.H{}

// H is a shortcut for map[string]interface{}
type H map[string]any

字符串响应

// String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...any) {
	c.Render(code, render.String{Format: format, Data: values})
}
func Index(ctx *gin.Context) {
	ctx.String(http.StatusOK,"我是你%s大爷","二")
}

JSON/YAML/TOML/ProtoBuf响应

// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj any) {
	c.Render(code, render.JSON{Data: obj})
}

// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj any) {
	c.Render(code, render.YAML{Data: obj})
}

// TOML serializes the given struct as TOML into the response body.
func (c *Context) TOML(code int, obj interface{}) {
	c.Render(code, render.TOML{Data: obj})
}

// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj any) {
	c.Render(code, render.ProtoBuf{Data: obj})
}
c.JSON(http.StatusOK, gin.H{
	"message": fmt.Sprintf("%d files uploaded!", len(files)),
})

重定向

http重定向

r.GET("/test", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})

路由重定向

r.GET("/test", func(c *gin.Context) {
    // 指定重定向的URL
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

同步异步

goroutine机制可以方便的实现异步处理

func main() {
	engine := gin.Default()
	engine.HandleMethodNotAllowed = true  // 开启方法不允许校验

	engine.GET("/long_async", func(context *gin.Context) {
		// Copy 返回可以在请求范围之外安全使用的当前上下文的副本。当必须将上下文传递给 goroutine 时,必须使用它。
		// goroutine中只能使用上下文的副本
		// 1. 异步
		cp := context.Copy()
		go func() {
			time.Sleep(5 * time.Second)
			// 注意:goroutine中必须使用上下文副本
			log.Println("done! in path", cp.Request.URL.Path)
		}()
	})

	engine.GET("/long_sync", func(context *gin.Context) {
		// 同步:可以使用原始上下文,context
		time.Sleep(5 * time.Second)
		log.Println("done! int path", context.Request.URL.Path)
	})

	engine.Run(":8000")
}

视图响应

先要使用LoadHTMLTemplates加载模板文件

func main() {
	engine := gin.Default()

	// 加载模板文件
	//engine.LoadHTMLGlob("html/*")
	engine.LoadHTMLFiles("html/index.html", "html/user.html")

	// url: http://127.0.0.1:8000
	engine.GET("/index.html", func(context *gin.Context) {
		context.HTML(http.StatusOK, "index.html", gin.H{"name": "张三"})
	})
	engine.GET("/user.html", func(context *gin.Context) {
		var User struct{
			User string `json:"user"`
			Age int `json:"age"`
		}
		User.User = "李四"
		User.Age = 18

		data, _ := json.Marshal(&User)
		m := make(map[string]any)
		json.Unmarshal(data, &m)

		context.HTML(http.StatusOK, "user.html", m)
	})

	engine.Run(":8000")
}

文件响应(静态文件+文件响应)

func main() {
	engine := gin.Default()

	// url: http://127.0.0.1:8000/index/user.html
	//engine.Static("/index", "./html")
	
	// url: http://127.0.0.1:8000/index/index.html
	//engine.StaticFS("/index", gin.Dir("./html", false))
	
	// url: http://127.0.0.1:8000/index
	engine.StaticFile("/index", "./html/index.html")
	
	// 设置返回头并返回数据
	fileContentDisposition := "attachment;filename=\"" + attachmentName + "\""
	c.Header("Content-Type", "application/zip") // 这里是压缩文件类型 .zip
	c.Header("Content-Disposition", fileContentDisposition)
	c.Data(http.StatusOK, contentType, fileContent)
	
	// fileContent是文件的字节流
	ctx.DataFromReader(200, response.ContentLength, "application/octet-stream", fileContent, nil)

	// 传入路径的文件下载
	c.File("local/file.go")

	engine.Run(":8000")
}

路由

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})

此外,还有一个可以匹配所有请求方法的Any方法如下:

r.Any("/test", func(c *gin.Context) {...})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
	c.HTML(http.StatusNotFound, "views/404.html", nil)
})

路由组

可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰。

func main() {
	r := gin.Default()
	userGroup := r.Group("/user")
	{
		userGroup.GET("/index", func(c *gin.Context) {...})
		userGroup.GET("/login", func(c *gin.Context) {...})
		userGroup.POST("/login", func(c *gin.Context) {...})

	}
	shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
	}
	r.Run()
}

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
{
	shopGroup.GET("/index", func(c *gin.Context) {...})
	shopGroup.GET("/cart", func(c *gin.Context) {...})
	shopGroup.POST("/checkout", func(c *gin.Context) {...})
	// 嵌套路由组
	xx := shopGroup.Group("xx")
	xx.GET("/oo", func(c *gin.Context) {...})
}

通常我们将路由分组用在划分业务逻辑或划分API版本时。

路由原理

  1. Gin框架中的路由使用的是httprouter这个库。

  2. 其基本原理就是构造一个路由地址的前缀树。

在这里插入图片描述

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

在这里插入图片描述

Next()

在这里插入图片描述

Abort()

在这里插入图片描述

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		// 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		c.Set("name", "123")
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}

注册中间件

在gin框架中,可以为每个路由添加任意数量的中间件

为全局路由注册

func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
	r.Run()
}

为某个路由单独注册

// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), func(c *gin.Context) {
	name := c.MustGet("name").(string) // 从上下文取值
	log.Println(name)
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello world!",
	})
})

为路由组注册中间件

shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

小例子

func InitMiddleWare(e *gin.Engine){
	e.Use(RequestResponseMiddleWare(),AuthMiddleWare())
}

func AuthMiddleWare()gin.HandlerFunc{
	return func(ctx *gin.Context) {
		token:=ctx.GetHeader("token")
		if len(token)!=0{
			fmt.Printf("request通过认证,token为:%s \n",token)
			ctx.Next()
			fmt.Printf("response通过认证,token为:%s \n",token)
		}else{
			// 不再向后执行
			ctx.Abort()
			// 响应错误信息
			ctx.JSON(http.StatusBadRequest,gin.H{
				"code":1,
				"err":http.StatusText(http.StatusBadRequest),
			})
			// 结束本次请求
			return
		}
	}
}

func RequestResponseMiddleWare() gin.HandlerFunc{
	return func(ctx *gin.Context) {
		fmt.Printf("请求到达,地址为:%s \n",ctx.RemoteIP())
		// 下一步
		ctx.Next()
		// 回到此处
		fmt.Printf("响应到达,地址为:%s \n",ctx.RemoteIP())
	}
}

请求到达,地址为:127.0.0.1
request通过认证,token为:1

response通过认证,token为:1
响应到达,地址为:127.0.0.1

报错后的顺序

func Index(ctx *gin.Context) {
	panic("故意的")
}

请求到达,地址为:127.0.0.1
request通过认证,token为:1

提前返回的顺序

任何write操作都会向response的缓冲区写入数据,请求结束时才会返回。

func RequestResponseMiddleWare() gin.HandlerFunc{
	return func(ctx *gin.Context) {
		fmt.Printf("请求到达,地址为:%s \n",ctx.RemoteIP())
		// 下一步
		// ctx.Next()
		ctx.String(200,"提前返回")
		return
		// 回到此处
		fmt.Printf("响应到达,地址为:%s \n",ctx.RemoteIP())
	}
}

请求到达,地址为:127.0.0.1
request通过认证,token为:1
response通过认证,token为:1
请求到达,地址为:127.0.0.1
request通过认证,token为:1
2023/01/26 21:21:57 Key: 'User.Username' Error:Field validation for 'Username' failed on the 'required' tag
Key: 'User.Password' Error:Field validation for 'Password' failed on the 'required' tag
response通过认证,token为:1
响应到达,地址为:127.0.0.1

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(ctx.Copy())。

中间件推荐

跨域中间件

gin-cors gin跨域的官方中间件。

  1. 安装
go get github.com/gin-contrib/cors
  1. 典型案例
package main

import (
  "time"

  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  // CORS for https://foo.com and https://github.com origins, allowing:
  // - PUT and PATCH methods
  // - Origin header
  // - Credentials share
  // - Preflight requests cached for 12 hours
  router.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://foo.com"},
    AllowMethods:     []string{"PUT", "PATCH"},
    AllowHeaders:     []string{"Origin"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {
      return origin == "https://github.com"
    },
    MaxAge: 12 * time.Hour,
  }))
  router.Run()
}
  1. Using DefaultConfig as start point
func main() {
  router := gin.Default()
  // - No origin allowed by default
  // - GET,POST, PUT, HEAD methods
  // - Credentials share disabled
  // - Preflight requests cached for 12 hours
  config := cors.DefaultConfig()
  config.AllowOrigins = []string{"http://google.com"}
  // config.AllowOrigins = []string{"http://google.com", "http://facebook.com"}
  // config.AllowAllOrigins = true

  router.Use(cors.New(config))
  router.Run()
}
  1. 默认允许全部
func main() {
  router := gin.Default()
  // same as
  // config := cors.DefaultConfig()
  // config.AllowAllOrigins = true
  // router.Use(cors.New(config))
  router.Use(cors.Default())
  router.Run()
}

jwt中间件

gin-jwt 用于Gin框架的JWT中间件

go get github.com/appleboy/gin-jwt/v2

会话控制

Cookie

// 设置
ctx.SetCookie()
// 获取
ctx.Cookie()
// 删除
ctx.SetCookie(maxAge=-1)

Session=Cookie+存储

Token

参数验证

  1. 用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。
  2. form用于标记请求参数的入参,json用于反序列化
type User struct {
	Username string `json:"username" binding:"required" form:"username"`
	Password string `json:"password" binding:"required" form:"password"`
}

func Index(ctx *gin.Context) {
	user:=new(User)
	if err:=ctx.ShouldBind(user);err!=nil{
		log.Println(err)
	}else{
		fmt.Println(user)
	}
	ctx.String(200,"OK")
}

自定义验证

package main
import (
	"fmt"
	"net/http"
	"github.com/gin-gonic/gin"
	"gopkg.in/go-playground/validator.v10"
)
/*
对绑定解析到结构体上的参数,自定义验证功能
比如我们需要对URL的接受参数进行判断,判断用户名是否为root如果是root通过否则
返回false
*/
type Login struct {
	User string `uri:"user" validate:"required,checkName"`
	Pssword string `uri:"password"`
}
// 自定义验证函数
func checkName(fl validator.FieldLevel) bool {
	if fl.Field().String() != "root" {
		return false
	}
	return true
}

func main() {
	r := gin.Default()
	validate := validator.New()
	//注册自定义函数,与struct tag关联起来
	err := validate.RegisterValidation("checkName", checkName)
	r.GET("/:user/:password", func(c *gin.Context) {
		var login Login
		//注册自定义函数,与struct tag关联起来
		err := validate.RegisterValidation("checkName", checkName)
		if err := c.ShouldBindUri(&login); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		err = validate.Struct(login)
		if err != nil {
			for _, err := range err.(validator.ValidationErrors) {
				fmt.Println(err)
			}
			return
		}
		fmt.Println("success")
	})
	r.Run()
}

自定义验证v10

go get github.com/go-playground/validator/v10

Web 框架 gin 的默认验证器,gin将其validate标签改为了binding标签

func (v *defaultValidator) lazyinit() {
	v.once.Do(func() {
		v.validate = validator.New()
		v.validate.SetTagName("binding")
	})
}

变量验证

Var 方法使用 tag(标记)验证方式验证单个变量。

func (*validator.Validate).Var(field interface{}, tag string) error
  1. 它接收一个 interface{} 空接口类型的 field 和一个 string 类型的 tag,返回校验报错信息(ValidationErrors)
  2. 如果是验证数组、slice 和 map,可能会包含多个错误。
func main() {
	validate := validator.New()
	// 验证变量
	email := "admin#admin.com"
	email := ""
	err := validate.Var(email, "required,email")
	if err != nil {
		validationErrors := err.(validator.ValidationErrors)
		fmt.Println(validationErrors)
		// output: Key: '' Error:Field validation for '' failed on the 'email' tag
		// output: Key: '' Error:Field validation for '' failed on the 'required' tag
		return
	}
}

结构体验证

func (*validator.Validate).Struct(s interface{}) error
func main() {
	validate = validator.New()
	type User struct {
		ID int64 `json:"id" validate:"gt=0"`
		Name string `json:"name" validate:"required"`
		Gender string `json:"gender" validate:"required,oneof=man woman"`
		Age uint8 `json:"age" validate:"required,gte=0,lte=130"`
		Email string `json:"email" validate:"required,email"`
	}
	user := &User{
		ID: 1,
		Name: "frank",
		Gender: "boy",
		Age: 180,
		Email: "gopher@88.com",
	}
	err = validate.Struct(user)
	if err != nil {
		validationErrors := err.(validator.ValidationErrors)
		// output: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
		fmt.Println(validationErrors)
		return
	}
}

注册一个函数,获取结构体字段的名称:

validate.RegisterTagNameFunc(func(field reflect.StructField) string {
		return field.Tag.Get("json")
})

标签

在这里插入图片描述

关键字针对对象功能示例
required属性,结构,文件标示必须存在(0时验证失败validate:"required"
omitempty属性,结构,文件omitempty要么不传,传的话就要大于5validate:"omitempty,gt=5"
len字符串,数组,时间间隔,文件长度标示长度,size,间隔,大小validate:"len=1"
min字符串,数字,数组,时间间隔标示最小validate:"min=1"
max字符串,数字,数组,时间标示最大validate:"max=7"
eq字符串,数组,时间间隔,布尔值标示相等,正对数组是长度validate:"eq=3"
ne字符串,数组,时间间隔,布尔值标示不相等validate:"ne="
lt字符串,数字,数组,时间小于validate:"lt=3"
lte字符串,数字,数组,时间小于等于validate:"lte=3"
gt字符串,数字,数组,时间大于validate:"gt=3"
gte字符串,数字,数组,时间大于等于validate:"gte=3"
eqfield同级属性等于validate:"eqfield=MaxString"
eqcsfield内部属性等于validate:"eqcsfield=Inner.EqCSFieldString"
necsfield内部属性不等于validate:"necsfield=Inner.NeCSFieldString"
gtcsfield内部属性大于validate:"gtcsfield=Inner.GtCSFieldString"
ltcsfield内部属性小于validate:"ltcsfield=Inner.LtCSFieldString"
ltecsfield内部属性小于等于validate:"ltecsfield=Inner.LteCSFieldString"
nefield同级属性不等于validate:"nefield=EqFieldString"
gtfield同级属性大于validate:"gtfield=MaxString"
gtefield同级属性大于等于validate:"gtefield=MaxString"
ltfield同级属性小于validate:"ltfield=MaxString"
ltefield同级属性小于等于validate:"ltefield=MaxString"
alpha字符串"^[a-zA-Z]+$"validate:"alpha"
alphanum字符串"^[a-zA-Z0-9]+$"validate:"alphanum"
numeric字符串"^[-+]?[0-9]+(?:\\.[0-9]+)?$"validate:"numeric"
number字符串"^[0-9]+$"validate:"number"
hexadecimal字符串"^(0[xX])?[0-9a-fA-F]+$"validate:"hexadecimal"
hexcolor字符串"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"validate:"hexcolor"
rgb字符串复杂正则不展示validate:"rgb"
rgba字符串复杂正则不展示
hsl字符串复杂正则不展示
hsla字符串复杂正则不展示
email字符串复杂正则不展示validate:"email"
url字符串url规则validate:"url"
uri字符串uri规则validate:"uri"
base64字符串"^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-|Za-z0-9+\\/]{4})$"validate:"base64"
contains字符串包含validate:"contains=purpose"
containsany字符串包含任意一个validate:"containsany=!@#$"
excludes字符串不包含validate:"excludes=text"
excludesall字符串不包含任意一个validate:"excludesall=!@#$"
excludesrune字符串不包含某个rune类型validate:"excludesrune=☻"
isbn字符串两个isbnvalidate:"isbn"
isbn10字符串"^(?:[0-9]{9}X|[0-9]{10})$"validate:"isbn10"
isbn13字符串^(?:(?:97(?:8|9))[0-9]{10})$"validate:"isbn13"
uuid字符串"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"validate:"uuid"
uuid3字符串"^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"validate:"uuid3"
uuid4字符串"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"validate:"uuid4"
uuid5字符串"^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"validate:"uuid5"
ascii字符串"^[\x00-\x7F]*$"validate:"ascii"
printascii字符串^[\x20-\x7E]*$"validate:"printascii"
multibyte字符串"[^\x00-\x7F]"validate:"multibyte"
datauri字符串^data:((?:\w+\/(?:([^;]|;[^;]).)+)?)validate:"datauri"
latitude字符串"^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"validate:"latitude"
longitude字符串"^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"validate:"longitude"
ssn字符串^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$validate:"ssn"
ip字符串ip规则validate:"ip"
ipv4字符串ipv4规则validate:"ipv4"
ipv6字符串ipv6规则validate:"ipv6"
cidr字符串ip规则validate:"cidr"
cidrv4字符串ipv4规则validate:"cidrv4"
cidrv6字符串ipv6规则validate:"cidrv6"
tcp_addr字符串对应规则按需验证validate:"tcp_addr"
tcp4_addr字符串对应规则按需验证validate:"tcp4_addr"
tcp6_addr字符串对应规则按需验证validate:"tcp6_addr"
udp_addr字符串对应规则按需验证validate:"udp_addr"
udp4_addr字符串对应规则按需验证validate:"udp4_addr"
udp6_addr字符串对应规则按需验证validate:"udp6_addr"
ip_addr字符串对应规则按需验证validate:"ip_addr"
ip4_addr字符串对应规则按需验证validate:"ip4_addr"
ip6_addr字符串对应规则按需验证validate:"ip6_addr"
unix_addr字符串对应规则按需验证validate:"unix_addr"
mac字符串对应规则按需验证validate:"mac"
iscolor字符串颜色校验所有颜色规则validate:"iscolor"
oneofOneOfString对应规则按需验证validate:"oneof=red green"
oneofOneOfInt对应规则按需验证validate:"oneof=5 63"
uniqueUniqueSlice对应规则按需验证validate:"unique"
uniqueUniqueArray对应规则按需验证validate:"unique"
uniqueUniqueMap对应规则按需验证validate:"unique"
jsonJSONString对应规则按需验证validate:"json"
lowercaseLowercaseString对应规则按需验证validate:"lowercase"
uppercaseUppercaseString对应规则按需验证validate:"uppercase"
datetimeDatetime对应规则按需验证validate:"datetime=2006-01-02"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一起自学SLAM算法:8.2 Cartographer算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; Gmapping代码实现相对简洁&#xff0c;非常适合初学者入门学习。但是Gmapping属于基于滤波方法的SLAM系统&#xff0c;明显的缺点是无法构建大规模的地图&#xff0c;这一点已经在第7章中讨论过了。而基于优化的…

Python爬虫之findall和lxml

Python爬虫之findall和lxml 提示&#xff1a;前言 Python爬虫之findall和lxml 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录Python爬虫之findall和lxml前言一、导入包二、设置URL和获取视频链接三、解析视频名字…

31. 实战:PyQuery获取小电视Top100详细信息(文末源码)

目录 前言 &#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09; 目的 &#xff08;链接放在评论区&#xff09;&#xff08;链接放在评论区&#xff09;&#xff08;链接放…

趣味三角——第4章——三角学迈向解析化

第4章 三角学迈向解析化(或分析化) 目录 4.1 三角学迈向解析化的过程简述 4.2 Franois Vieter对三角学解析化的贡献 “Thus the analysis of angular sections involves geometric and arithmetic secrets which hitherto have been penetrated by no one(因此&#xf…

Idea中指定xml文件失效

目录一、&#x1f407; 项目场景&#xff1a;二、&#x1f407; 问题描述三、&#x1f407; 原因分析&#xff1a;四、&#x1f407; 解决方案&#xff1a;一、&#x1f407; 项目场景&#xff1a; 最近狮子在搞一个项目&#xff0c;需要用到数据库多表查询&#xff0c;所以在…

数据挖掘,计算机网络、操作系统刷题笔记35

数据挖掘&#xff0c;计算机网络、操作系统刷题笔记35 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;orac…

【论文翻译】Jointformer :一种基于误差预测和改进的三维人体姿态估计的单帧提升变压器

摘要 单目三维人体姿态估计技术有望极大地提高人体运动数据的可用性。表现最好的单幅图像2D3D提升模型使用图卷积网络(GCNs)&#xff0c;通常需要一些手动输入来定义不同身体关节之间的关系。我们提出了一种新的基于变压器的方法&#xff0c;该方法使用更广泛的自我注意机制来…

nodejs+vue高校网上报名系统

本课题利用nodejsVue设计实现网上报名系统。系统的主要功能是&#xff1a;用户在线注册信息之后&#xff0c;利用注册时填写的用户账号与密码&#xff0c;登入系统后&#xff0c;对注册的个人信息进行修改&#xff0c;在线报名&#xff0c;能正确的提交有送报考的基本信息&…

【图卷积网络】01-卷积神经网络:从欧氏空间到非欧氏空间

人工神经网络发展浪潮 第三次浪潮——卷积神经网络 加拿大多伦多大学教授&#xff0c;机器学习领域泰斗Geoffery Hinton及其学生在《科学》上发表了一篇论文 &#xff08;Hinton, G. E . Reducing the Dimensionality of Data with Neural Networks[J]. Science, 2006, 313(578…

【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

教程来自freecodeCamp&#xff1a;【英字】使用 React 和 TypeScript 构建应用程序 跟做&#xff0c;仅记录用 其他资料&#xff1a;https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/ 第二天 以下是视频(0:18-0:40) 的内容 目录第二天1 App 函数…

【二叉树】java实现代码,详解二叉树,带大家更深刻的掌握二叉树递归思想

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#x1fa90;&#x1fa90;&#x1fa90;&#xff0c;在探索数据结构的旅程中&#xff0c;二叉树可以说是数据结构中的重点&#xff0c;笔试面试经常出现的问题&#xff0c;同时也是难点。&#x1f425;&#x1f425;&#x1f4…

【Java开发】Spring Cloud 09 :微服务网关 Gateway

Spring Cloud Gateway&#xff08;简称 Gateway&#xff09;&#xff0c;它在微服务架构中扮演的角色是“微服务网关”&#xff0c;Nginx 和 Gateway 在微服务体系中的分工是不一样的。Gateway 作为更底层的微服务网关&#xff0c;通常是作为外部 Nginx 网关和内部微服务系统之…

Markdown编辑器基本语法

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

【C语言从0到1之文件操作(FILE)】(原理 画图 举例 不信教不会你 不要放收藏夹落灰 学起来好嘛)

&#x1f57a;作者&#xff1a;迷茫的启明星 &#x1f383;专栏&#xff1a;《数据库》《C语言从0到1专栏》《数据结构》《C语言杂谈》&#x1f3c7;分享喜欢的一句话&#xff1a;心如花木&#xff0c;向阳而生前言在我们的学习中&#xff0c;文件操作是被我们忽略&#xff0c;…

NodeJS 与第三方模块 mysql(基本操作)

文章目录参考描述mysql 模块连接数据库检测基本操作查询数据与代码分离原则占位符插入另一种姿态修改另一种姿态删除标记删除参考 项目描述哔哩哔哩黑马程序员搜索引擎Bing 描述 项目描述NodeJSv18.13.0nodemon2.0.20MySQL5.7.40mysql2.18.1 mysql 模块 npm&#xff08;Node…

Linux——进程

目录 冯诺依曼体系结构 操作系统(Operator System) 概念 设计OS的目的 定位 如何理解 "管理" 总结 系统调用和库函数概念 承上启下 进程 基本概念 描述进程-PCB task_struct-PCB的一种 task_ struct内容分类 组织进程 查看进程 通过系统调用获取进程…

Pycharm使用Git进行版本控制(自建远端Git仓库)

目录本地Git安装远端Git仓库搭建在Pycharm中使用Git进行版本控制设置Git可执行文件路径创建本地Git仓库设置远端Git仓库提交及推送本地Git安装 安装本地Git用于被Pycharm调用&#xff0c;安装方法参考以下博客&#xff1a; Git 的下载与安装_作者&#xff1a;fengzhx0820 远端…

四轮两驱小车(四):STM32驱动5路灰度传感器PID循迹

目录 前言&#xff1a; 小车效果展示&#xff1a; 5路数字灰度传感器&#xff1a; 巡线思路&#xff1a; 加入PID调节的代码&#xff1a; 前言&#xff1a; 之前买了一批5路灰度传感器&#xff0c;想用这传感器进行循迹&#xff0c;无奈网上和官方的资料提供的还是比较少&a…

ARM X210 官方 uboot 配置编译实践

一、X210官方uboot配置编译实践1 1. 找到官方移植好的 uboot&#xff08;BSP 概念&#xff09; (1) 源头的源代码是 uboot 官网下载的。这个下载的源代码可能没有你当前使用的开发板的移植&#xff0c;甚至找不到当前开发板使用的 SoC 对应的移植版本。 (2) SoC 厂商在推出一…

分享145个ASP源码,总有一款适合您

ASP源码 分享145个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 145个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1gxm3rFFLu8pUhVncQga6-g?pwd7n85 提取码&#x…