Gin的基本使用| 青训营

160 阅读5分钟

Gin是一个用于构建Web应用程序的轻量级框架,基于Go语言开发。它提供了简单、高效和灵活的API,使得开发Web应用程序和API变得容易。Gin的设计理念是尽可能快速地处理HTTP请求,并提供易于理解和使用的接口。

快速入门

  1. 通过gin.Default()创建了一个带有默认中间件的Gin引擎
  2. 通过r.GET定义路由以及它的处理函数
  3. 通过r.Run()启动Gin服务器
package main
​
import "github.com/gin-gonic/gin"func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

Gin路由

在Gin中,路由用于将HTTP请求映射到相应的处理函数。Gin提供了多种方式来定义和设置路由,包括基本路由、路由组、参数路由等。以下是Gin中路由的基本使用方法

基本路由

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
​
    // 定义基本路由,处理GET请求到根路径"/"
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, Gin!")
    })
​
    r.Run(":8080")
}

路由组

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
​
    // 定义路由组,处理GET请求到"/api"路径
    api := r.Group("/api")
    {
        api.GET("/users", func(c *gin.Context) {
            c.String(200, "List of users")
        })
​
        api.POST("/users", func(c *gin.Context) {
            c.String(200, "Create a new user")
        })
    }
​
    r.Run(":8080")
}

参数路由

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
​
    // 定义带参数的路由,处理GET请求到路径"/user/:id"
    r.GET("/user/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.String(200, "User ID: "+id)
    })
​
    r.Run(":8080")
}

路由匹配顺序

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
​
    // 第一个路由,精确匹配
    r.GET("/users", func(c *gin.Context) {
        c.String(200, "List of users")
    })
​
    // 第二个路由,通配符匹配
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.String(200, "User ID: "+id)
    })
​
    r.Run(":8080")
}

响应数据

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
// ResponseData 表示响应数据的结构体
type ResponseData struct {
    Code int         `json:"code"`
    Data interface{} `json:"data"`
    Msg  string      `json:"msg"`
}
​
func main() {
    r := gin.Default()
​
    // 定义基本路由,处理GET请求到根路径"/"
    r.GET("/", func(c *gin.Context) {
        // 创建一个ResponseData结构体实例,用于构造响应数据
        respData := ResponseData{
            Code: 200,
            Data: gin.H{"message": "Hello, Gin!"},
            Msg:  "Success",
        }
​
        // 使用JSON方法将ResponseData作为JSON响应数据返回给客户端
        c.JSON(200, respData)
    })
​
    r.Run(":8080")
}

在Gin中,gin.H是一个简化的map[string]interface{}类型,用于在处理HTTP请求时构建和处理JSON响应的键值对。使用gin.H构造JSON十分方便。

Gin中间件

在Gin中,中间件是一种在处理请求和发送响应之间执行的功能。中间件可以用于访问请求和响应参数,执行某些逻辑,或者在请求传递给路由处理函数之前进行预处理。

中间件是通过使用gin.HandlerFunc类型来实现的。下面是一个简单的中间件示例,它在请求到达路由处理函数之前输出日志信息:

基本使用

通过r.Use()配置中间件

package main
​
import (
    "github.com/gin-gonic/gin"
    "time"
)
​
// LoggerMiddleware 是自定义的日志中间件
func LoggerMiddleware(c *gin.Context) {
    // 在处理请求之前记录请求的起始时间
    start := time.Now()
​
    // 处理请求,继续向下执行
    c.Next()
​
    // 在请求处理完成后记录请求处理时间
    elapsed := time.Since(start)
​
    // 输出日志信息
    c.Writer.WriteString("Request took: " + elapsed.String())
}
​
func main() {
    r := gin.Default()
​
    // 使用LoggerMiddleware中间件
    r.Use(LoggerMiddleware)
​
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, Gin!")
    })
​
    r.Run(":8080")
}

中间件执行顺序

