Gin框架介绍

Gin框架介绍

介绍

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。

快速入门

安装gin

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

引入gin

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

开始

package main

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

func main() {
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  router.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

路由

  • 普通路由

    router.GET("/", func)
    router.POST("/login", func)
    router.Any("/login", func)
    
  • 路由分组

    // 简单的路由组: v1
      {
        v1 := router.Group("/v1")
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
      }
    
      // 简单的路由组: v2
      {
        v2 := router.Group("/v2")
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
      }
    
  • RESTFUL

    router.GET("/user", QueryFunc)	//查询
    router.Post("/user", AddFunc)	// 新增
    router.Delete("/user", DeleteFunc)	// 删除
    router.PUT("/user", UpdateFunc)	// 更新(客户端提供完整数据)
    router.PATCH("/user", PatchUpdateFunc)	// 更新(客户端提供需要修改的数据)
    
  • 重定向

    // 重定向到外部
    router.GET("/test", func(c *gin.Context) {
      c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
    })
    
    // 重定向到内部
    router.POST("/test", func(c *gin.Context) {
      c.Redirect(http.StatusFound, "/foo")
    })
    
    
    router.GET("/test", func(c *gin.Context) {
        c.Request.URL.Path = "/test2"
        router.HandleContext(c)
    })
    router.GET("/test2", func(c *gin.Context) {
        c.JSON(200, gin.H{"hello": "world"})
    })
    
  • 静态文件

    func main() {
      router := gin.Default()
      router.Static("/assets", "./assets")	// 文件目录
      router.StaticFS("/more_static", http.Dir("my_file_system"))
      router.StaticFile("/favicon.ico", "./resources/favicon.ico")	// 单独的文件
    
      // 监听并在 0.0.0.0:8080 上启动服务
      router.Run(":8080")
    }
    

输出

  • XML/JSON/TOML/YAML/ProtoBuf

    c.JSON(http.StatusOK, struct/gin.H)
    c.XML(http.StatusOK, struct/gin.H)
    c.YAML(http.StatusOK, struct/gin.H)
    c.ProtoBuf(http.StatusOK, struct/gin.H)
    ...
    可以到定义文件中去看更多的输出方法
    
  • HTML

    func main() {
      router := gin.Default()
      router.LoadHTMLGlob("templates/*")
      //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
      router.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
          "title": "Main website",
        })
      })
      router.Run(":8080")
    }
    
    templates/index.tmpl
    <html>
      <h1>
        {{ .title }}
      </h1>
    </html>
    
    func main() {
      router := gin.Default()
      router.LoadHTMLGlob("templates/**/*")
      router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
          "title": "Posts",
        })
      })
      router.GET("/users/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
          "title": "Users",
        })
      })
      router.Run(":8080")
    }
    
    templates/posts/index.tmpl
    {{ define "posts/index.tmpl" }}
    <html><h1>
      {{ .title }}
    </h1>
    <p>Using posts/index.tmpl</p>
    </html>
    {{ end }}
    
    templates/users/index.tmpl
    {{ define "users/index.tmpl" }}
    <html><h1>
      {{ .title }}
    </h1>
    <p>Using users/index.tmpl</p>
    </html>
    {{ end }}
    
    自定义模板渲染器
    ...
    

参数

  • 参数绑定

    ShouldBind: 自动识别参数,并绑定对应字段到结构体中
    
    ShouldBindJSON	tag:json
    ShouldBindXML		tag:xml
    ShouldBindQuery	tag:form
    ShouldBindYAML	tag:yaml
    ShouldBindTOML	tag:toml
    ShouldBindHeader	tag:header	无法自动识别
    ShouldBindUri	tag:uri	无法自动识别
    
    type formA struct {
      Foo string `json:"foo" xml:"foo" binding:"required" form:"foo"`
    }
    
    type formA struct {
      Foo string `json:"foo" xml:"foo" binding:"required"`
    }
    
    type formB struct {
      Bar string `json:"bar" xml:"bar" binding:"required"`
    }
    
    func SomeHandler(c *gin.Context) {
      objA := formA{}
      objB := formB{}
      // c.ShouldBind 使用了 c.Request.Body,不可重用。
      if errA := c.ShouldBind(&objA); errA == nil {
        c.String(http.StatusOK, `the body should be formA`)
      // 因为现在 c.Request.Body 是 EOF,所以这里会报错。
      } else if errB := c.ShouldBind(&objB); errB == nil {
        c.String(http.StatusOK, `the body should be formB`)
      } else {
        ...
      }
    }
    

    要想多次绑定,可以使用 c.ShouldBindBodyWith

    
    func SomeHandler(c *gin.Context) {
      objA := formA{}
      objB := formB{}
      // 读取 c.Request.Body 并将结果存入上下文。
      if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
        c.String(http.StatusOK, `the body should be formA`)
      // 这时, 复用存储在上下文中的 body。
      } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
        c.String(http.StatusOK, `the body should be formB JSON`)
      // 可以接受其他格式
      } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
        c.String(http.StatusOK, `the body should be formB XML`)
      } else {
        ...
      }
    }
    
    c.MustBindWith(&obj, binding.JSON)
    如果发生绑定错误,则请求终止,并设置错误码为400。ShouldBind()返回错误给开发者,不会导致请求终止。
    
  • 非绑定获取

    r.GET("/:u/:p", func)
    c.Param("u")	// url: /11/22
    
    r.GET("/", func)
    c.Query("u")	// url: /?u=11
    c.DefaultQuery("u", "222")
    
    r.GET("/", func)
    c.PostForm("u")	// 获取表单中字段的值
    c.DefaultPostForm("u", "222")
    

