简介


Gin是一个用Go (Golang) 编写的web框架。它具有类似martini-like的API,具有更好的性能,由于httprouter,速度提高了40倍。如果你需要性能和良好的生产力,你会喜欢它的。


如何使用


  1. import "github.com/gin-gonic/gin"
  1. r := gin.New()
  2. // or
  3. r := gin.Default()
  1. // 创建仅接收Get请求方法
  2. r.GET("/", IndexHandler)
  3. // 创建仅接收Post请求方法
  4. r.Post("/",IndexHandler)
  5. // 创建接收所有请求方法
  6. r.Any("/",IndexHandler)
  1. // 绑定地址和端口
  2. err := r.Run("127.0.0.1:8080")
  3. if err != nil {
  4. fmt.Printf("gin server start error : %s\n", err.Error())
  5. }
  1. func IndexHandler(ctx *gin.Context) {
  2. // 返回字符串
  3. ctx.String(200,"hello")
  4. }

一些实践


路由

通常情况下,一个项目会有很多相似的路由,比如: /user/login``/user/registry``/user/logout等,单个写起来会很麻烦,并且不利于管理,gin中提供路由组来管理这些相同前缀的路由。

  1. func main() {
  2. r := gin.Default()
  3. // 创建用户路由组
  4. userRg := r.Group("/user")
  5. userRg.GET("/login")
  6. userRg.GET("/registry")
  7. userRg.GET("/logout")
  8. r.Run(":80")
  9. }

合理利用路由组可以极大的简化声明路由时产生的问题

  1. r.NoRoute(func(ctx *gin.Context) {
  2. ctx.Header("Content-Type", "text/html")
  3. ctx.String(200, "<h1>404</h1>")
  4. })

参数获取

  1. // 获取Query参数
  2. key := ctx.Query("key")
  3. fmt.Printf("key: %s\n", key)
  4. // 获取Body-FromData参数
  5. name := ctx.PostForm("name")
  6. fmt.Printf("name: %s\n", name)
  7. // 绑定到结构体
  8. var p struct {
  9. Key string `uri:"key"`
  10. Name string `form:"name"`
  11. }
  12. // 根据Content-Type绑定值
  13. _ = ctx.ShouldBind(&p)
  14. fmt.Printf("p: %+v\n", p)
  1. // 绑定到结构体
  2. // req is Request
  3. var req struct {
  4. Key string `form:"key"` // FormData & QueryParam
  5. Name string `form:"name"` // ...
  6. Password string `json:"password"` // JsonData
  7. Version string `header:"version"` // Header
  8. }
  9. // 根据Content-Type绑定值
  10. _ = ctx.ShouldBind(&req)
  11. // 绑定JsonData参数
  12. _ = ctx.ShouldBindJSON(&req)
  13. // 绑定Header
  14. _ = ctx.ShouldBindHeader(&req)
  15. fmt.Printf("request: %+v\n", req)

