Gin框架

一、Gin框架使用

1、Gin框架简介

Gin是一个用Go语言编写的Web框架。Gin框架拥有很好的性能,借助高性能的HttpRouter包,运行速度得到了极大的提升。

2、Gin框架安装与示例

(1)、安装

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

(2)、示例

func Get(c *gin.Context) {
    name := c.Query("name") // 从url中获取name字段
    // 返回字符串格式的数据
    //c.String(200, "欢迎您:%s", name)
    // c.JSON :返回JSON格式的数据
    c.JSON(200, gin.H{
        "code": http.StatusOK,
        "msg":  "返回信息",
        "data": "欢迎您" + name,
    })
}
func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // GET:请求方式:/hello:请求的路径,当客户端以Get方法请求/hello路径时,会执行后面的函数
    r.GET("/hello", Get)
    //r.POST("/user/login", Post)
    // 启动HTTP服务,默认在0.0.0.0:8080启动服务
    r.Run("127.0.0.1:8090")
}

image

3、Gin路由和控制器

路由:一个HTTP请求找到对应 的处理器函数的过程。处理器函数主要负责执行HTTP请求和响应任务。如下代码中的goLogin() 函数就是Gin的处理器函数。

func goLogin(context *gin.Context) {
    name := context.PostForm("username")
    password := context.PostForm("password")
    if name == "dujie" && password == "123456" {
        context.JSON(200, gin.H{
            "code":     http.StatusOK,
            "msg":      "登录成功",
            "username": name,
        })
    } else {
        context.JSON(http.StatusOK, gin.H{
            "code": 403,
            "msg":  "用户或密码错误",
        })
    }
    //context.String(200, "username=%s password=%s", name, password)
}

####### (1)、路由规则
一条路由规则由HTTP请求方法、URL路径、处理器函数这3个部分组成

  1. HTTP请求方法

常用的HTTP请求方法有GET、POST、PUT、DELETE等

  1. URL路径

Gin框架的URL路径有3种写法

1)静态URL路径,即不带任何参数的URL路径,例如:

/users/dujie
/user/1
/article/6

2)带路径参数的URL路径,URL路径中带有参数,参数由英文冒号”:” 跟着一个字符串定义。例如:

定义参数:id

3)带星号(*)模糊匹配参数的URL路径

