Gin官方地址:https://gin-gonic.com/
Gin的Github 地址:https://github.com/gin-gonic/gin
gin官方简介

Gin is a web framework written in Golang.

It features a martini-like API with much better performance, up to 40 times faster.

If you need performance and good productivity, you will love Gin.

粗俗的翻译成中文就是Gin是一个web框架,并且性能雕的飞起,谁用谁知道

安装方式

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

导入

  1. import "github.com/gin-gonic/gin"

JSON

Gin 使用go包的中的encoding/json 作为json解析库 也可以使用第三方json解析库jsoniter

  1. o build -tags=jsoniter

Hello world

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. r.GET("/ping", func(c *gin.Context) {
  9. c.JSON(http.StatusOK, gin.H{
  10. "code":0,
  11. "message": "success",
  12. "data":"pong",
  13. })
  14. })
  15. r.Run()
  16. }

c.JSON方法就是返回一个JSON格式的字符串具体的执行函数是

  1. // render/json.go
  2. func WriteJSON(w http.ResponseWriter, obj interface{}) error {
  3. writeContentType(w, jsonContentType)
  4. encoder := json.NewEncoder(w)
  5. err := encoder.Encode(&obj)
  6. return err
  7. }

gin.H其实是一个 map[string]interface{}

  1. type H map[string]interface{}

执行

  1. $ curl http://127.0.0.1:8080/ping
  2. {"code":0,"data":"pong","message":"success"}

创建web服务请求,使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. "time"
  6. )
  7. func main() {
  8. router := gin.Default()
  9. router.GET("/", func(c *gin.Context) {
  10. c.JSON(http.StatusOK, gin.H{
  11. "method": c.Request.Method,
  12. "time": time.Now().Format("2006-01-02:15:04:05")})
  13. })
  14. router.Run(":8000")
  15. }

参数

路由参数

通过Param获取路径中的可变参数

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. r.GET("/user/:id", func(c *gin.Context) {
  9. id := c.Param("id")
  10. c.JSON(http.StatusOK,gin.H{"user_id":id})
  11. })
  12. r.GET("/user/:id/:name", func(c *gin.Context) {
  13. id := c.Param("id")
  14. name := c.Param("name")
  15. c.JSON(http.StatusOK,gin.H{"id":id,"name":name})
  16. })
  17. r.Run()
  18. }

go run main.go 然后执行请求

  1. curl -X GET 'http://localhost:8080/user/123'
  2. {"user_id":"123"}
  3. curl -X GET 'http://localhost:8080/user/你好'
  4. {"user_id":"你好"}
  5. curl -X GET 'http://localhost:8080/user/123/tony'
  6. {"id":"123","name":"tony"}

Gin路径中的匹配都是字符串,模糊匹配中对数字、字母和汉字都认为合法的
需要注意的是Gin的路由是单一的,不能有重复。比如这里我们注册了/users/:id,那么我们就不能再注册匹配/users/list路由。

  1. r.GET("/user/list", func(c *gin.Context) {
  2. c.JSON(http.StatusOK,gin.H{"hello":"world"})
  3. })

上面的路由会出现冲突报错

  1. panic: 'list' in new path '/user/list' conflicts with existing wildcard ':id' in existing prefix '/user/:id'

Query参数

  • query(?)参数可以通过DefaultQuery()或Query()方法获取
  • DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串 ```go package main

import ( “github.com/gin-gonic/gin” “net/http” )

func main() { router := gin.Default() router.GET(“/user”, func(c *gin.Context) { name:= c.DefaultQuery(“name”,”guest”) id := c.Query(“id”) c.JSON(http.StatusOK,gin.H{“name”:name,”id”:id}) }) router.Run(“:8090”) }

  1. ```bash
  2. curl -X GET 'http://127.0.0.1:8090/user?id=1&name=yangyu'
  3. {"id":"1","name":"yangyu"}
  4. curl -X GET 'http://127.0.0.1:8090/user?id=1'
  5. {"id":"1","name":"guest"}

表单参数

表单参数可以通过PostForm()方法获取

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. router := gin.Default()
  8. router.POST("/post", func(c *gin.Context) {
  9. page := c.Query("page")
  10. size := c.DefaultQuery("size", "10")
  11. id := c.PostForm("id")
  12. name := c.PostForm("name")
  13. c.JSON(http.StatusOK,gin.H{"Id":id,"page":page,"size":size,"name":name})
  14. })
  15. router.Run(":8090")
  16. }
  1. $ curl -X POST -F 'name=小明' -F 'id=101' 'http://127.0.0.1:8090/post?page=2'
  2. {"Id":"101","name":"小明","page":"2","size":"10"}

