Gin

Gin

_

简介

介绍

  • Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
  • 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错
  • 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范

安装

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

hello world

package main

import (
    "net/http"

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

func main() {
        // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    // 1.创建默认路由
   r := gin.Default()
   // 2.绑定路由规则,执行的函数
   // gin.Context,封装了request和response
   r.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
   // 3.监听端口,默认在8080
   // Run("里面不指定端口号默认为8080") 
   // localhost:8080
   r.Run(":8080")
}

gin路由

基本路由

package main

import (
    "net/http"

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

func main() {
        // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "hello word")
    })
    r.POST("/xxxpost",getting)
    r.PUT("/xxxput")
    //监听端口默认为8080
    r.Run(":8080")
}

Restful

  • gin支持Restful风格的API
  • 即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作
package main

import (
    "net/http"
    "strings"

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

func main() {
        // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
    //请求 localhost:8080/张三/666 
    r.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        //截取/
        action = strings.Trim(action, "/")
        // 像页面直接输出字符串
        c.String(http.StatusOK, name+" is "+action)
    })
    //默认为监听8080端口
    r.Run(":8080")
}

URL参数

  • URL参数可以通过DefaultQuery()或Query()方法获取
  • DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串
  • 只对 localhost:8080/user?name=张三 这种格式的url生效
package main

