6.1 Go之web框架gin

前言

近来计划用go做一个项目,在选哪个web框架上比较纠结。go的web框架有很多,近几年流行的以性能见长的有gin、iris、echo等,经过查阅资料,及咨询从事go开发的同仁,最终选择用gin框架做。

主要基于以下几点:

  1. 人多占优(无论从使用人数还是参与维护的人数),这一点很重要。用的人多,不能证明他很好,但起码不差。维护的人多有利于版本的迭代,不至于给自己将来挖坑。
  2. 文档支持相对比较好,入手成本低。
  3. gin拥有详尽的出错信息,极为方便调试。
  4. 采用轻量级的中间件式框架,把网站变成只有api的一个个小服务。

官方地址:https://github.com/gin-gonic/gin 详细文档地址:https://godoc.org/github.com/gin-gonic/gin

安装

下载安装,安装环境 centos7

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

前提是你已经 安装go。

创建main.go 创建目录

  1. mkdir $GOPATH/src/test
  2. cd $GOPATH/src/test
  3. vim main.go
  1. package main
  2. import "github.com/gin-gonic/gin"
  3. func main() {
  4. r := gin.Default()
  5. r.GET("/ping", func(c *gin.Context) {
  6. c.JSON(200, gin.H{
  7. "message": "pong",
  8. })
  9. })
  10. r.Run() // listen and serve on 0.0.0.0:8080
  11. }

运行

  1. # run example.go and visit 0.0.0.0:8080/ping on browser
  2. $ go run example.go

访问 ip:8080/ping 8080为默认端口,你也可以通过 r.Run(端口号)设置。

路由

示例

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. )
  5. func main() {
  6. r := gin.Default()
  7. r.GET("/buildget", BuildGet)//BuildGet 方法,自己建
  8. r.POST("/buildpost", BuildPost)
  9. r.PUT("/buildput", BuildPut)
  10. r.DELETE("/builddelete", BuildDelete)
  11. r.PATCH("/buildpatch", BuildPatch)
  12. r.HEAD("/buildhead", BuildHead)
  13. r.OPTIONS("/buildoptions", BuildOptions)
  14. r.Run(":8282")
  15. }

带参数的路由

示例

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. //访问方式路由必须为/buildget/{参数} 不可以为/buildget/或/buildget
  9. r.GET("/buildget/:name", func(c *gin.Context) {
  10. name := c.Param("name") //通过Param获取
  11. c.String(http.StatusOK, "Hello world"+name)
  12. })
  13. //路由支持 /buildget/{参数}/ 或 /buildget/{参数}/{参数}
  14. r.GET("/buildget/:name/*action", func(c *gin.Context) {
  15. name := c.Param("name") //通过Param获取
  16. action := c.Param("action")
  17. c.String(http.StatusOK, "Hello world"+name+" "+action)
  18. })
  19. r.Run(":8282")
  20. }

多路由

示例

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. // Simple group: v1 访问方式 /v1/login
  9. v1 := r.Group("/v1")
  10. {
  11. v1.GET("/login", func(c *gin.Context) {
  12. c.String(http.StatusOK, "v1 login")
  13. })
  14. }
  15. // Simple group: v2 访问方式 /v2/login
  16. v2 := r.Group("/v2")
  17. {
  18. v2.GET("/login", func(c *gin.Context) {
  19. c.String(http.StatusOK, "v2 login")
  20. })
  21. }
  22. r.Run(":8282")
  23. }

参数

获取get方式路由的参数

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "net/http"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. //访问实例 /login?param1=a&param2=b&ids[1]=f&ids[2]=h
  9. r.GET("/login", func(c *gin.Context) {
  10. param1 := c.DefaultQuery("param1", "c") //设置初始值
  11. param2 := c.Query("param2")
  12. //param2 := c.Request.URL.Query().Get("param2")同上
  13. ids := c.QueryMap("ids") //ids数组
  14. fmt.Println(ids)
  15. c.String(http.StatusOK, "Hello world "+param1+"_"+param2)
  16. })
  17. r.Run(":8282")
  18. }

获取post方式路由的参数

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "fmt"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. //访问实例 /login?param1=a&param2=b
  9. r.POST("/login", func(c *gin.Context) {
  10. param1 := c.DefaultPostForm("param1", "c") //设置初始值
  11. param2 := c.PostForm("param2")
  12. param3,has :=c.GetPostForm("param2")
  13. names := c.PostFormMap("names")//names 是数组
  14. if !has {
  15. }
  16. fmt.Println( param1,param2,param3,names)
  17. })
  18. r.Run(":8282")
  19. }