星号(*)代表匹配任意路径的意思。必须在 * 后面指定一个参数名,之后可以通过这个参数获取 * 号匹配的内容。例如:”/user/*path” 可以通过path参数获取 * 号匹配的内容,如/user/1 、/user/dujie/comment/1等。

  1. 处理器函数

Gin框架的处理器函数定义如下:

type HandlerFunc func(*Context)

处理器函数接受一个上下文参数。可以通过上下文参数获取HTTP的请求参数,返回HTTP请求的响应。

####### (1)、分组路由
在做API开发时,如果要支持多个API版本,则可以通过分组路由来处理API版本。

func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // GET:请求方式:/hello:请求的路径,当客户端以Get方法请求/hello路径时,会执行后面的函数
    v1 := r.Group("/v1")
    {
        v1.POST("/login", goLogin)
    }
    v2 := r.Group("/v2")
    {
        v2.POST("/login", goLogin)
    }
    r.Run(":8090")

}
func goLogin(context *gin.Context) {
    fmt.Println("1:", context.Params)
    fmt.Println("2:", context.Request)
    fmt.Println("3:", context.Keys)

    name := context.PostForm("username")
    password := context.PostForm("password")
    if name == "dujie" && password == "123456" {
        context.JSON(200, gin.H{
            "code":     http.StatusOK,
            "msg":      "登录成功",
            "username": name,
        })
    } else {
        context.JSON(http.StatusOK, gin.H{
            "code": 403,
            "msg":  "用户或密码错误",
        })
    }
    //context.String(200, "username=%s password=%s", name, password)
}

上面的例子会将注册下面的路由信息:

/v1/login
/v2/login
4、Gin处理请求参数

(1)、获取GET请求参数

Gin获取GET请求参数常用方法如下:

// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
//
//	    GET /path?id=1234&name=Manu&value=
//		   c.Query("id") == "1234"
//		   c.Query("name") == "Manu"
//		   c.Query("value") == ""
//		   c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) {
	value, _ = c.GetQuery(key)
	return
}
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
//
//	GET /?name=Manu&lastname=
//	c.DefaultQuery("name", "unknown") == "Manu"
//	c.DefaultQuery("id", "none") == "none"
//	c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string {
	if value, ok := c.GetQuery(key); ok {
		return value
	}
	return defaultValue
}
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
//
//	GET /?name=Manu&lastname=
//	c.DefaultQuery("name", "unknown") == "Manu"
//	c.DefaultQuery("id", "none") == "none"
//	c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string {
	if value, ok := c.GetQuery(key); ok {
		return value
	}
	return defaultValue
}

(2)、获取POST请求参数

Gin获取POST请求参数的常用方法如下:

// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) (value string) {
	value, _ = c.GetPostForm(key)
	return
}
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string.
// See: PostForm() and GetPostForm() for further information.
func (c *Context) DefaultPostForm(key, defaultValue string) string {
	if value, ok := c.GetPostForm(key); ok {
		return value
	}
	return defaultValue
}
// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns ("", false).
// For example, during a PATCH request to update the user's email:
//
//	    email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
//		   email=                  -->  ("", true) := GetPostForm("email") // set email to ""
//	                            -->  ("", false) := GetPostForm("email") // do nothing with email
func (c *Context) GetPostForm(key string) (string, bool) {
	if values, ok := c.GetPostFormArray(key); ok {
		return values[0], ok
	}
	return "", false
}

使用方法示例:

// 跟PostForm的区别是:可以通过第2个参数设置参数默认值
name := context.DefaultPostForm("username", "name")
//name := context.PostForm("username")
password, ok := context.GetPostForm("password")
if !ok {
    log.Panicln("参数不存在")
}

(3)、获取URL路径参数

Gin获取URL路径参数是指,获取/user/:id 这类路由绑定的参数。/user/:id 绑定了一个参数id。获取URL路径参数的函数如下:

func (c *Context) Param(key string) string {
	return c.Params.ByName(key)
}
r.GET("/user/:id", func(context *gin.Context) {
    id := context.Param("id")
    fmt.Println(id)
    if id == "1" {
        context.JSON(200, gin.H{
            "code": 200,
            "msg": User{
                UserName: "dujie",
                Age:      32,
                Phone:    "15811047166",
            },
        })
    } else {
        context.JSON(200, gin.H{
            "code": "404",
            "msg":  "无此用户!!!",
        })
    }

})

(4)、将请求参数绑定到结构体

前面获取参数的方式都是逐个进行参数的读取,比较麻烦。Gin支持将请求参数自动绑定到一个结构体对象,这种方式支持GET/POST请求,也支持HTTP请求体中内容为JSON或XML格式的参数。

type User struct {
    UserName string `json:"userName" form:"username"`
    Phone    string `json:"phone" form:"phone"`
    Age      int    `json:"age" form:"age"`
}
标签 说明
json:”userName” 数据为JSON格式,并且json字段名为userName
form:”phone” 表单参数名为phone

模拟添加用户:

r.POST("/user/:id", func(context *gin.Context) {
    u := User{}
    if context.ShouldBind(&u) == nil {
        log.Println(u.UserName)
        log.Println(u.Phone)
        log.Println(u.Age)
    }
    context.JSON(200, gin.H{
        "code": http.StatusOK,
        "msg":  u,
    })
    //context.String(200, "Success")

})

context.ShouldBind(&u) 用于将请求中的数据绑定到指定的结构体

5、Gin生成HTTP请求响应

Gin支持以字符串、JSON、XML、文件等格式生成HTTP请求响应。gin.Context 上下文对象支持多种返回处理结果。

(1)以字符串方式生成HTTP请求响应。

通过String()方法生成字符串方式的HTTP请求响应。String()方法的定义如下:

// String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...any) {
	c.Render(code, render.String{Format: format, Data: values})
}

该方法使用示例如下:

func Handler(c *gin.Context){

        c.String(200,"hello%s ,欢迎%s","go","dujie")

}

(2)以JSON格式生成HTTP请求响应。

实际开发API接口时,最常用的就是JSON

r.POST("/user/:id", func(context *gin.Context) {
    u := User{}
    if context.ShouldBind(&u) == nil {
        log.Println(u.UserName)
        log.Println(u.Phone)
        log.Println(u.Age)
    }
    context.JSON(http.StatusOK, gin.H{
        "code": http.StatusOK,
        "msg":  u,
    })
})

(3)以XML格式生成HTTP请求响应。

定义一个User结构体,默认结构体的名字就是XML的根节点名字。

type User struct {
    UserName string `xml:"userName" form:"username"`
    Phone    string `xml:"phone" form:"phone"`
    Age      int    `xml:"age" form:"age"`
}
    r.GET("/user/:id", func(context *gin.Context) {
        id := context.Param("id")
        fmt.Println(id)
        if id == "1" {
            u := &User{
                UserName: "dujie",
                Age:      32,
                Phone:    "15811047166",
            }
            context.XML(200, u)
        } else {

        }

image

(4)以文件格式生成HTTP请求响应。

通过File() 方法直接返回本地文件,参数为本地文件地址

func(c *gin.Context){
// 通过File()方法直接返回本地丈件’参数为本地丈件地址  ′
      c.File("/var/www/gin/test.jpg")
}

(5)设置HTTP响应头。

Gin中提供了Header()方法来设置HTTP响应头。默认采用key/value方式,支持设置多个Header

    r.GET("/user/:id", func(context *gin.Context) {
        id := context.Param("id")
        fmt.Println(id)
        context.Header("Content-Type", "application/json")
        context.Header("site", "dujie")
}

image

(6)Gin处理静态文件

如果项目中包含JS、CSS、JPG之类的静态文件,下面的例子:

func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    r.Static("/assets","/var/www/gin/assets")
    r.StaticFile("/favicon.ico","./static/favicon.ico")
    r.Run(":8080")
}

(7)Gin处理cookie

Gin主要通过上下文对象提供的SetCookie()Cookie() 两个方法操作cookie,这两个函数都是对go语言net/http包中http.SetCookie()方法的重新封装,本质是一样

  1. 设置cookie

Gin使用SetCookie()方法设置cookie。

// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,  // Cookie 的名称
		Value:    url.QueryEscape(value),  // Cookie的值
		MaxAge:   maxAge, // 过期时间(秒)
		Path:     path, // 路径
		Domain:   domain, // 域
		SameSite: c.sameSite, 
		Secure:   secure, // 是否只允许HTTPs访问
		HttpOnly: httpOnly, // 是否允许浏览器仅通过HTTP获取Cookie
	})
}
r.GET("/user/:id", func(context *gin.Context) {
    context.SetCookie("my_cookie", "cookievalue", 3600, "/", "localhost", false, true)
    id := context.Param("id")
    fmt.Println(id)
    context.Header("Content-Type", "application/json")
    context.Header("site", "dujie")
    if id == "1" {
        u := User{
            UserName: "dujie",
            Age:      32,
            Phone:    "15811047166",
        }
        //context.XML(200, u)
        context.JSON(200, gin.H{
            "code": 200,
            "msg":  u,
        })
    } else {
        context.JSON(200, gin.H{
            "code": "404",
            "msg":  "无此用户!!!",
        })
    }

})

image

  1. 读取Cookie

Gin使用Cookie() 方法读取cookie

func main() {
    r := gin.Default()
    r.GET("/user/:id", func(context *gin.Context) {
        //context.SetCookie("cookie", "value", 3600, "/", "127.0.0.1", false, true)
        cookie, err := context.Cookie("cookie")
        if err != nil {
            log.Panicln(err)
        }
  1. 删除Cookie

将SetCookie() 方法的MaxAge参数设置为-1,就可以删除cookie

context.SetCookie("cookie", "value", -1, "/", "127.0.0.1", false, true)

(8)Gin文件上传

Gin使用SaveUploadFile() 方法实现文件上传

func main() {
    r := gin.Default()
    r.MaxMultipartMemory = 64 <<20
    r.POST("/upload", func(context *gin.Context) {
        // file 是表单字段名字
        file,_ := context.FormFile("file")
        // 打印上传的文件名
        log.Println(file.Filename)
        // 将上传的文件保存到./dujie.jpg中
        context.SaveUploadedFile(file,"./dujie.jpg")
        context.String(200,fmt.Sprintf("'%s' uploaded!",file.Filename))
    })
    r.Run(":8090")

}

(9)Gin中间件

Gin中,中间件(Middleware)是指可以拦截HTTP请求——响应生命周期的特殊函数。在请求——响应生命周期中可以注册多个中间件。每个中间件执行不同的功能,一个中间件执行完,才轮到下一个中间件执行,中间件常用的应用场景如下:

  • 请求限速
  • API接口签名处理
  • 权限校验
  • 统一错误处理

如果想拦截所有请求,则可以开发一个中间件函数来实现。Gin支持设置全局中间件和针对路由分组的中间件。在设置全局中间件后,会拦截所有请求。通过分组路由设置的中间件,仅对这个分组下的路由起作用

(9.1)使用中间件

Gin中使用Use()方法来使用中间件

func main() {
    r := gin.Default()
    // 通过Use()方法设置中间件
    // 设置日志中间件,主要用于打印请求日志
    r.Use(gin.Logger())
    // 设置Recovery中间件,主要用于拦截panic错误,不至于导致程序崩溃
    r.Use(gin.Recovery())
    r.GET("/user/:id", func(context *gin.Context) {
        context.JSON(200, gin.H{
            "code": 200,
            "msg":  "成功",
        })
    })
    r.Run(":8090")

}

(9.2)自定义中间件

func Logger() gin.HandlerFunc {
    return func(context *gin.Context) {
        t := time.Now()
        // 可以通过上下文对象,设置一些依附在上下文对象里面的键值数据
        context.Set("example", "这是一个中间件数据")
        // 在这里处理请求到达处理器函数之前的逻辑

        // 调用下一个中间件,或者处理器的处理函数,具体得看注册了多少个中间件
        context.Next()
        // 在这里可以处理返给客户端之前的响应逻辑
        latency := time.Since(t)
        log.Println(latency)

        // 例如,查询请求状态码
        status := context.Writer.Status()
        log.Println(status)

    }
}
func main() {
    r := gin.Default()
    // 注册上面自定义的日志中间件
    r.Use(Logger())
    r.GET("/hello", func(context *gin.Context) {
        // 获取日志中间件中注入的键值数据,接口断言是否是string类型
        example := context.MustGet("example").(string)
        log.Println(example)
    })
    r.Run(":8090")

}
// 运行访问后输出
[GIN-debug] GET    /hello                    --> main.main.func1 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8090
2023/08/30 09:07:53 这是一个中间件数据
2023/08/30 09:07:53 256.417µs
2023/08/30 09:07:53 200
[GIN] 2023/08/30 - 09:07:53 | 200 |     369.125µs |       127.0.0.1 | GET      "/hello"

(10)Gin处理session

在Gin中,依赖”github.com/gin-contrib/sessions”包中的中间件处理session。该包中的中间件支持cookie、MemStore、Redis、Memcached、Mongodb引擎。

1、安装

go get -u github.com/gin-contrib/sessions

2、用法示例

func main() {
    r := gin.Default()
    // 创建基于cookie的存储引擎,password123456参数是用于加密的秘钥
    store := cookie.NewStore([]byte("password123456"))
    // 设置session中间件,参数my_session 指的是session的名字,也是cookie的名字
    // store是前面创建的存储引擎,可以将其替换成其他存储引擎,如Redis等
    r.Use(sessions.Sessions("my_session", store))
    r.GET("/hello", func(context *gin.Context) {
        // 初始化session对象
        session := sessions.Default(context)
        // 通过session Get()函数读取session值
        // session 是键值对格式数据,因此需要通过key查询数据
        if session.Get("hello") != "world" {
            // 设置session数据
            session.Set("hello", "world")
            // 删除session数据
            session.Delete("dujie")
            // 保存session数据
            session.Save()
            // 删除整个session
            // session.Clear()
        }
        context.JSON(200, gin.H{
            "hello": session.Get("hello"),
        })
    })
    // 获取session
    r.GET("/session", Handler)

    r.Run(":8090")

}
// 获取session的handler函数
func Handler(c *gin.Context) {
    data, err := c.Cookie("my_session")
    fmt.Println(data)
    fmt.Println(err)
    if err == nil {
        c.String(200, data)
        return
    }
    c.String(200, "not found")
}

image

3、基于Redis存储引擎的session

如果想将session数据保存到Redis中,则只要将session的存储引擎改成Redis即可

安装Gin的Redis存储引擎包

go get -u github.com/gin-contrib/sessions/redis

基于Redis存储引擎示例

func main() {
    r := gin.Default()
    //store := cookie.NewStore([]byte("password123456"))
    // 初始化基于redis的存储引擎
    store, err := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("password"))
    if err != nil {
        log.Panicln(err)
    }
    r.Use(sessions.Sessions("my_session", store))
    r.GET("/hello", func(context *gin.Context) {
        session := sessions.Default(context)
        var count int
        v := session.Get("count")
        if v == nil {
            count = 0
        } else {
            count = v.(int)
            count++
        }
        session.Set("count", count)
        session.Save()
        context.JSON(200, gin.H{
            "count": count,
        })
    })
    r.GET("/session", Handler)
    r.Run(":8090")

}
func Handler(c *gin.Context) {
    // 上下文中获取session
    session := sessions.Default(c)
    count := session.Get("count")
    fmt.Println("————————", count)
    if count != nil {
        c.JSON(200, gin.H{
            "count": count,
        })
        return
    }
    c.String(200, "not found")
}