RestAPI

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. router := gin.Default()
  8. c := func(c *gin.Context) {
  9. c.JSON(http.StatusOK, gin.H{"method": c.Request.Method})
  10. }
  11. router.GET("/get", c)
  12. router.POST("/post", c)
  13. router.PUT("/put", c)
  14. router.DELETE("/delete", c)
  15. router.PATCH("/patch", c)
  16. router.HEAD("/head", c)
  17. router.OPTIONS("/options", c)
  18. router.Run(":8000")
  19. }

数据协议

目前主流的是json和ProtoBuf,xml已经逐渐西山,yaml 主要用于配置方面

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "github.com/gin-gonic/gin/testdata/protoexample"
  5. "net/http"
  6. )
  7. func main() {
  8. r := gin.Default()
  9. r.GET("/proto/json", func(c *gin.Context) {
  10. c.JSON(http.StatusOK, gin.H{"msg": "hello json", "code": 0})
  11. })
  12. r.GET("/proto/xml", func(c *gin.Context) {
  13. c.XML(http.StatusOK, gin.H{"message": "hello xml", "code": 0})
  14. })
  15. r.GET("/proto/yaml", func(c *gin.Context) {
  16. c.YAML(http.StatusOK, gin.H{"message": "hello yaml", "code": 0})
  17. })
  18. r.GET("/proto/pb", func(c *gin.Context) {
  19. reps := []int64{int64(1), int64(2)}
  20. label := "ProtoBuf"
  21. data := &protoexample.Test{
  22. Label: &label,
  23. Reps: reps,
  24. }
  25. c.ProtoBuf(http.StatusOK, data)
  26. })
  27. r.GET("/user/info", func(c *gin.Context) {
  28. user := struct {
  29. Id int64
  30. Name string
  31. IsVIP bool
  32. }{Id:101,Name:"小明",IsVIP:true}
  33. response := struct {
  34. Data interface{}
  35. Msg string
  36. Code int
  37. }{Data:user,Msg:"success",Code:0}
  38. c.JSON(http.StatusOK, response)
  39. })
  40. r.Run(":8090")
  41. }

执行请求.

  1. $ curl 'http://127.0.0.1:8090/proto/json'
  2. {"code":0,"msg":"hello json"}
  3. $ curl 'http://127.0.0.1:8090/user/info'
  4. {"Data":{"Id":101,"Name":"小明","IsVIP":true},"Msg":"success","Code":0}

参数请求校验

gin 格式校验使用了第三库是https://github.com/go-playground/validator

MustBind

主要包括Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader
底层调用的都是MustBindWith,如果校验不通过直接调用的c.AbortWithError(400, err).SetType(ErrorTypeBind)

ShouldBind

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. type LoginReq struct {
  7. Password string `json:"password" binding:"required"`
  8. Name string `json:"name" binding:"required"`
  9. }
  10. func main() {
  11. router := gin.Default()
  12. router.POST("/user", func(c *gin.Context) {
  13. var req LoginReq
  14. if err :=c.ShouldBind(&req);err!=nil{
  15. c.JSON(http.StatusBadRequest,gin.H{"code":400,"mes":"非法请求","data":err.Error()})
  16. return
  17. }
  18. c.JSON(http.StatusOK,gin.H{"code":0,"mes":"success","data":req})
  19. })
  20. router.Run(":8090")
  21. }
  1. curl -X POST -d '{"password":"123456","name":"123@qq.com"}' -H "Content-Type: application/json" 'http://127.0.0.1:8090/user'
  2. {"code":0,"data":{"password":"123456","name":"123@qq.com"},"mes":"success"}

如果缺少参数

  1. $ curl -X POST -d '{"password":"123456"}' -H "Content-Type: application/json" 'http://127.0.0.1:8090/user'
  2. {"code":400,"data":"Key: 'LoginReq.Name' Error:Field validation for 'Name' failed on the 'required' tag","mes":"非法请求"}

ShouldBindBodyWith