上传单个文件

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "path"
  5. "os"
  6. "io"
  7. "time"
  8. )
  9. func main() {
  10. r := gin.Default()
  11. //访问实例
  12. r.POST("/upload", func(c *gin.Context) {
  13. //处理上传文件
  14. //根据当前时间生成目录
  15. timestamp := time.Now().Unix()
  16. tm2 := time.Unix(timestamp, 0)
  17. //创建上传目录
  18. uploadDir := "/images/" + tm2.Format("20060102") + "/"
  19. os.MkdirAll(uploadDir, os.ModePerm)
  20. file, header, err := c.Request.FormFile("file")
  21. if err == nil {
  22. filename := header.Filename
  23. //扩展名
  24. ext := path.Ext(header.Filename)
  25. defer out.Close()
  26. //生成文件名
  27. filefullname := uploadDir + filename + ext
  28. //fmt.Println(file, err, filename)
  29. out, err := os.Create(filefullname)
  30. if err != nil {
  31. }
  32. io.Copy(out, file)
  33. //c.SaveUploadedFile(file,filefullname)
  34. }
  35. })
  36. r.Run(":8282")
  37. }

上传多个文件

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "log"
  5. )
  6. func main() {
  7. r := gin.Default()
  8. // Set a lower memory limit for multipart forms (default is 32 MiB)
  9. r.MaxMultipartMemory = 8 << 20 // 8 MiB
  10. //访问实例
  11. r.POST("/upload", func(c *gin.Context) {
  12. //处理上传多个文件
  13. //获取上传的文件组
  14. form, _ := c.MultipartForm()
  15. files := form.File["upload[]"]
  16. for _, file := range files {
  17. log.Println(file.Filename)
  18. c.SaveUploadedFile(file,file.Filename)
  19. }
  20. })
  21. r.Run(":8282")
  22. }

Bind

很多时候客户端和服务端采用json进行通信,即无论返回的response还是提交的request,其content-type类型都是application/json的格式。对于gin框架来说,我们就需要bind功能来解决这个问题。

示例

  1. type User struct {
  2. Username string `form:"username" json:"username" binding:"required"`
  3. Password string `form:"password" json:"password" binding:"required"`
  4. }
  5. func Login(c *gin.Context) {
  6. var u User
  7. err :=c.BindJSON(&u)
  8. fmt.Println(err)
  9. fmt.Println(u)
  10. }

先建一个结构体user,再使用BindJSON绑定,将request中的Body中的数据按照JSON格式解析到u结构体中。

需要注意:

  • binding:”required” 字段对应的参数未必传没有会抛出错误,非banding的字段,对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。
  • 结构体字段类型和所传参数类型要一致。

Bind的实现都在gin/binding里面. 这些内置的Bind都实现了Binding接口, 主要是Bind()函数.

  • context.BindJSON() 支持MIME为application/json的解析
  • context.BindXML() 支持MIME为application/xml的解析
  • context.BindYAML() 支持MIME为application/x-yaml的解析
  • context.BindQuery() 只支持QueryString的解析, 和Query()函数一样
  • context.BindUri() 只支持路由变量的解析
  • Context.Bind() 支持所有的类型的解析, 这个函数尽量还是少用(当QueryString, PostForm, 路由变量在一块同时使用时会产生意想不到的效果), 目前测试Bind不支持路由变量的解析, Bind()函数的解析比较复杂, 这部分代码后面再看

针对content-type类型是application/json的格式的数据,也可以通过以下方式获取。

示例

  1. jsonstr, _ := ioutil.ReadAll(c.Request.Body)
  2. fmt.Println(string(jsonstr))
  3. var data map[string]interface{}
  4. err := json.Unmarshal(jsonstr, &data)
  5. if err != nil {
  6. fmt.Println(err)
  7. }
  8. fmt.Println(data)

获取header、body信息

  1. body,_ := ioutil.ReadAll(c.Request.Body)
  2. fmt.Println(string(body))
  3. for k,v :=range c.Request.Header {
  4. fmt.Println(k,v)
  5. }

获取ip

  1. func GetClientIp(c *gin.Context) string{
  2. ip:=c.ClientIP()
  3. if ip=="::1" {
  4. ip="127.0.0.1"
  5. }
  6. return ip
  7. }

参考:

https://www.haohongfan.com/post/2019-01-21-gin-03/

http://www.okyes.me/2016/05/03/go-gin.html

https://www.jianshu.com/p/a31e4ee25305

links