Gin 框架的路由机制
1. 获取 querystring 参数
解析
- querystring 指的是 URL 中?后面携带的参数,
- 例如:/collage/search?title=工业&location=杭州。
- 获取请求的 querystring 参数的方法如下:
func main() {
func testQuery1(context *gin.Context) {
query1 := context.Query("avatar")//直接,最常用的获取query参数
query2 := context.Query("title")
query3 := context.DefaultQuery("age", "未知") //具有默认值的获取(当age没传来的时候,默认为未知)
query4, ok := context.GetQuery("location") //具有bool判断是否传来的query获取
if !ok {
//代表没有传值过来
query4 = "中国"
}
fmt.Println(query1)
fmt.Println(query2)
fmt.Println(query3)
fmt.Println(query4)
context.JSON(http.StatusOK, gin.H{"作者": query1, "作品": query2, "年龄": query3, "出生地": query4})
}
server.GET("/search1", testQuery1) //测试获取querystring数据
}
2. 获取 form 参数
详细解析
- 当前端请求的数据通过 form 表单提交时,
- 例如向/user/search 发送一个 POST 请求,
- 获取请求数据的方式如下:
func main() {
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
username := c.PostForm("username")//最基础的获取form参数形式
address := c.PostForm("address")
password := c.DefaultPostForm("password", "root")// DefaultPostForm取不到值时会返回指定的默认值
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
3. 获取原生 json
详细解析
- 当前端请求的数据通过 JSON 提交时,
- 例如向/json 发送一个 JSON 格式的 POST 请求,
- 但实际项目中更优先使用参数绑定来解决(除非该数据根本不需要设立 struct)
func main() {
r := gin.Default()
r.POST("/json", func(c *gin.Context) {
// 注意:下面为了举例子方便,暂时忽略了错误处理
b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
// 定义map或结构体
var m map[string]interface{}
// 反序列化
_ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m)
})
r.Run(":8080")
}
4. 获取 path 参数
详细解析
- 请求的参数通过 URL 路径传递(就是严格意义的 RESTful 风格的形式),
- 例如:/user/search/小王子/沙河。
- 获取请求 URL 路径中的参数的方式如下。
r.GET("/user/search/:username/:address", func(c *gin.Context) {
username := c.Param("username")
address := c.Param("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
5. 参数绑定 🚀
详细解析
- 首先,它是实际项目中最常见,最高效的方式
- 为了能够更方便的获取请求相关参数,提高开发效率,
- Gin 框架基于请求的 Content-Type 识别请求数据类型并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等参数到结构体中。
- 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象。
// Binding from JSON
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
router.POST("/loginJSON", func(c *gin.Context) {
var login Login
if err := c.ShouldBind(&login); err == nil {
fmt.Printf("login info:%#v\n", login)
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// 绑定form表单示例 (user=q1mi&password=123456)
router.POST("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := c.ShouldBind(&login); err == nil {
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
router.GET("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的Content-Type自行选择绑定器
if err := c.ShouldBind(&login); err == nil {
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
router.Run(":10086")
}
注意 注意
关于字段绑定要说的
ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
如果是 GET 请求,只使用 Form 绑定引擎(query)。
如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)。
6. 文件上传
6.1 单文件上传
小例子的形式详细讲说一下
分别是前端原生 html 的代码和后端 Gin 的代码
前端的原生上传
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1" />
<input type="submit" value="上传" />
</form>
</body>
</html>
后端 Gin 接收文件
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单个文件
file, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
log.Println(file.Filename)
dst := "./uploads/"+ file.Filename//创建文件存储路径
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
})
})
router.Run()
}
6.2 多文件上传
7. 重定向
7.1 HTTP 重定向
解析
HTTP 重定向很容易。 内部、外部重定向均支持。
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.map.baidu.com/")
})
7.2 路由重定向(分发)
路由重定向,使用 HandleContext:
r.GET("/test", func(c *gin.Context) {
// 指定重定向的URL
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
8. 路由组和其他路由
8.1 any 路由
信息
字面意思,可接受所有形式,逻辑里面判断是 get 还是 post 来进行后续的处理
实际开发并不常用
r.Any("/test", func(c *gin.Context) {...})
8.2 404 路由
信息
为没有配置处理函数的路由添加处理程序,默认情况下它返回 404 代码,下面的代码为没有匹配到路由的请求都返回 views/404.html 页面。
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})
8.3 一般路由
信息
就是之前写过的最经典的路由形式
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
8.4 路由组
信息
- 我们可以将拥有共同 URL 前缀的路由划分为一个路由组。
- 习惯性一对{}包裹同组的路由,这只是为了看着清晰,
- 你用不用{}包裹功能上没什么区别。
常规路由组
func main() {
r := gin.Default()
userGroup := r.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
r.Run()
}
路由组再嵌套
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
// 嵌套路由组
xx := shopGroup.Group("xx")
xx.GET("/oo", func(c *gin.Context) {...})
}