import (
    "fmt"
    "net/http"

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

func main() {
        // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
    r.GET("/user", func(c *gin.Context) {
        //指定默认值
        //http://localhost:8080/user 才会打印出来默认的值
        name := c.DefaultQuery("name", "李四")
        c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
    })
    r.Run(":8080")
}

表单参数

  • 表单传输为post请求,http常见的传输格式为四种:
    • application/json
    • application/x-www-form-urlencoded
    • application/xml
    • multipart/form-data
  • 表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数
  • x-www-form-urlencoded 提交数据时使用 from-data文件上传是使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>表单参数</title>
</head>
<body>

    <form action="http://localhost:8080/form" method="post" enctype="application/x-www-form-urlencoded">
        用户名<input type="text" name="username" placeholder="请输入用户名">
        用户名<input type="password" name="password" placeholder="请输入密码">
        <input type="submit" value="提交"/>
    </form>
    
</body>
</html>
package main

import (
    "fmt"
    "net/http"

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

func main() {
    // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
	// 表单参数可以通过PostForm()方法获取,
	// 该方法默认解析的是x-www-form-urlencoded或from-data格式的参数
	r.POST("/form", func(c *gin.Context) {
		types := c.DefaultPostForm("type", "post")

		username := c.PostForm("username")
		password := c.PostForm("password")
		c.String(http.StatusOK, fmt.Sprintf("username=%s password=%s type=%s", username, password, types))
	})
    r.Run(":8080")
}

文件上传

package main

import (
    "fmt"
    "net/http"

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

func main() {
    // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
	// 限制上传最大尺寸  默认32m
	r.MaxMultipartMemory = 8 << 20

	// 当儿文件上传
	r.POST("/upload", func(c *gin.Context) {

		file, err := c.FormFile("file")
		if err != nil {
			c.String(500, "上传图片出错")
		}
		c.SaveUploadedFile(file, file.Filename)
		c.String(200, file.Filename)

	})

	// 上传特定文件 (演示只能上传zip文件)
	r.POST("/zipupload", func(c *gin.Context) {
		// 获取文件信息
		_, headers, err := c.Request.FormFile("zip")
		if err != nil {
			c.String(500, "获取文件信息错误")
			return
		}
		// 限定大小
		if headers.Size > 1024*1024*2 {
			c.String(500, "文件太大,限制上传")
			return
		}

		//headers.Header.Get("Content-Type")获取上传文件的类型
		// zip = application/x-zip-compressed
		// RAR = application/octet-stream
		if headers.Header.Get("Content-Type") != "application/x-zip-compressed" {
			c.String(500, "只能上传zip文件")
			return
		}

		c.SaveUploadedFile(headers, headers.Filename)
		c.String(200, headers.Filename+"上传成功")
	})

	// 多个文件上传
	r.POST("/uploads", func(c *gin.Context) {
		// 获取很多个文件
		f, err := c.MultipartForm()
		if err != nil {
			c.String(500, "获取上传信息失败")
		}

		// 获取所有文件
		fh := f.File["files"]
		// 遍历所有文件
		for _, file := range fh {
			// 一个一个存
			if err := c.SaveUploadedFile(file, file.Filename); err != nil {
				c.String(500, "存入"+file.Filename+"文件失败")
				return
			}
		}
		c.String(200, fmt.Sprintf("upload ok %d files", len(fh)))

	})
    r.Run(":8080")
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
</head>
<body>
    <!-- 单文件上传 -->
    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit" value="单个文件上传"/>
    </form>
    <hr color="red">
    <!-- 特定文件上传 (zip)-->
    <form action="http://localhost:8080/zipupload" method="post" enctype="multipart/form-data">
        <input type="file" name="zip">
        <input type="submit" value="上传zip文件"/>
    </form>

    <hr color="red">

    <!-- 批量文件上传 -->
    <form action="http://localhost:8080/uploads" method="post" enctype="multipart/form-data">
        <input type="file" name="files" multiple>
        <input type="submit" value="多个文件上传"/>
    </form>
</body>
</html>

路由分组

package main

import (
    "fmt"
    "net/http"

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

func main() {
    // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
		// 路由
	// gin.Default()是默认路由
	// 默认使用了2个中间件Logger(), Recovery()

	// 路由分组
	us := r.Group("/users")

	// {} 是书写规范
	{
		us.GET("/add", func(ctx *gin.Context) {
			ctx.String(200, "user/add")
		})
	}

	lg := r.Group("/getway")
	{
		lg.GET("/login", func(ctx *gin.Context) {
			value, _ := ctx.Get("ha")
			// ctx.String(200, "getway/login",value)
			ctx.JSON(200, gin.H{
				"login": "getway/login",
				"value": value,
			})
		})
	}
    r.Run(":8080")
}

路由拆分

创建一个routers包 ,在包下创建一个routers.go文件

package routers

import (
	"github.com/gin-gonic/gin"
)


func hello(c *gin.Context) {
	c.JSON(200, gin.H{
		"msg": "hello",
	})
}

func SetupRouter(e *gin.Engine) {
	e.GET("/hello", hello)
}

在main中引入拆分的路由

package main

import (
    "fmt"
    "net/http"

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

func main() {
    // 设置模式  报警看着烦
    gin.SetMode(gin.TestMode)
    r := gin.Default()
	
    // 引入拆分的路由
    routers.SetupRouter(r)


    r.Run(":8080")
}

gin数据解析和绑定

在上面的分组的路由实现

package routers

import (
	"net/http"

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

// json的数据解析和绑定

// 定义接收数据的结构体
type Login struct {
	// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
	User     string `form:"username" json:"username" uri:"user" xml:"username" binding:"required"`
	Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func login(c *gin.Context) {
	// 声明接收的变量
	var login Login
	// 将request的body中的数据,自动按照json格式解析到结构体
	if err := c.ShouldBindJSON(&login); err != nil {
		// 返回错误信息
		// gin.H封装了生成json数据的工具
		c.JSON(http.StatusOK, gin.H{"error": err.Error()})
		return
	}

	// 判断用户密码是否正确
	if login.User != "root" || login.Password != "123456" {
		c.JSON(http.StatusOK, gin.H{"status": "304"})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg": "登录成功",
	})

}

func hello(c *gin.Context) {
	c.JSON(200, gin.H{
		"msg": "hello",
	})
}

func SetupRouter(e *gin.Engine) {
	e.GET("/hello", hello)

	e.POST("/login", login)
}

postman 请求

gin数据解析与绑定

gin 渲染

各种数据格式的响应

  • json、结构体、XML、YAML类似于java的properties、ProtoBuf
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/testdata/protoexample"
)

// 多种响应方式
func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 1.json
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    })
    // 2. 结构体响应
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })
    // 3.XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })
    // 4.YAML响应
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "zhangsan"})
    })
    // 5.protobuf格式,谷歌开发的高效存储读取的工具
    // 数组?切片?如果自己构建一个传输格式,应该是什么格式?
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        // 定义数据
        label := "label"
        // 传protobuf格式数据
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

    r.Run(":8000")
}

