Gin框架的学习

Posted weixin_45747080

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gin框架的学习相关的知识,希望对你有一定的参考价值。

什么是Gin?

Gin 是一个用 Go (Golang) 编写的 web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 你会爱上 Gin。

中文官方文档地址

安装Gin

使用Go Modules安装Gin

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

第一个Gin应用

创建main.go,注意目录结构,同时需要确保go.mod中已经引入gin

│  go.mod
│  go.sum
│  main.go

main.go:

package main

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

func main()  {
	//默认的路由引擎
	r := gin.Default()

	//检查连接
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK,gin.H{
			"message":"gin-demo server is running by gin",
		})
	})
    
	//启动Web服务
	r.Run(":9090")
}
  1. 利用gin.Default()创建默认的路由引擎r
  2. r.GET()即是处理HTTP请求中的GET方法
  3. r.Run()加上端口号即是启动web服务

处理HTTP请求

处理HTTP请求的GET方法是使用r.GET( ),第一个参数是处理的URI地址,第二个参数是具体的处理函数,上述是匿名函数的写法,你也可以写成具名函数,如:

package main

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

func main()  {
	//默认的路由引擎
	r := gin.Default()

	//检查连接
	r.GET("/",index)
    
	//启动Web服务
	r.Run(":9090")
}

func index(c *gin.Context) {
    c.JSON(http.StatusOK,gin.H{
        "message":"gin-demo server is running by gin",
    })
}

同理,处理POST方法即是r.POST(),PUT方法是r.PUT()等。如果想要同时匹配全部的HTTP请求的方法,可以使用r.Any()

返回JSON

可以利用c.JSON(code int, obj interface{})来返回JSON数据

gin内置的gin.H数据类型实际上就是Map[string]interface{}数据类型,以key-value的形式返回JSON数据

gin.H:

type H map[string]interface{}
r.GET("/", func(c *gin.Context) {
   c.JSON(http.StatusOK,gin.H{
      "message":"gin-demo server is running by gin",
   })
})

返回的数据

{
    "message": "gin-demo server is running by gin"
}

同理返回JSON对象则是

type Student struct {
	Name    string `json:"name,omitempty" form:"name"`
	Age     int    `json:"age,omitempty" form:"age"`
}

func main()  {
    //返回JSON对象
    r.GET("/student", func(c *gin.Context) {
        student := &Student{
            Name:    "student",
            Age:     20,
        }
        //返回JSON类型的对象
        c.JSON(http.StatusOK,student)
    })
}

返回的数据

{
    "name": "student",
    "age": 20
}

Tips

需要以JSON数据格式返回的属性的首字母需要大写(因为本质实际上就是通过反射来判断该结构体对象的字段是否能够导出),同时需要加上tag:`json:“name”`使得具体返回的字段名有tag决定,如果没有指定则是返回属性名,即Name

获取query参数(Query)

可以利用c.Query(key)c.GetQuery(key)c.DefaultQuery(key,defaultValue)来获取query中的参数

如:path

http://localhost:9090/query?id=1&name=test&age=18

则可以分别用过上述三种方式来获取参数

r.GET("/query", func(c *gin.Context) {
    //1、直接获取query
    id := c.Query("id")
    //2、获取query(返回value和ok)
    name, _ := c.GetQuery("name")
    //3、获取并且设置可能的默认值
    age := c.DefaultQuery("age","0")
    c.JSON(http.StatusOK,gin.H{
        "id": id,
        "name": name,
        "age": age,
    })
})

获取form表单参数(PostForm)

可以利用c.PostForm(key)c.GetPostForm(key)c.DefaultPostForm(key,defaultValue)来获取form(POST)表单提交的参数

r.POST("/form", func(c *gin.Context) {
    //1、直接获取form中的value
    id := c.PostForm("id")
    //2、获取form中value(返回value和ok)
    name, _ := c.GetPostForm("name")
    //3、返回form中的value并且设置可能的默认值
    age := c.DefaultPostForm("age", "10")
    c.JSON(http.StatusOK,gin.H{
        "id": id,
        "name": name,
        "age": age,
    })
})

获取path路径参数URI(Param)

可以利用c.Param(key)来获取path中的参数(URI)

如:path

http://localhost:9090/path/10/18

//path中使用`/:param`的方式来定义路径参数
r.GET("/path/:id/:age", func(c *gin.Context) {
    //获取路径参数
    id := c.Param("id")
    //params := c.Params	//可以使用c.Params来获取所有路径参数
    c.JSON(http.StatusOK,gin.H{
        "id": id,
    })
})