上面的代码也可以使用ShouldBindBodyWith,如果需要在其他地方使用body 建议使用ShouldBindBodyWith,

  1. func main() {
  2. router := gin.Default()
  3. router.POST("/user", func(c *gin.Context) {
  4. var req LoginReq
  5. if err :=c.ShouldBindBodyWith(&req,binding.JSON);err!=nil{
  6. c.JSON(http.StatusBadRequest,gin.H{"code":400,"mes":"非法请求","data":err.Error()})
  7. return
  8. }
  9. c.JSON(http.StatusOK,gin.H{"code":0,"mes":"success","data":req})
  10. })
  11. router.Run(":8090")
  12. }

ShouldBindBodyWith的源码就是上下文中存储了请求体,如果没有对请求体的额外使用地方 建议直接ShouldBindWith

  1. // ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
  2. // body into the context, and reuse when it is called again.
  3. //
  4. // NOTE: This method reads the body before binding. So you should use
  5. // ShouldBindWith for better performance if you need to call only once.
  6. func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
  7. var body []byte
  8. if cb, ok := c.Get(BodyBytesKey); ok {
  9. if cbb, ok := cb.([]byte); ok {
  10. body = cbb
  11. }
  12. }
  13. if body == nil {
  14. body, err = ioutil.ReadAll(c.Request.Body)
  15. if err != nil {
  16. return err
  17. }
  18. c.Set(BodyBytesKey, body)
  19. }
  20. return bb.BindBody(body, obj)
  21. }

binding.BindingBody 有如下具体类型

  1. var (
  2. JSON = jsonBinding{}
  3. XML = xmlBinding{}
  4. Form = formBinding{}
  5. Query = queryBinding{}
  6. FormPost = formPostBinding{}
  7. FormMultipart = formMultipartBinding{}
  8. ProtoBuf = protobufBinding{}
  9. MsgPack = msgpackBinding{}
  10. YAML = yamlBinding{}
  11. Uri = uriBinding{}
  12. Header = headerBinding{}
  13. )

自定义

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"`
    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 {
    if date, ok := field.Interface().(time.Time); ok {
        today := time.Now()
        if today.Year() > date.Year() || (today.Year() == date.Year() && today.YearDay() > date.YearDay()) {
            return false
        }
    }
    return true
}

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

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("bookabledate", bookableDate)
    }

    route.GET("/bookable", getBookable)
    route.Run(":8090")
}

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

中间件

需要做权限校验,需要在每个请求中加入权限业务逻辑 ,代码不优雅

func main() {
    router := gin.Default()
    router.GET("/bar", func(c *gin.Context) {
        token := c.Query("token")
        if len(token)>0 {
            c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":token})
        }else {
            c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
        }
    })
    router.GET("/foo", func(c *gin.Context) {
        token := c.Query("token")
        if len(token)>0 {
            c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":token})
        }else {
            c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
        }
    })
    router.Run(":8090")
}

利用中间件方式,类似于责任链模式

package main

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

func AuthMiddleWare() gin.HandlerFunc{
    return func(c *gin.Context) {
        token := c.Query("token")
        if len(token)>0 {
            c.Set("TOKEN",token)
            c.Next()
        }else {
            c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
        }
    }
}


func main() {
    router := gin.Default()
    router.Use(AuthMiddleWare())
    router.GET("/user", func(c *gin.Context) {
        v ,_ := c.Get("TOKEN")
        c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":v})
    })
    router.Run(":8090")
}

请求测试

$ curl "http://127.0.0.1:8090/bar?token=123456"
{"code":0,"data":"123456","msg":"success"}
$ curl "http://127.0.0.1:8090/bar"
{"code":401,"data":{},"msg":"权限错误"}

接口分组

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

    // Simple group: v1
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    // Simple group: v2
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}

Github的Gin项目

https://github.com/YahuiAn/Go-bjut

热加载调试 Hot Reload

Python 的 Flask框架,有 debug 模式,启动时传入 debug=True 就可以热加载(Hot Reload, Live Reload)了。即更改源码,保存后,自动触发更新,浏览器上刷新即可。免去了杀进程、重新启动之苦。
Gin 原生不支持,但有很多额外的库可以支持。例如

  • github.com/codegangsta/gin
  • github.com/pilu/fresh

这次,我们采用 github.com/pilu/fresh

go get -v -u github.com/pilu/fresh

安装好后,只需要将go run main.go命令换成fresh即可。每次更改源文件,代码将自动重新编译(Auto Compile)。

参考

https://geektutu.com/post/gee.html
https://github.com/gin-gonic/examples