HTML模板渲染

  • gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换

  • LoadHTMLGlob()方法可以加载模板文件 直接指定相对路径

  • LoadHTMLFiles()指定具体的路径,每添加一个html文件就需要添加一个LoadHTMLFiles()路径

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    // 第一个gin实例
    
    func main() {
    	// 创建一个默认路由引擎
    	gin.SetMode(gin.ReleaseMode)
    	engine := gin.Default()
    	// 加载模板文件
    	engine.LoadHTMLGlob("templates/**/*")
    
    	engine.GET("/posts/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "posts/index.html", gin.H{
    			"title": "posts/index",
    		})
    	})
    
    	engine.GET("/users/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "users/index.html", gin.H{
    			"title": "users/index",
    		})
    	})
    
    	engine.Run(":8888")
    
    }
    
    

    m

重定向

package main

import (
    "net/http"

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

func main() {
    r := gin.Default()
    r.GET("/index", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
    })
    r.Run()
}

同步异步

  • goroutine机制可以方便地实现异步处理
  • 另外,在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
package main

import (
    "log"
    "time"

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

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 1.异步
    r.GET("/long_async", func(c *gin.Context) {
        // 需要搞一个副本
        copyContext := c.Copy()
        // 异步处理
        go func() {
            time.Sleep(3 * time.Second)
            log.Println("异步执行:" + copyContext.Request.URL.Path)
        }()
    })
    // 2.同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(3 * time.Second)
        log.Println("同步执行:" + c.Request.URL.Path)
    })

    r.Run(":8000")
}

gin中间件

  • 所有请求都经过此中间件
package main

import (
    "fmt"
    "time"

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

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        // 执行函数  如果不执行,测试是 第一次访问不会执行这个中间件
        c.Next()
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 注册中间件
    r.Use(MiddleWare())
    // {}为了代码规范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}

局部中间件

只会在使用的路由中执行

package main

import (
    "fmt"
    "time"

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

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        // 执行函数
        c.Next()
        // 中间件执行完后续的一些事情
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    //局部中间键使用
    r.GET("/ce", MiddleWare(), func(c *gin.Context) {
        // 取值
        req, _ := c.Get("request")
        fmt.Println("request:", req)
        // 页面接收
        c.JSON(200, gin.H{"request": req})
    })
    r.Run()
}

会话控制

  • 测试服务端发送cookie给客户端,客户端请求时携带cookie
package main

import (
   "github.com/gin-gonic/gin"
   "fmt"
)

func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 服务端要给客户端cookie
   r.GET("cookie", func(c *gin.Context) {
      // 获取客户端是否携带cookie
      cookie, err := c.Cookie("key_cookie")
      if err != nil {
         cookie = "NotSet"
         // 给客户端设置cookie
         //  maxAge int, 单位为秒
         // path,cookie所在目录
         // domain string,域名
         //   secure 是否智能通过https访问
         // httpOnly bool  是否允许别人通过js获取自己的cookie
         c.SetCookie("key_cookie", "value_cookie", 60, "/",
            "localhost", false, true)
      }
      fmt.Printf("cookie的值是: %s\n", cookie)
   })
   r.Run(":8000")
}

session

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/sessions"
)

