回顾
上一次,我们介绍了Gin框架的基本使用,包括路由的使用,html模板的渲染等。
静态文件服务
当我们渲染的 HTML 文件中引用了静态文件时,我们需要配置静态 web 服务。
r.Static("/static", "./static")
, 前面的 /static
表示路由;后面的./static
表示路径。
main.go
//配置静态资源, 浏览器访问链接为 http://localhost:8080/static/css/base.css
r.Static("/static", "./static")
静态文件文件css引入:
<link rel="stylesheet" href="/static/css/base.css">
路由详解
路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问。
前面章节我们给大家介绍了路由基础以及路由配置,这里我们详细给大家讲讲路由传值、路由返回值。
GET POST以及获取GET POST传值
GET 请求传值
// GET /?name=john&age=20&sex=male&address=shanghai
r.GET("/", func(c *gin.Context) {
name := c.Query("name")
age := c.Query("age")
sex := c.Query("sex")
address := c.DefaultQuery("address", "shanghai")
c.JSON(200, gin.H{
"name": name,
"age": age,
"sex": sex,
"address": address,
})
})
动态路由传值
// xxxxxxxx/hello/ypb
r.GET("/hello/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(200, "hello %s", name)
})
POST请求传值 获取表单数据
首先我们定义一个 html 页面。
{{define "templates/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/doAddUser1" method="post">
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<!-- 年龄:<input type="text" name="age"> <br>-->
<input type="submit" value="login">
</form>
<br>
</body>
</html>
{{end}}
通过 c.PostForm
接受表单传递来的数据。
r.POST("/doAddUser1", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
age := c.DefaultPostForm("age", "18")
c.JSON(200, gin.H{
"username": username,
"password": password,
"age": age,
})
})
获取 GET传递的数据绑定到结构体
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的 Content-Type 识别请求数据类型并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等参数到结构体中。
示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象。
main.go
// 定义UserInfo2结构体
type UserInfo1 struct {
Name string `form:"name" json:"name" binding:"required"`
Age int `form:"age" json:"age" binding:"required"`
}
// 将数据绑定到UserInfo结构体
// http://localhost:8080/getUser?name=ypb&age=123456
r.GET("/getUser", func(c *gin.Context) {
user := &UserInfo1{}
if err := c.ShouldBind(&user); err != nil {
c.JSON(200, gin.H{
"error": err.Error(),
})
} else {
fmt.Printf("user: %+v\n", user)
c.JSON(200, user)
}
})
返回数据
{"name":"ypb","age":123456}
获取 POST 传递的数据绑定到结构体
模板代码
{{define "templates/index.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/doAddUser2" method="post">
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<!-- 年龄:<input type="text" name="age"> <br>-->
<input type="submit" value="login">
</form>
</body>
</html>
{{end}}
main.go
// 获取模板页面
r.GET("/login", func(c *gin.Context) {
c.HTML(200, "templates/index.html", gin.H{})
})
// 绑定
r.POST("/doAddUser2", func(c *gin.Context) {
user := &UserInfo2{}
if err := c.ShouldBind(&user); err != nil {
c.JSON(200, gin.H{
"error": err.Error(),
})
} else {
fmt.Printf("user: %+v\n", user)
c.JSON(200, user)
}
})
返回数据
{"username":"yxc","password":"123456"}
获取POSTXML数据
在 API 的开发中,我们经常会用到 JSON 或 XML 来作为数据交互的格式,这个时候我们 可以在 gin 中使用 c.GetRawData()
获取数据。
定义数据结构体
type Article struct {
Title string `xml:"title" binding:"required"`
Content string `xml:"content" binding:"required"`
}
// 获取POST xml请求的数据
r.POST("/getUserXml", func(c *gin.Context) {
article := &Article{}
xmlSliceData, _ := c.GetRawData()
if err := xml.Unmarshal(xmlSliceData, &article); err == nil {
c.JSON(200, article)
} else {
c.JSON(400, gin.H{"err": err.Error()})
}
})
XML 请求:
<?xml version="1.0" encoding="UTF-8"?> <article>
<content type="string">我是张三</content>
<title type="string">张三</title> </article>
返回数据:
{
"Title": "张三",
"Content": "我是张三"
}
路由分组
路由分组就是将具有相似功能或路径的路由集合在一起,形成一个路由组。
func main() {
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1") {
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// 简单的路由组: v2
v2 := router.Group("/v2") {
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
路由文件分组
核心思想是将相关的路由定义放在同一个文件或文件夹中,以便于维护和管理。
新建 router文件夹,在此文件夹下面新建adminRoutes.go, apiRoutes.go 和 defaultRoutes.go。
adminRoutes.go
// AdminRoutersInit 管理后台路由
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin")
{
adminRouters.GET("/", func(c *gin.Context) {
c.String(200, "后台首页")
})
adminRouters.GET("/user", func(c *gin.Context) {
c.String(200, "用户列表")
})
adminRouters.GET("/article", func(c *gin.Context) {
c.String(200, "新闻列表")
})
}
}
apiRoutes.go
// ApiRoutersInit 管理api路由
func ApiRoutersInit(r *gin.Engine) {
apiRouters := r.Group("/api")
{
apiRouters.GET("/", func(c *gin.Context) {
c.String(200, "api首页")
})
apiRouters.GET("/userlist", func(c *gin.Context) {
c.String(200, "一个userlist接口")
})
}
}
defaultRoutes.go
// DefaultRoutesInit 管理默认路由
func DefaultRoutersInit(r *gin.Engine) {
defaultRouters := r.Group("/")
{
defaultRouters.GET("/", func(c *gin.Context) {
c.String(200, "首页")
})
defaultRouters.GET("/news", func(c *gin.Context) {
c.String(200, "新闻")
})
}
}
配置 main.go
func main() {
//初始化路由
r := gin.Default()
// 注册路由
router.AdminRoutersInit(r)
router.ApiRoutersInit(r)
router.DefaultRoutersInit(r)
// 运行服务器
r.Run(":8080")
}
自定义控制器
在 Gin 框架中,控制器主要负责处理 HTTP 请求并返回响应,它是 MVC(Model-View-Controller)架构中的一部分。自定义控制器,顾名思义,就是由开发者根据业务需求自行创建和定义的控制器。
在 Gin 中,一个控制器通常是一个函数,它接收一个 *gin.Context
类型的参数,这个参数包含了当前 HTTP 请求的所有信息,如请求头、请求体、URL参数等。控制器可以通过这个参数来获取请求信息,进行业务逻辑处理,然后返回响应。
新建 controller/admin/ArticleController.go
package admin
import "github.com/gin-gonic/gin"
type ArticleController struct{}
func (a ArticleController) GetArticle(c *gin.Context) {
c.JSON(200, "获取文章")
}
func (a *ArticleController) AddArticle(c *gin.Context) {
c.JSON(200, "添加文章")
}
func (a *ArticleController) EditArticle(c *gin.Context) {
c.JSON(200, "编辑文章")
}
新建 controller/admin/IndexController.go
package admin
import "github.com/gin-gonic/gin"
type IndexController struct {
}
func (con IndexController) Index(c *gin.Context) {
c.String(200, "后台首页")
}
配置对应的路由
package router
import (
"ginpro/gincontroller/controller/admin"
"github.com/gin-gonic/gin"
)
// AdminRoutersInit 管理后台路由
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin")
{
adminRouters.GET("/", admin.IndexController{}.Index)
adminRouters.GET("/article", admin.ArticleController{}.GetArticle)
}
}
控制器的继承
新建 controller/admin/BaseController
package admin
import (
"github.com/gin-gonic/gin"
"net/http"
)
type BaseController struct{}
func (con BaseController) success(c *gin.Context) {
c.String(http.StatusOK, "成功")
}
func (con BaseController) error(c *gin.Context) {
c.String(http.StatusOK, "失败")
}
新建 controller/admin/UserController.go
,UserController
继承 BaseController
,继承后就可以调用控制器里面的公共方法了。
package admin
import "github.com/gin-gonic/gin"
type UserController struct {
BaseController
}
func (con UserController) GetUserList(c *gin.Context) {
//c.String(200, "用户列表1")
con.success(c)
}
func (con UserController) AddUser(c *gin.Context) {
c.String(200, "用户添加2")
}
func (con UserController) EditUser(c *gin.Context) {
c.String(200, "用户编辑3")
}
Gin中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函 数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等。
通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作。
路由中间件
初始中间件
Gin 中的中间件必须是一个 gin.HandlerFunc
类型,配置路由的时候可以传递多个 func
回调函数,最后一个 func
回调函数前面触发的方法都可以称为中间件。
定义中间件:
func InitMiddleWareOne(c *gin.Context) {
fmt.Println("hello world1")
c.Next()
fmt.Println("hello world2")
}
在路由中使用中间件:
r.GET("/hello2", InitMiddleWareOne, func(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "hello world2",
})
})
ctx.Next()
ctx.Next()
是一个非常重要的方法,它在中间件中使用,用于调用后续的处理函数。
在 Gin 中,一个请求的处理流程是这样的:当一个请求到来时, Gin 会按照定义的顺序调用所有的中间件和处理函数。在每个中间件中,你可以选择在处理函数执行前后做一些操作,比如记录日志、处理错误、检查权限等。
这时候,ctx.Next()
就派上用场了。当你在一个中间件中调用 ctx.Next()
,Gin 会继续执行后续的中间件和处理函数。如果你没有调用 ctx.Next()
,那么后续的中间件和处理函数就不会被执行。
定义包含 ctx.Next()
的中间件
func TestCtxNext(c *gin.Context) {
fmt.Println("1-执行中间件")
start := time.Now().UnixNano()
// 执行下一个中间件
c.Next()
fmt.Println("3-程序执行完成,计算时间")
// 计算时间
end := time.Now().UnixNano()
fmt.Printf("middle time: %d\n", end-start)
}
在路由中使用中间件
r.GET("/", TestCtxNext, func(c *gin.Context) {
fmt.Println("2-执行路由")
time.Sleep(time.Second)
c.String(200, "hello world")
})
访问路由:http://localhost:8080/
控制台输出结果
1-执行中间件
2-执行路由
3-程序执行完成,计算时间
middle time: 1001261000
多个中间件执行顺序
接着我们重新定义两个中间件:
func InitMiddleWareOne(c *gin.Context) {
fmt.Println("hello world1")
c.Next()
fmt.Println("hello world2")
}
func InitMiddleWareTwo(c *gin.Context) {
fmt.Println("hello world3")
c.Next()
fmt.Println("hello world4")
}
在路由中使用中间件
r.GET("/hello2", InitMiddleWareOne, InitMiddleWareTwo, func(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "hello world2",
})
})
访问路由:http://localhost:8080/hello2
控制台输出结果
hello world1
hello world3
hello world4
hello world2
ctx.Abort()
在 Gin 框架中,ctx.Abort()
是一个用于终止当前请求处理流程的方法。一旦调用了该方法,那么后续的中间件和处理函数都不会被执行。
ctx.Abort()
通常在中间件中使用,特别是在一些需要检查请求有效性或权限的中间件中。例如,如果有一个身份验证的中间件,在用户身份验证失败时调用 ctx.Abort()
,立即终止请求处理,返回一个错误响应。
编写中间件
func InitMiddleWare(c *gin.Context) {
fmt.Println("hello world1")
start := time.Now().UnixNano()
c.String(200, "hello world")
c.Abort()
fmt.Println("hello world2")
end := time.Now().UnixNano()
fmt.Printf("middle time: %d\n", end-start)
}
在路由中使用中间件
r.GET("/hello", InitMiddleWare, func(c *gin.Context) {
fmt.Println("hello world3")
c.JSON(200, gin.H{
"msg": "hello world",
})
})
访问路由:http://localhost:8080/hello
控制台输出
hello world1
hello world2
middle time: 52000
全局中间件
在 Gin 框架中,全局中间件是一种应用于所有路由的中间件。我们可以使用 router.Use()
方法来添加全局中间件。
// 全局中间件
// 使用每一个路由都会调用InitMiddleWareOne, InitMiddleWareTwo中间件
r.Use(InitMiddleWareOne, InitMiddleWareTwo)
在路由分组中使用中间件
为路由组注册中间件有两种写法
// 写法一
// AdminRoutersInit 管理后台路由
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin", middleware.InitMiddleWareOne, middleware.InitMiddleWareTwo)
{
...
}
}
// 写法二
// AdminRoutersInit 管理后台路由
func AdminRoutersInit(r *gin.Engine) {
adminRouters := r.Group("/admin")
adminRouters.Use(middleware.InitMiddleWareOne)
{
adminRouters.GET("/index", func(c *gin.Context) {...}}
...
}
}
中间件和对应控制器之间共享数据
设置值
// MiddleWareSetValue 设置值
func MiddleWareSetValue(c *gin.Context) {
// 可以通过 ctx.Set 在请求上下文中设置值,后续的处理函数能够取到该值
c.Set("username", "ypb")
c.String(200, "中间件设置值")
}
获取值
type Controller struct{}
// ControllerGetValue 获取值
func (c Controller) ControllerGetValue(ctx *gin.Context) {
username, _ := ctx.Get("username")
fmt.Println(username)
ctx.String(200, "获取用户名成功")
}
路由请求 http://localhost:8080/getvalue
r.GET("/getvalue", MiddleWareSetValue, Controller{}.ControllerGetValue)
控制台输出
ypb
注意事项
Gin 中的默认中间件
gin.Default()
默认使用了 Logger
和 Recovery
中间件。
其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover
任何panic
。如果有panic
的话,会写入500
响应码。
如果不想使用上面两个默认的中间件,可以使用 gin.New()
新建一个没有任何默认中间件的 路由。
总结
本次,我们讲解了如何在 Gin 框架中使用静态文件,控制器,路由分组以及中间件相关等。