暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Gin快速入门

Golang学习杂记 2021-07-22
471

使用Go搭建Web后端,最简单的就是使用标准的net/http
库了,下面是一个简单的例子

package main

import (
 "fmt"
 "net/http"
)

func greet(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintln(w, "Hello World!")
}

func main() {
 http.HandleFunc("/", greet)
 http.ListenAndServe(":8080"nil)
}

运行代码之后,便会在8080端口处监听,接收客户端的请求,然后返回响应,访问http://localhost:8080
即可以访问到对应的内容

对于简单的服务器,使用标准库提供的函数就已经可以解决了,如果更加复杂一点,使用标准库处理就显得力不从心了,这时候我们可以使用网上提供的框架,比如Gin

Gin
使用起来特别方便(真的很方便!),并且支持中间件,异常处理,路由组等等,下面用code
演示一下如何使用吧。

快速入门

先我们需要下载gin,我们要保证设置了GO111MODULE=on
,然后进行下载

go mod init gin-demo
go get -u github.com/gin-gonic/gin

下面是一个简单的web服务,我们使用gin.Default()
创建一个默认的路由引擎,然后使用GET
方式注册一个路由,负责接受来自客户端的GET
请求,并且返回一个JSON
数据给客户端。

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",
  })
 })
 r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

使用curl进行测试结果如下,符合预期

$ curl http://localhost:8080/ping 
{"message":"pong"}

使用HTTP方法

// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
router := gin.Default()

router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)

使用路由组

使用Group
方法进行创建,路由组可以使得路由结构更加清晰,用大括号包围其实没有其他含义,主要是为了显示这部分路由属于该路由组

router := gin.Default()

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

返回内容

返回JSON内容

gin.Context
提供了JSON
方法, 我们可以直接使用, 第一个参数为返回的状态码,第二个参数为返回的数据,gin.H
其实就是map[string]interface{}
的简写,最后会被序列化返回给客户端。

router := gin.Default()
router.GET("/ping"func(c *gin.Context) {
  c.JSON(200, gin.H{
    "message""pong",
  })
})

返回HTML内容

首先需要加载HTML
模板文件,然后使用c.HTML
方法将数据注入到指定的模板文件中

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",
  })
})

模板文件posts/index.tmpl

{{ define "posts/index.tmpl" }}
<html><h1>
{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

返回字符串

String方法即可

router.GET("String"func(c *gin.Context) {
  c.String(http.StatusOK, "Hello World")
})

获取查询参数

对于简单的query
参数,使用GetQuery
DefaultQuery
方法就好了

router.GET("/query"func(c *gin.Context) {
  q, exist := c.GetQuery("q")
  if exist {
    c.JSON(200, gin.H{
      "data": q,
    })
  } else {
    c.JSON(200, gin.H{
      "code":    400,
      "message""q is not exist",
    })
  }
})

$ curl http://localhost:8080/query
{"code":400,"message":"q is not exist"}

$ curl http://localhost:8080/query?q=hello
{"data":"hello"}

不过如果需要获取到的query
参数是数组或者map,那么就需要使用其他的方式了,比如数组需要使用GetQueryArray
or QueryArray
GetQueryMap
or QueryMap

r.GET("/query_array"func(c *gin.Context) {
  q, exist := c.GetQueryArray("q")
  if exist {
    c.JSON(200, gin.H{
      "data": q,
    })
  } else {
    c.JSON(200, gin.H{
      "code":    400,
      "message""q is not exist",
    })
  }
})

r.GET("/query_map"func(c *gin.Context) {
  q, exist := c.GetQueryMap("q")
  if exist {
    c.JSON(200, gin.H{
      "data": q,
    })
  } else {
    c.JSON(200, gin.H{
      "code":    400,
      "message""q is not exist",
    })
  }
})

$ curl "http://localhost:8080/query_array?q=1&q=2"
{"data":["1","2"]}

$ curl "http://localhost:8080/query_map?q[1]=1&q[2]=2"
{"data":{"1":"1","2":"2"}}

获取表单内容

router.POST("/form"func(c *gin.Context) {
  name := c.PostForm("name")  // or GetPostForm
  list := c.PostFormArray("list"// or GetPostFormArray
  m := c.PostFormMap("map")  // or GetPostFormMap
  c.JSON(http.StatusOK, gin.H{
    "name": name,
    "list": list, 
    "map": m,
  })
})

$ curl  --form name=test --form list=1 --form list=2 --form map[1]=1 --form map[2]=2 http://localhost:8080/form
{"list":["1","2"],"map":{"1":"1","2":"2"},"name":"test"}

文件上传

单文件

router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20  // 8 MiB
r.POST("/upload/single"func(c *gin.Context) {
    // 单文件
    file, _ := c.FormFile("file")
    fmt.Println(file.Filename)
  
    // 上传文件至指定目录
    c.SaveUploadedFile(file, dst)
  
    c.JSON(http.StatusOK, gin.H{
      "code"0,
      "message""upload success",
    })
})

多文件

router.POST("/upload/multiple"func(c *gin.Context) {
  // Multipart form
  form, _ := c.MultipartForm()
  files := form.File["upload[]"]

  for _, file := range files {
    log.Println(file.Filename)

    // 上传文件至指定目录
    c.SaveUploadedFile(file, dst)
  }

使用中间件

中间件可以对经过一个路由的请求进行预处理,比如说鉴权,记录日志等等, gin
中创建一个中间件十分简单,我们只需要返回一个gin.Handler
的类型就好了。下面就创建了一个简单的中间件来计算路由花费的时间

func Cost() gin.HandlerFunc{
 return func(c *gin.Context) {
  start := time.Now()
  // 一定要调用
  c.Next()
  fmt.Println("total time: ", time.Now().Sub(start))
 }
}

使用中间件有多种方式,一种是全局中间件,一种是路由组中间件,一种单个路由中间件

router := gin.New() // 没有使用任何中间件的路由引擎
// 整个路由
router.Use(Cost())

// 路由组
group := r.Group("/api")
group.Use(Cost())
{
  group.GET("/greet", greet)
  group.POST("/form", form)
}
// 单个路由,不会影响其他路由
router.POST("/middleware", Cost(), controller)

绑定参数至结构体

在业务中,我们经常需要将用户发送过来的请求绑定到一个特定的结构体中,这样使用起来更加方便,gin
提供了大量的绑定方法,下面以ShouldBind
为例讲解如何使用:

// 首先声明需要绑定参数的结构体
type LoginReq struct {
  // form 参数指定需要绑定的表单内容
  // binding:"required" 表示不能非空,否则绑定的时候返回错误
  // 如果该字段非必须,可以去掉该tag
  Username string `form:"username" json:"username"  binding:"required"`
  Password string `form:"password" json:"password" binding:"required"`
}

r.POST("/login"func(c *gin.Context) {
  var form LoginReq

  if err := c.ShouldBind(&form); err != nil {
    c.JSON(http.StatusOK, gin.H{
      "code":    -1,
      "message": err.Error(),
    })
    return
  }
  fmt.Printf("%#v\n", form)
  c.JSON(http.StatusOK, gin.H{
    "code":    0,
    "message": form,
  })
})

gin
还有很多方面的特性,这里只是简单的介绍了如何去使用,更多的使用方法可以参考文档[1],个人之前也用过写了一个 小项目[2]

相关链接

[1]

文档: https://gin-gonic.com/docs/

[2]

小项目: https://github.com/junhaideng/Golang-BBS


文章转载自Golang学习杂记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论