单独调用ShouldBind方法时会根据Content-Type绑定值,所以在同时使用多种参数类型时需要单独调用具体绑定方法,如ShouldBindJSON``ShouldBindQuery QueryParam参数默认情况下是没有Content-Type的,默认ShouldBind方法会通过FromDataTag绑定

  1. import (
  2. "fmt"
  3. "net/http"
  4. "github.com/gin-gonic/gin"
  5. "github.com/gin-gonic/gin/binding"
  6. "github.com/go-playground/locales/en"
  7. "github.com/go-playground/locales/zh"
  8. ut "github.com/go-playground/universal-translator"
  9. "github.com/go-playground/validator/v10"
  10. enTranslations "github.com/go-playground/validator/v10/translations/en"
  11. chTranslations "github.com/go-playground/validator/v10/translations/zh"
  12. )
  13. var trans ut.Translator
  14. // transInit
  15. // local 通常取决于 http 请求头的 'Accept-Language'
  16. func transInit(local string) (err error) {
  17. if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  18. zhT := zh.New() // chinese
  19. enT := en.New() // english
  20. uni := ut.New(enT, zhT, enT)
  21. var o bool
  22. trans, o = uni.GetTranslator(local)
  23. if !o {
  24. return fmt.Errorf("uni.GetTranslator(%s) failed", local)
  25. }
  26. // register translate
  27. // 注册翻译器
  28. switch local {
  29. case "zh":
  30. err = chTranslations.RegisterDefaultTranslations(v, trans)
  31. default:
  32. err = enTranslations.RegisterDefaultTranslations(v, trans)
  33. }
  34. return
  35. }
  36. return
  37. }
  38. func main() {
  39. if err := transInit("zh"); err != nil {
  40. fmt.Printf("init trans failed, err:%v\n", err)
  41. return
  42. }
  43. r := gin.Default()
  44. r.GET("/", func(ctx *gin.Context) {
  45. var req struct {
  46. Username string `form:"username" binding:"required"` // 必传参数
  47. Password string `form:"password" binding:"min=6,max=10"` // 长度: 最小6,最大15
  48. Age int `form:"age" binding:"gt=10,lt=60"` // 数值: 大于10,小于60
  49. Version string `header:"version"`
  50. }
  51. if err := ctx.ShouldBind(&req); err != nil {
  52. errs, ok := err.(validator.ValidationErrors)
  53. if !ok {
  54. // 非validator.ValidationErrors错误直接返回
  55. }
  56. ctx.JSON(http.StatusOK, errs.Translate(trans))
  57. }
  58. })
  59. r.Run(":80")
  60. }
  1. file, err := ctx.FormFile("file")
  2. if err != nil {
  3. ctx.String(http.StatusBadRequest, fmt.Sprintf("param error: %s", err.Error()))
  4. return
  5. }
  6. fmt.Printf("filename: %s\n", file.Filename)
  7. fmt.Printf("file_size: %d\n", file.Size)
  8. f, err := file.Open()
  9. if err != nil {
  10. ctx.String(http.StatusBadRequest, fmt.Sprintf("open file error: %s", err.Error()))
  11. }
  12. defer func(f multipart.File) {
  13. _ = f.Close()
  14. }(f)
  15. var fileByte []byte
  16. buf := make([]byte, 1024)
  17. for {
  18. var num int
  19. if num, err = f.Read(buf); err != nil {
  20. if err == io.EOF {
  21. break
  22. }
  23. }
  24. fileByte = append(fileByte, buf[:num]...)
  25. }
  26. fmt.Printf("fileByte:\n%s\n", fileByte)
  1. // 接收 multipart/form-data类型参数
  2. // form 为FromData map
  3. form, err := ctx.MultipartForm()
  4. if err != nil {
  5. ctx.String(http.StatusBadRequest, fmt.Sprintf("param error: %s", err.Error()))
  6. return
  7. }
  8. files := form.File["files"]
  9. if len(files) == 0 {
  10. ctx.String(http.StatusBadRequest, "files is none")
  11. return
  12. }
  13. for _, file := range files {
  14. // 存储文件
  15. if err := ctx.SaveUploadedFile(file, file.Filename); err != nil {
  16. ctx.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
  17. return
  18. }
  19. }
  20. ctx.String(200, fmt.Sprintf("upload ok %d files", len(files)))

中间件

  1. func TokenMid() gin.HandlerFunc {
  2. return func(ctx *gin.Context) {
  3. token, _ := ctx.Cookie("token")
  4. if token == "" {
  5. ctx.String(http.StatusUnauthorized, "please login !")
  6. ctx.Abort()
  7. }
  8. }
  9. }

上面代码定义了Token中间件,可以在执行Handler之前检查Cookietoken是否存在,不存在则中断执行并返回

  1. func main() {
  2. r := gin.Default()
  3. r.Use(TokenMid()).Get("/user/info",func (ctx *gin.Context) {
  4. ctx.String(200,"success")
  5. })
  6. }