四、组成
前面的两篇文章中,我们介绍了其中一部分组成,接下来再继续学习:
-
Router(路由器)
- Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。
- 路由器负责将请求路径映射到相应的处理函数。
-
Context(上下文)
gin.Context
是 Gin 中最重要的结构之一,它在请求生命周期内传递信息。- Context 提供了对请求和响应对象的访问,以及用于存储数据、设置状态码、返回 JSON 等方法。
-
Middleware(中间件)
- 中间件是可以在请求被最终处理之前或之后执行的一段代码,用于实现日志记录、错误恢复、认证等功能。
- Gin 支持全局中间件和特定路由组或单个路由使用的中间件。
-
Handlers(处理函数)
- 处理函数是实际执行业务逻辑的位置,每个路由都会关联一个或多个处理函数。
- 这些函数接收
gin.Context
参数,通过它们可以获取请求数据并生成响应。
-
Error Handling(错误处理)
- Gin 提供了一种机制来捕获和管理应用程序中的错误,可以通过 Context 的方法进行错误报告和恢复操作。
-
Rendering and Responses(渲染与响应)
- 支持多种格式的数据输出,包括 JSON、XML 和 HTML 渲染等,方便客户端消费不同类型的数据格式。
-
Binding and Validation(绑定与验证)
- 自动将 HTTP 请求中的数据绑定到结构体,并支持对输入数据进行验证,以确保其符合预期格式和规则。
-
Templates (模板)
- 虽然不是框架核心,但 Gin 支持集成 HTML 模板引擎,用于生成动态网页内容。
4.6 Rendering and Responses(渲染与响应)
在 Gin 框架中,渲染与响应是处理 HTTP 请求的核心功能之一。Gin 提供了多种方式来生成和发送响应给客户端,包括 JSON、XML、HTML 等格式。
4.6.1 JSON 响应
Gin 提供了简单的方法来返回 JSON 格式的数据,这是 RESTful API 开发中最常用的格式。
go
c.JSON(http.StatusOK, gin.H{
"message": "测试json格式",
"status": "success",
})
gin.H
是一个快捷方式,用于创建map[string]interface{}
类型的数据结构。
4.6.2 XML 响应
类似于 JSON,Gin 也支持返回 XML 格式的数据。
go
// 使用 c.XML() 方法可以将数据序列化为 XML 格式并发送给客户端。
c.XML(http.StatusOK, gin.H{
"message": "测试XML格式",
"status": "success",
})
4.6.3 HTML 渲染
Gin 支持使用模板引擎渲染 HTML 页面。首先需要加载模板文件,然后在处理函数中进行渲染
LoadHTMLGlob()
用于加载模板文件,可以使用通配符指定路径。- 在处理函数中,通过调用
c.HTML()
来渲染指定的模板,并传递数据用于填充模板变量。 - 代码案例在gin的第一篇文章有提到,自行查阅
4.6.4 文件响应
可以直接将文件作为响应发送给客户端,例如下载或显示图片等
go
// 使用 c.File() 方法可以直接将服务器上的文件内容发送到客户端。
c.File("./temp_pic.png")
4.6.5 数据流(Streaming)
对于需要逐步生成或传输大块数据的场景,可以使用流模式
go
func Test2(t *testing.T) {
r := gin.Default()
r.GET("test", func(c *gin.Context) {
// 模拟数据
data := []byte("test steaming")
// 创建一个新的Reader对象,它可以用来从data中读取数据
reader := bytes.NewReader(data)
// 计算data的长度,并将其转换为int64类型,这个值将用于HTTP响应的Content-Length头。
contentLength := int64(len(data))
// 使用http.DetectContentType函数自动检测data的MIME类型,这个值将用于HTTP响应的Content-Type头。
contentType := http.DetectContentType(data)
// 创建一个名为extraHeaders的map,其中 attachment; filename="example.txt" 告诉浏览器应该将响应作为文件下载,文件名为example.txt
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="example.txt"`,
}
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
r.Run()
}
使用 DataFromReader()
可以从一个实现了 io.Reader 接口的数据源读取并输出到 HTTP 响应体中。
http.StatusOK
:HTTP状态码,表示请求成功。contentLength
:响应内容的长度。contentType
:响应内容的MIME类型。reader
:用于读取响应内容的Reader
对象。extraHeaders
:额外的HTTP头。
4.6.6 重定向(Redirect)
可以通过重定向让客户端访问另一个 URL
go
func Test3(t *testing.T) {
r := gin.Default()
r.GET("test1", func(c *gin.Context) {
// 调用 Redirect() 方法设置重定向状态码和目标 URL,让浏览器自动跳转到新的地址。
c.Redirect(http.StatusMovedPermanently, "test2")
// 推荐用http自带的code
// StatusMultipleChoices = 300 // RFC 9110, 15.4.1
// StatusMovedPermanently = 301 // RFC 9110, 15.4.2
// StatusFound = 302 // RFC 9110, 15.4.3
// StatusSeeOther = 303 // RFC 9110, 15.4.4
// StatusNotModified = 304 // RFC 9110, 15.4.5
// StatusUseProxy = 305 // RFC 9110, 15.4.6
// _ = 306 // RFC 9110, 15.4.7 (Unused)
// StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
// StatusPermanentRedirect = 308 // RFC 9110, 15.4.9
})
r.GET("test2", func(c *gin.Context) {
fmt.Println("跳转到了test2")
})
r.Run()
}
4.7 Binding and Validation(绑定与验证)
在 Gin 框架 中,Binding 和 Validation(绑定与验证)是处理客户端请求数据时的核心功能。Gin 提供了 数据绑定(将请求参数绑定到 Go 结构体)和 数据验证(校验数据是否符合预设规则)机制,简化了开发流程。
- 自动数据绑定:支持 JSON、表单、查询参数、URI 参数等多种来源。
- 结构化验证 :使用
binding
标签定义字段的验证规则。 - 内置规则 :提供丰富的验证规则,如
required
、email
、min
、max
等。 - 自定义验证:支持注册自定义验证器,满足个性化需求。
4.7.1 Binding(数据绑定)
数据绑定的主要作用是将 请求参数(如 JSON、XML、表单数据、查询字符串等)自动绑定到结构体中。
Gin 提供了几种常见的绑定方法
方法 | 数据来源 |
---|---|
c.ShouldBindJSON |
请求体中的 JSON 数据 |
c.ShouldBindQuery |
URL 查询参数(Query String) |
c.ShouldBindForm |
表单数据 |
c.ShouldBindHeader |
HTTP 请求头 |
c.ShouldBindUri |
URL 路径参数(动态路由参数) |
c.ShouldBindXML |
请求体中的 XML 数据 |
4.7.1.1 JSON 数据绑定
go
// 定义一个结构体,接收 JSON 数据
type LoginInfo struct {
Username string `json:"username" binding:"required"` // 使用 "binding" 标签进行验证,声明字段为必填项
Password string `json:"password" binding:"required"`
Email string `json:"email"`
}
func Test4(t *testing.T) {
r := gin.Default()
// 严格来说应该用r.POST请求,这里只是测试,无所谓了
r.GET("test", func(c *gin.Context) {
var loginInfo LoginInfo
// 绑定 JSON 数据到结构体
err := c.ShouldBindJSON(&loginInfo)
fmt.Printf("接受到请求参数:%v", loginInfo)
if err != nil {
// 验证失败,返回错误
c.JSON(http.StatusBadRequest, gin.H{
"msg": "请求参数不对",
"error": err.Error(),
})
return
}
// 验证通过,正常响应
c.JSON(200, gin.H{
"msg": "登录成功",
"user_name": loginInfo.Username,
})
})
r.Run()
}
输出
json
{
"msg": "登录成功",
"user_name": "小明"
}
没有password时
json
{
"error": "Key: 'LoginInfo.Password' Error:Field validation for 'Password' failed on the 'required' tag",
"msg": "请求参数不对"
}
4.7.1.2 Query 参数绑定
通过 URL 查询参数进行绑定,跟上面的例子差别就只是结构体和bind方法
go
type QueryParam struct {
Page string `form:"page" binding:"required"`
Size string `form:"size"`
}
func Test5(t *testing.T) {
r := gin.Default()
r.GET("test", func(c *gin.Context) {
var query QueryParam
err := c.ShouldBindQuery(&query)
fmt.Printf("接受到请求参数:%v", query)
if err != nil {
// 验证失败,返回错误
c.JSON(http.StatusBadRequest, gin.H{
"msg": "请求参数不对",
"error": err.Error(),
})
return
}
// 验证通过,正常响应
c.JSON(200, gin.H{
"msg": "查询成功",
})
})
r.Run()
}
4.7.1.3 URI 参数绑定
当使用动态路由时,可以绑定路径参数到结构体中
go
type UriPrams struct {
UserName string `uri:"username" binding:"required"`
UserId string `uri:"user_id"` // 这种情况下binding:"required"有点多余,因为有些情况下(比如这个参数在最后)如果用户不传这个参数,则可能不能正常访问到这个路由
}
func Test6(t *testing.T) {
r := gin.Default()
r.GET("test/:username/:user_id", func(c *gin.Context) {
var uriPrams UriPrams
err := c.ShouldBindUri(&uriPrams)
fmt.Printf("接受到请求参数:%v", uriPrams)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "请求参数不对",
"error": err.Error(),
})
return
}
c.JSON(200, gin.H{
"msg": "请求成功",
})
})
r.Run()
}
4.7.2 Validation(数据验证)
Gin 使用 go-playground/validator
库作为验证器,通过结构体标签(binding
标签)定义验证规则。
常见验证规则:
验证标签 | 含义 |
---|---|
required |
必填字段 |
min=X |
最小值(数字)/ 最小长度(字符串) |
max=X |
最大值(数字)/ 最大长度(字符串) |
email |
必须是合法的邮箱格式 |
len=X |
长度必须等于 X |
gte=X / lte=X |
大于等于 / 小于等于 |
oneof=X Y Z |
值必须是 X、Y、Z 中的一个 |
4.7.2.1 验证规则
go
type Register struct {
Username string `json:"username" binding:"required,min=1,max=15"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
Phone string `json:"phone" binding:"min=11,max=11"` // 如果是string类型,有了这个min和max,默认就是必填的了
Age int `json:"age" binding:"min=0,max=90"` // 但是如果是int类型,就没有这个限制
Age2 int `json:"age2" binding:"gte=18"` // 也是默认必填
Gender string `json:"gender" binding:"oneof=male female"` // 也是默认必填
}
func Test7(t *testing.T) {
r := gin.Default()
r.POST("register", func(c *gin.Context) {
var register Register
err := c.ShouldBindJSON(®ister)
fmt.Println(register)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "注册失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": fmt.Sprintf("恭喜,注册成功:%s", register.Username),
})
})
r.Run()
}
4.7.2.2 自定义验证器
你也可以注册自定义验证器来满足特殊需求,比如验证密码设置的规则:密码长度为8-16位,必须包含大小写英文字母和数字
1、自定义验证规则需要实现gin.Validator
接口
2、在Gin的全局配置中注册这个自定义验证器
go
type Register2 struct {
Username string `json:"username" binding:"required,min=1,max=15"`
Password string `json:"password" binding:"required,password"`
}
// 自定义验证器
var v = func(field validator.FieldLevel) bool {
// 密码长度为8-16位,必须包含大小写英文字母和数字
str := field.Field().String()
if len(str) > 16 || len(str) < 8 {
return false
}
var hasLowerCase bool
var hasUpperCase bool
var hasNumber bool
for _, s := range str {
if unicode.IsLower(s) {
hasLowerCase = true
continue
}
if unicode.IsUpper(s) {
hasUpperCase = true
continue
}
if unicode.IsDigit(s) {
hasNumber = true
}
}
return hasLowerCase && hasUpperCase && hasNumber
}
func Test8(t *testing.T) {
r := gin.Default()
// 注册自定义验证器
engine := binding.Validator.Engine().(*validator.Validate)
err := engine.RegisterValidation("password", v)
if err != nil {
return
}
r.POST("register", func(c *gin.Context) {
var register Register2
err := c.ShouldBindJSON(®ister)
fmt.Println(register)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "注册失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": fmt.Sprintf("恭喜,注册成功:%s", register.Username),
})
})
r.Run()
}
4.8 Templates(模板)
Gin 框架提供了基本的模板渲染功能,允许开发者在服务器端生成 HTML 页面。Gin 的模板功能是基于 Go 语言的标准库 html/template
实现的。
-
模板文件 :通常是
.tmpl
或.html
文件,包含 HTML 和嵌入其中的动态内容占位符。 -
加载模板:使用 Gin 提供的方法将这些文件加载到应用程序中。
-
渲染模板:在处理请求时,将数据传递给已加载的模板进行渲染,并将结果发送给客户端。
4.8.1 创建模板文件
首先,在项目目录下创建一个目录来存放你的 HTML 模板文件,例如 templates/
。
模板文件通常以 .tmpl
或 .html
为后缀,包含静态 HTML 和动态内容占位符。动态内容通过双大括号 {``{ }}
包裹。在这个示例中,.Title
和 .Name
是数据占位符,它们将在渲染时被实际的数据替换。
html
<!-- templates/index.tmpl -->
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
</head>
<body>
<h1>Hello, {{ .Name }}!</h1>
</body>
</html>
4.8.2 加载和渲染模板
1、使用 LoadHTMLGlob()
或 LoadHTMLFiles()
方法来加载这些模板。
2、在处理请求时,使用上下文对象中的 c.HTML()
方法来渲染并返回 HTML 响应。
- HTTP 状态码(如
http.StatusOK
) - 模板名称(即你要渲染的具体模板)
- 数据对象(通常为一个 map 或结构体),用于填充模板中的动态内容。
go
func Test9(t *testing.T) {
r := gin.Default()
// 加载 templates 目录下所有以 .tmpl 为后缀名的文件
r.LoadHTMLGlob("templates/*")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"Title": "Home",
"Name": "World",
})
})
r.Run()
}