参数绑定至结构体(ShouldBind)

在大多数时候,返回的参数很多的情况下,如果使用Query或者PostForm去一一获取可能会比较慢,我们可以预先定义好结构体,然后在直接使用参数绑定ShouldBind()来使得参数能够自动匹配到结构体对象上。在这之前,我们需要先定义结构体

定义结构体

切记:需要在结构体上加上form的tag

type Student struct {
   Name    string `json:"name,omitempty" form:"name"`
   Age     int    `json:"age,omitempty" form:"age"`
}

绑定GET的query

r.GET("/bindGet", func(c *gin.Context) {
    //新建空对象
    var s Student
    //传入对象的地址
    err := c.ShouldBind(&s)
    if err != nil {
        //参数绑定失败
        c.JSON(http.StatusBadRequest,gin.H{
            "error": err.Error(),
        })
    }
    fmt.Printf("student = %v",s)
    c.JSON(http.StatusOK,s)
})

绑定POST的form

r.POST("/bindForm", func(c *gin.Context) {
    var s Student
    err := c.ShouldBind(&s)
    if err != nil {
        //参数绑定失败
        c.JSON(http.StatusBadRequest,gin.H{
            "error": err.Error(),
        })
    }
    fmt.Printf("student = %v",s)
    c.JSON(http.StatusOK,s)
})

绑定JSON对象

r.POST("/bindJson", func(c *gin.Context) {
    var s Student
    err := c.ShouldBind(&s)
    if err != nil {
        //参数绑定失败
        c.JSON(http.StatusBadRequest,gin.H{
            "error": err.Error(),
        })
    }
    fmt.Printf("student = %v",s)
    c.JSON(http.StatusOK,s)
})

Tips

基本上来自GET请求的query和POST请求的form对象以及json对象都能自动绑定到结构体对象上,不过需要注意的是需要传入对象的指针

上传文件

文件上传需要注意两个步骤

  1. 从POST请求(form表单)中读取文件(需要处理错误)
  2. 尝试将读取到的文件保存的服务端本地(需要处理错误)

单个文件上传

r.POST("/upload", func(c *gin.Context) {
    //1、从请求中读取文件
    file, err := c.FormFile("file") //根据文件的标识来获取文件(form表单的name属性)
    if err != nil {
        //文件读取错误
        c.JSON(http.StatusBadRequest,gin.H{
            "error": err.Error(),
        })
    } else {
        //文件读取成功
        //2、到读取到的文件保存到本地(服务端)
        filename := file.Filename	//上传的文件的文件名
        dst := path.Join("./", filename)	//将文件保存到的目标路径(`.\\`,即当前目录下)
        err := c.SaveUploadedFile(file, dst)
        if err != nil {
            //文件保存到本地失败
            c.JSON(http.StatusBadRequest,gin.H{
                "error": err.Error(),
            })
        } else {
            //文件保存到本地成功
            c.JSON(http.StatusOK,gin.H{
                "message": filename+" 上传成功",
            })
        }
    }
})

多个文件上传

多个文件上传只不过就是需要获取到文件列表,然后迭代文件列表并依次存储到本地服务器

r.POST("/uploadList", func(c *gin.Context) {
    //拿到form表单对象
    form, _ := c.MultipartForm()
    //根据form表单对象Map获取到file文件列表(根据form的name属性来获取)
    fileList := form.File["file"]
    //循环文件列表
    for _, file := range fileList {
        //得到文件名
        filename := file.Filename
        dst := path.Join("./",filename)
        c.SaveUploadedFile(file,dst)	//存储到服务端本地
    }
    c.JSON(http.StatusOK,gin.H{
        "message": "上传成功",
    })
})

Tips

无论是单文件上传还是多文件上传,都是先根据form表单的name属性来获取到文件或者文件列表,如果是文件列表则迭代文件列表,再将单个文件通过SaveUploadedFile(file,dst)来存储到本地

请求重定向,请求转发

HTTP重定向

将该HTTP请求(GET、POST均可)重定向到某个具体的location

r.GET("/redirectGet", func(c *gin.Context) {
    //注意第二个参数location需要加上协议名称(也就是http://)
    //跳转到baidu
   c.Redirect(http.StatusMovedPermanently,"http://www.baidu.com/")
})

请求转发(路由重定向)

将该HTTP请求交由本服务的其他路由来处理,即请求转发

r.GET("/redirectLocal", func(c *gin.Context) {
    //路由重定向到本机的`/`请求,交由它处理
    c.Request.URL.Path = "/"
    r.HandleContext(c)
})