中间件

  • 调用栈

    func mw1() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("mw1 before")
    		c.Next()
    		fmt.Println("mw1 after")
    	}
    }
    
    func mw2() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("mw2 before")
    		c.Next()
    		fmt.Println("mw2 after")
    	}
    }
    
    func Middleware() {
    	r := gin.Default()
    
    	r.GET("/", mw1(), mw2(), func(c *gin.Context) {
    		fmt.Println("self")
    		c.String(http.StatusOK, "self")
    	})
    
    	err := r.Run(":8080")
    	if err != nil {
    		panic(err)
    	}
    }
    
    输出:
    mw1 before
    mw2 before
    self
    mw2 after
    mw1 after
    
  • 用户认证

    // 模拟一些私人数据
    var secrets = gin.H{
      "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
      "austin": gin.H{"email": "austin@example.com", "phone": "666"},
      "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
    }
    
    func main() {
      router := gin.Default()
    
      // 路由组使用 gin.BasicAuth() 中间件
      // gin.Accounts 是 map[string]string 的一种快捷方式
      authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
        "foo":    "bar",
        "austin": "1234",
        "lena":   "hello2",
        "manu":   "4321",
      }))
    
      // /admin/secrets 端点
      // 触发 "localhost:8080/admin/secrets
      authorized.GET("/secrets", func(c *gin.Context) {
        // 获取用户,它是由 BasicAuth 中间件设置的
        user := c.MustGet(gin.AuthUserKey).(string)
        if secret, ok := secrets[user]; ok {
          c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
        } else {
          c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
        }
      })
    
      // 监听并在 0.0.0.0:8080 上启动服务
      router.Run(":8080")
    }使用中间件
    
  • 使用中间件

    func main() {
      // 新建一个没有任何默认中间件的路由
      r := gin.New()
    
      // 全局中间件
      // Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
      // By default gin.DefaultWriter = os.Stdout
      router.Use(gin.Logger())
    
      // Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
      router.Use(gin.Recovery())
    
      // 你可以为每个路由添加任意数量的中间件。
      router.GET("/benchmark", MyBenchLogger(), benchEndpoint)
    
      // 认证路由组
      // authorized := router.Group("/", AuthRequired())
      // 和使用以下两行代码的效果完全一样:
      authorized := router.Group("/")
      // 路由组中间件! 在此例中,我们在 "authorized" 路由组中使用自定义创建的
      // AuthRequired() 中间件
      authorized.Use(AuthRequired())
      {
        authorized.POST("/login", loginEndpoint)
        authorized.POST("/submit", submitEndpoint)
        authorized.POST("/read", readEndpoint)
    
        // 嵌套路由组
        testing := authorized.Group("testing")
        testing.GET("/analytics", analyticsEndpoint)
      }
    
      // 监听并在 0.0.0.0:8080 上启动服务
      router.Run(":8080")
    }
    

模型验证

  • 官方验证器

    Gin使用 go-playground/validator/v10 进行验证。 查看标签用法的全部文档.

    type LoginInfo struct {
    	Username string `json:"username" form:"username" binding:"required"`
    	Password string `json:"password" form:"password" binding:"number"`
    	Email    string `json:"email" form:"email" binding:"email"`
    }
    
    func Validator() {
    	r := gin.Default()
    
    	r.GET("/", func(c *gin.Context) {
    		login := LoginInfo{}
    		err := c.ShouldBind(&login)
    		if err != nil {
    			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    			return
    		}
    
    		c.JSON(http.StatusOK, login)
    	})
    
    	err := r.Run(":8080")
    	if err != nil {
    		panic(err)
    	}
    }
    
  • 自定义验证器

    import (
      "net/http"
      "reflect"
      "time"
    
      "github.com/gin-gonic/gin"
      "github.com/gin-gonic/gin/binding"
      "github.com/go-playground/validator/v10"
    )
    
    // Booking 包含绑定和验证的数据。
    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,bookabledate" time_format:"2006-01-02"`
    }
    
    var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
      date, ok := fl.Field().Interface().(time.Time)
      if ok {
        today := time.Now()
        if today.After(date) {
          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(":8085")
    }
    
    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()})
      }
    }
    

HTTPS

  • 自有证书

    Openssl 生成证书,如果只是提供API服务,可以用没有经过认证的证书,如果是网站,则需要认证证书

    router.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
    
  • 开源免费认证证书(Let's Encrypt)

    package main
    
    import (
      "log"
    
      "github.com/gin-gonic/autotls"
      "github.com/gin-gonic/gin"
      "golang.org/x/crypto/acme/autocert"
    )
    
    func main() {
      router := gin.Default()
    
      // Ping handler
      router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
      })
    
      m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
        Cache:      autocert.DirCache("/var/www/.cache"),
      }
    
      log.Fatal(autotls.RunWithManager(r, &m))
    }
    
posted @ 2025-07-28 23:26  Lucas_coming  阅读(60)  评论(0)    收藏  举报