中间件的执行顺序是按照添加它们的顺序进行的,先添加的中间件先执行,后添加的中间件后执行。在每个中间件函数中,通过c.Next()调用链实现中间件的连续执行。

示例:

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
​
    // 第一个中间件
    r.Use(func(c *gin.Context) {
        c.Writer.WriteString("First Middleware - Before\n")
        c.Next()
        c.Writer.WriteString("First Middleware - After\n")
    })
​
    // 第二个中间件
    r.Use(func(c *gin.Context) {
        c.Writer.WriteString("Second Middleware - Before\n")
        c.Next()
        c.Writer.WriteString("Second Middleware - After\n")
    })
​
    // 路由处理函数
    r.GET("/", func(c *gin.Context) {
        c.Writer.WriteString("Main Handler\n")
    })
​
    r.Run(":8080")
}

在这个程序启动后,当访问根路径/时,输出结果如下:

First Middleware - Before
Second Middleware - Before
Main Handler
Second Middleware - After
First Middleware - After

Gin中,中间件(Middleware)的角色更接近于Spring框架中的拦截器(Interceptor)

  1. 执行位置:中间件和拦截器都可以在请求处理流程的开始和结束时执行,允许在请求被处理之前和之后执行一些逻辑。
  2. 功能:它们都可以用于实现一些公共的预处理逻辑,如日志记录、权限验证、请求参数处理等,使得这些逻辑可以被多个请求共享和复用。
  3. 顺序:它们的执行顺序都可以通过配置来决定,可以按照添加的顺序执行

Gin控制器

在Gin中,没有严格的"控制器"概念,但我们可以将Gin中的路由处理函数类比为MVC模式中的控制器。路由处理函数负责接收HTTP请求,处理请求参数、业务逻辑,并生成相应的HTTP响应。

package main

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

func main() {
	r := gin.Default()

	// 定义路由,处理GET请求到根路径"/"
	r.GET("/", IndexHandler)

	// 定义路由,处理GET请求到路径"/hello"
	r.GET("/hello", HelloHandler)

	r.Run(":8080")
}

// IndexHandler 处理根路径"/"的路由
func IndexHandler(c *gin.Context) {
	c.String(200, "Hello, Gin!")
}

// HelloHandler 处理路径"/hello"的路由
func HelloHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "Hello, Gin!",
	})
}

在这个示例中,我们定义了两个路由处理函数 IndexHandlerHelloHandlerIndexHandler 处理根路径"/"的GET请求,并直接返回"Hello, Gin!"字符串作为响应。HelloHandler 处理"/hello"路径的GET请求,返回一个包含"message"字段的JSON响应。

Gin文件上传

单文件

func main() {
	router := gin.Default()
	// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
	router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单文件
		file, _ := c.FormFile("file")
		log.Println(file.Filename)

		dst := "./" + file.Filename
		// 上传文件至指定的完整文件路径
		c.SaveUploadedFile(file, dst)

		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	})
	router.Run(":8080")
}

多文件

func main() {
	router := gin.Default()
	// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
	router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", 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)
		}
		c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
	})
	router.Run(":8080")
}

Gin Model

在Gin中,通常将 Model 定义为普通的 Go 结构体(struct),用于表示数据库表的行、业务实体或其他数据结构。Model 包含了数据的字段,以及可能的一些方法用于操作或处理数据。Gin Model类似于Java中的实体类。

以下为一个示例:

package main

import "time"