// 初始化一个cookie存储对象
// something-very-secret应该是一个你自己的密匙,只要不被别人知道就行
var store = sessions.NewCookieStore([]byte("something-very-secret"))

func main() {
    http.HandleFunc("/save", SaveSession)
    http.HandleFunc("/get", GetSession)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("HTTP server failed,err:", err)
        return
    }
}

func SaveSession(w http.ResponseWriter, r *http.Request) {
    // Get a session. We're ignoring the error resulted from decoding an
    // existing session: Get() always returns a session, even if empty.

    // 获取一个session对象,session-name是session的名字
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 在session中存储值
    session.Values["foo"] = "bar"
    session.Values[42] = 43
    // 保存更改
    session.Save(r, w)
}
func GetSession(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    foo := session.Values["foo"]
    fmt.Println(foo)
}

删除session

    // 删除
    // 将session的最大存储时间设置为小于零的数即为删除
    session.Options.MaxAge = -1
    session.Save(r, w)

参数验证

结构体验证

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

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()
}

自定义验证

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()
}

或者

package main

import (
    "net/http"
    "reflect"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v8"
)

// Booking contains binded and validated data.
type Booking struct {
    //定义一个预约的时间大于今天的时间
    CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    //gtfield=CheckIn退出的时间大于预约的时间
    CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
    v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
    //field.Interface().(time.Time)获取参数值并且转换为时间格式
    if date, ok := field.Interface().(time.Time); ok {
        today := time.Now()
        if today.Unix() > date.Unix() {
            return false
        }
    }
    return true
}

func main() {
    route := gin.Default()
    //注册验证
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        //绑定第一个参数是验证的函数第二个参数是自定义的验证函数
        v.RegisterValidation("bookabledate", bookableDate)
    }

    route.GET("/5lmh", getBookable)
    route.Run()
}

func getBookable(c *gin.Context) {
    var b Booking
    if err := c.ShouldBindWith(&b, binding.Query); err == nil {
        c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}

// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-09-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-01"

多语言翻译验证

当业务系统对验证信息有特殊需求时,例如:返回信息需要自定义,手机端返回的信息需要是中文而pc端发挥返回的信息需要时英文,如何做到请求一个接口满足上述三种情况。

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    "github.com/go-playground/locales/zh_Hant_TW"
    ut "github.com/go-playground/universal-translator"
    "gopkg.in/go-playground/validator.v9"
    en_translations "gopkg.in/go-playground/validator.v9/translations/en"
    zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)

var (
    Uni      *ut.UniversalTranslator
    Validate *validator.Validate
)

type User struct {
    Username string `form:"user_name" validate:"required"`
    Tagline  string `form:"tag_line" validate:"required,lt=10"`
    Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}

func main() {
    en := en.New()
    zh := zh.New()
    zh_tw := zh_Hant_TW.New()
    Uni = ut.New(en, zh, zh_tw)
    Validate = validator.New()

    route := gin.Default()
    route.GET("/5lmh", startPage)
    route.POST("/5lmh", startPage)
    route.Run(":8080")
}

func startPage(c *gin.Context) {
    //这部分应放到中间件中
    locale := c.DefaultQuery("locale", "zh")
    trans, _ := Uni.GetTranslator(locale)
    switch locale {
    case "zh":
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "en":
        en_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "zh_tw":
        zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
        break
    default:
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    }

    //自定义错误内容
    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
    })

    //这块应该放到公共验证方法中
    user := User{}
    c.ShouldBind(&user)
    fmt.Println(user)
    err := Validate.Struct(user)
    if err != nil {
        errs := err.(validator.ValidationErrors)
        sliceErrs := []string{}
        for _, e := range errs {
            sliceErrs = append(sliceErrs, e.Translate(trans))
        }
        c.String(200, fmt.Sprintf("%#v", sliceErrs))
    }
    c.String(200, fmt.Sprintf("%#v", "user"))
}
拓扑排序 2023-02-10
模具解决方案 2024-03-27

评论区