路由、路由组、路由组嵌套

路由

在Gin中,像如下这样的处理HTTP请求的叫做一个路由

r.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK,gin.H{
        "message": "gin-router server is running by gin",
    })
})

r.GET()r.POST()r.Any()

路由组

前缀相同的HTTP请求可以组合成路由组,更加直观

非路由组形式

r.GET("/user/index", func(c *gin.Context) {})
r.GET("/user/about", func(c *gin.Context) {})
r.GET("/user/detail", func(c *gin.Context) {})

等价于

路由组形式(r.Group()

userGroup := r.Group("/user")
{
    //省略路由组,等价于访问/user/index
    userGroup.GET("/index", func(c *gin.Context) {})
    userGroup.GET("/about", func(c *gin.Context) {})
    userGroup.GET("/detail", func(c *gin.Context) {})
}

路由组嵌套

同时路由组还支持嵌套

v1 := r.Group("/v1")
{
    // 匹配/v1/api
    v1.GET("/api", func(c *gin.Context) {})
    v2 := v1.Group("/v2")
    {
        //匹配/v1/v2/api
        v2.GET("/api", func(c *gin.Context) {})
    }

}

Gin中间件

Gin框架允许开发者在处理请求过程中,加入开发者自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件(两种方式)

Gin的中间件必须是一个gin.HandlerFunc类型

如m1中间件(定义方式一):

//定义中间件方式一(不可以传递额外参数)
func m1(c *gin.Context)  {
	fmt.Println("m1 start")
    // do something
	fmt.Println("m1 end")
}

m2中间件(定义方式二):

//定义中间件方式二(可以传递额外参数)
func m2() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("m2 start")
		// do something
		fmt.Println("m2 end")
	}
}

Tips

根据中间件的定义方式一我们不难发现

	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK,gin.H{
			"message": "gin-middle server is running by gin",
		})
	})

在处理GET请求中的匿名函数其实也是一个比较特殊的gin.HandlerFunc,即最后一个中间件

路由使用中间件

GET(relativePath string, handlers ...HandlerFunc)
// GET方法的第二个形参是多个HandlerFunc

所以我们可以给GET方法加上多个gin.HandlerFunc来使用中间件

r.GET("/", m1, m2(), func(c *gin.Context) {
    fmt.Println("index start")
    c.JSON(http.StatusOK,gin.H{
        "message": "gin-middle server is running by gin",
    })
    fmt.Println("index end")
})

中间件执行顺序:

m1中间件执行完毕->m2中间件执行完毕->匿名函数执行完毕

全局注册中间件

	//注册全局中间件
	r.Use(m1)	//给全局路由注册m1中间件

路由组注册中间件(两种方式)

1、r.Group()注册中间件

//user路由组(并且使用m2中间件)
userGroup := r.Group("/user",m2())
{
    userGroup.GET("/v1", func(c *gin.Context) {
        fmt.Println("/user/v1 start")
        fmt.Println("/user/v1 end")
    })
    userGroup.GET("/v2", func(c *gin.Context) {
        fmt.Println("/user/v1 start")
        fmt.Println("/user/v1 end")
    })
}

2、Use()注册中间件

//user路由组
userGroup := r.Group("/user")
userGroup.Use(m2())	//user路由组注册m2中间件
{
    userGroup.GET("/v1", func(c *gin.Context) {
        fmt.Println("/user/v1 start")
        fmt.Println("/user/v1 end")
    })
    userGroup.GET("/v2", func(c *gin.Context) {
        fmt.Println("/user/v1 start")
        fmt.Println("/user/v1 end")
    })
}

c.Next( )

可以在中间件中使用Next()立即调用下一中间件,如:

//定义中间件方式一(不可以传递额外参数)
func m1(c *gin.Context)  {
	fmt.Println("m1 start")
	// do something
	c.Next()
	fmt.Println("m1 end")
}

//定义中间件方式二(可以传递额外参数)
func m2() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("m2 start")
		// do something
		c.Next()
		fmt.Println("m2 end")
	}
}

func index(c *gin.Context)  {
	fmt.Println("/ start")
	fmt.Println("/ end")
}

func main() {
	r := gin.Default()
	r.以上是关于Gin框架的学习的主要内容,如果未能解决你的问题,请参考以下文章

Gin框架的学习

Gin框架学习

Go语言web框架 gin

gin框架学习-Gin框架和Gorm框架搭建一个简单的API微服务

学习go gin框架

学习go gin框架