// User 是一个简单的用户数据模型
type User struct {
    ID        uint      `json:"id"`
    Username  string    `json:"username"`
    Email     string    `json:"email"`
    Age       int       `json:"age"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

Model绑定

在 Gin 框架中,Model 绑定是一种方便的功能,用于将请求中的数据绑定到指定的结构体(Model)。Gin 提供了几个函数来实现 Model 绑定,包括 c.Bindc.ShouldBindc.ShouldBindWith。它们的主要区别在于处理绑定失败的方式以及对 Content-Type 的处理。

  1. c.Bind

    c.Bind 是最简单的 Model 绑定方法,它会根据请求的 Content-Type 来自动选择合适的绑定器(Binder),并将请求中的数据绑定到指定的结构体。如果绑定失败,将返回错误,并且应该由开发者进行错误处理。

    // 使用 c.Bind 绑定请求数据到指定结构体
    if err := c.Bind(&user); err != nil {
        // 处理绑定失败的情况
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
  2. c.ShouldBind

    c.ShouldBind 类似于 c.Bind,它也会根据请求的 Content-Type 来选择合适的绑定器。但是,不同之处在于 c.ShouldBind 会在绑定失败时返回错误,但同时也会继续处理请求。这意味着即使绑定失败,您仍然可以继续使用绑定之前的数据。

    // 使用 c.ShouldBind 绑定请求数据到指定结构体
    if err := c.ShouldBind(&user); err != nil {
        // 可以继续处理请求,即使绑定失败
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
    // 继续处理请求...
    
  3. c.ShouldBindWith

    c.ShouldBindWith 允许您使用自定义的绑定器来处理请求数据,而不是自动选择合适的绑定器。您可以在 c.ShouldBindWith 中指定一个绑定器(Binder),用于根据请求的 Content-Type 进行绑定。

    // 使用 c.ShouldBindWith 绑定请求数据到指定结构体,指定 JSON 绑定器
    if err := c.ShouldBindWith(&user, binding.JSON); err != nil {
        // 处理绑定失败的情况
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    

绑定器(Binder)是一个实现了 binding.Binder 接口的对象,Gin 默认提供了多个绑定器,包括 binding.JSONbinding.XMLbinding.Form 等,它们用于处理不同的 Content-Type。在绝大多数情况下,使用 c.Bindc.ShouldBind 即可满足大部分需求,Gin 会自动选择合适的绑定器。

Cookie & Session

入门

Cookie:

Cookie 是一种在客户端(通常是浏览器)存储少量数据的机制。它可以由服务器发送给客户端,并随着每个请求一起被发送回服务器。Cookie 通常用于:

  1. 会话管理:用于跟踪用户的会话状态,使得服务器可以识别每个不同的用户。
  2. 记住用户:可以将一些用户特定的信息保存在 Cookie 中,例如用户偏好设置、购物车内容等。

Cookie 存储在客户端的浏览器中,并可以设置过期时间,可以是会话级的(在浏览器关闭后失效)或持久的(设置了过期时间)。

在 Gin 中,您可以使用 c.SetCookie() 方法设置和发送 Cookie,以及使用 c.Cookie() 方法读取客户端发送回的 Cookie。

Session:

Session 是一种在服务器端存储用户状态和数据的机制。每个用户会话在服务器上有一个对应的数据存储,而客户端只需要保存一个唯一的 Session ID,通常通过 Cookie 发送给客户端。

Session 通常用于:

  1. 认证和用户登录:将用户的登录状态存储在 Session 中,允许用户在同一会话中保持登录状态。
  2. 用户状态跟踪:将用户在不同页面或请求之间的状态和数据存储在 Session 中,实现持久化跟踪。

在 Gin 中,可以使用第三方中间件(例如 github.com/gin-contrib/sessions)来实现 Session 管理。这些中间件可以处理 Session ID 的生成和管理,以及将 Session 数据存储在内存、数据库或其他存储介质中。

以下是一个简单的 Gin 示例代码,展示了如何使用 Cookie 和 Session:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
)

func main() {
	r := gin.Default()

	// 设置使用 Cookie 存储 Session 数据
	store := cookie.NewStore([]byte("secret"))
	r.Use(sessions.Sessions("mysession", store))

	// 设置路由,处理设置 Cookie 的请求
	r.GET("/setcookie", func(c *gin.Context) {
		c.SetCookie("username", "john", 3600, "/", "localhost", false, true)
		c.String(200, "Cookie set successfully!")
	})

	// 设置路由,处理读取 Cookie 的请求
	r.GET("/getcookie", func(c *gin.Context) {
		username, err := c.Cookie("username")
		if err != nil {
			c.String(400, "Cookie not found")
			return
		}
		c.String(200, "Username: "+username)
	})

	// 设置路由,处理设置 Session 的请求
	r.GET("/setsession", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Set("user_id", 123)
		session.Save()
		c.String(200, "Session set successfully!")
	})

	// 设置路由,处理读取 Session 的请求
	r.GET("/getsession", func(c *gin.Context) {
		session := sessions.Default(c)
		userID := session.Get("user_id")
		c.String(200, "User ID: %v", userID)
	})

	r.Run(":8080")
}

在上述示例中,使用了第三方中间件 github.com/gin-contrib/sessions 来实现 Session 管理。通过 cookie.NewStore([]byte("secret")) 创建了一个基于 Cookie 的 Session 存储。

其中定义了四个路由来演示设置和读取 Cookie 和 Session 的过程。/setcookie 路由设置了一个名为 "username" 的 Cookie,/getcookie 路由读取了 "username" Cookie 的值。/setsession 路由设置了一个名为 "user_id" 的 Session 数据,/getsession 路由读取了 "user" 的 Session 数据。

Api详解

  1. c.SetCookie():设置一个 HTTP Cookie 并将其发送给客户端。该方法的签名如下:

    func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
    
    • name:Cookie 名称。
    • value:Cookie 值。
    • maxAge:Cookie 的过期时间,以秒为单位。设置为负值表示在浏览器关闭时过期,设置为 0 表示删除 Cookie,设置为正值表示指定秒数后过期。
    • path:Cookie 适用的路径。
    • domain:Cookie 适用的域名。
    • secure:是否只在 HTTPS 连接中传输 Cookie。
    • httpOnly:是否只允许 HTTP 协议访问 Cookie,禁止 JavaScript 访问。
  2. c.Cookie():从客户端请求中获取指定名称的 Cookie。该方法的签名如下:

    func (c *Context) Cookie(name string) (string, error)
    
    • name:要获取的 Cookie 名称。

    如果指定名称的 Cookie 存在于客户端请求中,该方法返回 Cookie 的值;否则,返回错误信息。

  3. cookie.NewStore():创建一个新的 Cookie 存储。该方法的签名如下:

    func NewStore(keyPairs ...[]byte) Store
    
    • keyPairs:用于加密 Session 数据的密钥对,可以指定多个,用于对 Session 数据进行加密。
  4. sessions.Sessions():设置当前 Gin 引擎使用的 Session 中间件。该方法的签名如下:

    func Sessions(name string, store Store) gin.HandlerFunc
    
    • name:Session 名称,用于在 Cookie 中存储 Session ID。

    • store:Session 存储,指定了 Session 数据的存储方式。

    3,4结合可以理解为手动存储一个SessionId的Cookie,用于以后读取Session

    store可以设置的更为复杂,例如

    const secret = "mycomplexrandomsecretkey_@#(*$"
    store := cookie.NewStore([]byte(secret))
    
  5. sessions.Default():获取当前请求的 Session 对象。该方法的签名如下:

    func Default(c *gin.Context) Session
    
    • c:当前 Gin 请求上下文。

    通过该方法,可以获取当前请求的 Session 对象,用于存取和管理 Session 数据。

  6. session.Set():设置 Session 数据的键值对。该方法的签名如下:

    func (session Session) Set(key string, value interface{})
    
    • key:Session 数据的键。
    • value:Session 数据的值。
  7. session.Get():获取 Session 数据的值。该方法的签名如下:

    func (session Session) Get(key string) interface{}
    
    • key:Session 数据的键。

    通过该方法,可以获取指定键对应的 Session 数据值。