Gin框架
文档
官方文档地址:介绍 | Gin Web Framework (gin-gonic.com)
1. 安装
要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。
1.下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
|
2.将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
|
3.(可选)如果使用诸如 http.StatusOK
之类的常量,则需要引入 net/http
包:
2. Gin使用
2.1 接口
使用gin编写一个接口:
package main
import ( "github.com/gin-gonic/gin" )
func main() { //1.创建一个默认的路由 router := gin.Default() //2.绑定路由规则和路由函数,访问/index的路由,将有对应的函数去处理 router.GET("/index", func(context *gin.Context) { //回调函数 context.String(200, "hello world") //返回一个东西 }) //3.启动监听,访问0.0.0.0:8088的地址,0.0.0.0代表本机所有ip router.Run(":8088") }
|
router:=gin.Default()
:这是默认的服务器。使用gin的Default
方法创建一个路由Handler
;
- 然后通过Http方法绑定路由规则和路由函数。不同于
net/http
库的路由函数,gin进行了封装,把request
和response
都封装到了gin.Context
的上下文环境中。
- 最后启动路由的Run方法监听端口。还可以用
http.ListenAndServe(":8080", router)
,或者自定义Http服务器配置。
router.Run(":8000")
http.ListenAndServe(":8000", router)
|
router.Run("0.0.0.0:8000")
|
2.2 响应
状态码: 200(http.StatusOK),正常响应
响应JSON
响应xml和yaml
响应html
文件响应
重定向
package main
import ( "github.com/gin-gonic/gin" )
// 重定向 func redirect(ctx *gin.Context) { ctx.Redirect(301, "https://www.baidu.com") }
func main() { router := gin.Default() // 路由初始化 default和new的区别:default多了两个中间件:日志和错误恢复。 router.GET("/", func(ctx *gin.Context) { ctx.String(200, "你好,小溪") }) router.GET("/baidu", redirect) router.Run(":80") }
|
2.3 Restful
1.获取文章 /blog/getXxx Get blog/Xxx
2.添加 /blog/addXxx POST blog/Xxx
3.修改 /blog/updateXxx PUT blog/Xxx
4.删除 /blog/delXxxx DELETE blog/Xxx
3. 请求参数
3.1 查询参数
Query:查询已有的参数
func _query(c *gin.Context) { fmt.Println(c.Query("user")) fmt.Println(c.GetQuery("user")) fmt.Println(c.QueryArray("user")) // 拿到多个相同的查询参数 fmt.Println(c.DefaultQuery("addr", "四川省")) }
|
3.2 动态参数
Param:路径可变化
func _param(c *gin.Context) { fmt.Println(c.Param("user_id")) fmt.Println(c.Param("book_id")) }
router.GET("/param/:user_id/", _param) router.GET("/param/:user_id/:book_id", _param)
// ?param/12 // ?param/12/123
|
3.3 URL参数
- URL参数可以通过DefaultQuery()或Query()方法获取
- DefaultQuery()若参数不存在,返回默认值,Query()若不存在,返回空串
- API ? name=zs
package main
import ( "fmt" "net/http"
"github.com/gin-gonic/gin" )
func main() { 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() }
|
3.4 表单
PostForm():文件上传,方法默认解析的是x-www-form-urlencoded或from-data格式的参数
http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
4.validator库
参考:https://www.liwenzhou.com/posts/Go/validator-usages/
在web开发中一个不可避免的环节就是对请求参数进行校验,通常我们会在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析请求中的参数,例如 gin 框架中的Bind
和ShouldBind
系列方法。本文就以 gin 框架的请求参数校验为例,介绍一些validator
库的实用技巧。
gin框架使用github.com/go-playground/validator进行参数校验,目前已经支持github.com/go-playground/validator/v10
了,我们需要在定义结构体时使用 binding
tag标识相关校验规则,可以查看validator文档查看支持的所有 tag。
首先来看gin框架内置使用validator
做参数校验的基本示例。
package main
import ( "net/http"
"github.com/gin-gonic/gin" )
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() { r := gin.Default()
r.POST("/signup", func(c *gin.Context) { var u SignUpParam if err := c.ShouldBind(&u); err != nil { c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // 保存入库等业务逻辑代码...
c.JSON(http.StatusOK, "success") })
_ = r.Run(":8999") }
|
翻译校验错误提示信息
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") }
|
curl -H "Content-type: application/json" -X POST -d '{"name":"q1mi","age":18,"email":"123.com"}' http://127.0.0.1:8999/signup // 请求
|
// 输出结果 {"msg":{"SignUpParam.Email":"Email必须是一个有效的邮箱","SignUpParam.Password":"Password为必填字段","SignUpParam.RePassword":"RePassword为必填字段"}}
|
自定义错误提示信息的字段名
上面的错误提示看起来是可以了,但是还是差点意思,首先是错误提示中的字段并不是请求中使用的字段,例如:RePassword
是我们后端定义的结构体中的字段名,而请求中使用的是re_password
字段。如何是错误提示中的字段使用自定义的名称,例如json
tag指定的值呢?
只需要在初始化翻译器的时候像下面一样添加一个获取json
tag的自定义方法即可。
// InitTrans 初始化翻译器 func InitTrans(locale string) (err error) { // 修改gin框架中的Validator引擎属性,实现自定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name })
zhT := zh.New() // 中文翻译器 enT := en.New() // 英文翻译器
// 第一个参数是备用(fallback)的语言环境 // 后面的参数是应该支持的语言环境(支持多个) // uni := ut.New(zhT, zhT) 也是可以的 uni := ut.New(enT, zhT, enT)
// ... liwenzhou.com ... }
|
{"msg":{"SignUpParam.email":"email必须是一个有效的邮箱","SignUpParam.password":"password为必填字段","SignUpParam.re_password":"re_password为必填字段"}} //输出
|
可以看到现在错误提示信息中使用的就是我们结构体中json
tag设置的名称了。
但是还是有点瑕疵,那就是最终的错误提示信息中心还是有我们后端定义的结构体名称——SignUpParam
,这个名称其实是不需要随错误提示返回给前端的,前端并不需要这个值。我们需要想办法把它去掉。
定义一个去掉结构体名称前缀的自定义方法:
func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res }
|
我们在代码中使用上述函数将翻译后的errors
做一下处理即可:
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类型错误则进行翻译 // 并使用removeTopStruct函数去除字段名中的结构体名称标识 c.JSON(http.StatusOK, gin.H{ "msg": removeTopStruct(errs.Translate(trans)), }) return }
|
{"msg":{"email":"email必须是一个有效的邮箱","password":"password为必填字段","re_password":"re_password为必填字段"}} //输出
|
自定义结构体校验方法
框架介绍
GoWeb框架
0.序言
我们需要实现一个 Web 应用,第一反应是应该使用哪个框架。
在设计一个框架之前,我们需要回答框架核心为我们解决了什么问题。
我们先看看标准库net/http
如何处理一个请求。
func main() { http.HandleFunc("/", handler) //鉴权:没有分组/统一鉴权的能力,需要在每个路由映射的handler中实现。 http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe("localhost:8000", nil)) }
func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) } 即监听端口,映射静态路由,解析HTTP报文。
|
- 路由(Routing):将请求映射到函数,支持动态路由。例如
'/hello/:name
。
- 模板(Templates):使用内置模板引擎提供模板渲染机制。
- 工具集(Utilites):提供对 cookies,headers 等处理机制。
- 插件(Plugin):Bottle本身功能有限,但提供了插件机制。可以选择安装到全局,也可以只针对某几个路由生效。
1.Gee框架
标准库启动Web服务
Go语言内置了 net/http
库,封装了HTTP网络编程的基础的接口,我们实现的Gee
Web 框架便是基于net/http
的
用 curl 这个工具测试
实现http.Handler接口