forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
9.6 KiB
266 lines
9.6 KiB
4 years ago
|
# Gin(二):路由Router
|
||
|
|
||
|
经过[上一章节](https://youngxhui.top/2019/07/gin一hello/)的介绍,搭建一个简单的 `Gin` `web` 项目非常容易,同时也引入了一些新的概念,比如说:**路由** `Router`。
|
||
|
|
||
|
**路由**是一个非常重要的概念,所有的接口都要有路由来进行管理。
|
||
|
|
||
|
|
||
|
|
||
|
# 请求方法
|
||
|
|
||
|
`Gin` 的路由支持 `GET` , `POST` , `PUT` , `DELETE` , `PATCH` , `HEAD` , `OPTIONS` 请求,同时还有一个 `Any` 函数,可以同时支持以上的所有请求。
|
||
|
|
||
|
将上一章节的代码添加其他请求方式的路由,并编写单元测试。
|
||
|
|
||
|
```
|
||
|
// 省略其他代码
|
||
|
// 添加 Get 请求路由
|
||
|
router.GET("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin get method")
|
||
|
})
|
||
|
// 添加 Post 请求路由
|
||
|
router.POST("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin post method")
|
||
|
})
|
||
|
// 添加 Put 请求路由
|
||
|
router.PUT("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin put method")
|
||
|
})
|
||
|
// 添加 Delete 请求路由
|
||
|
router.DELETE("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin delete method")
|
||
|
})
|
||
|
// 添加 Patch 请求路由
|
||
|
router.PATCH("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin patch method")
|
||
|
})
|
||
|
// 添加 Head 请求路由
|
||
|
router.HEAD("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin head method")
|
||
|
})
|
||
|
// 添加 Options 请求路由
|
||
|
router.OPTIONS("/", func(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin options method")
|
||
|
})
|
||
|
// 省略其他代码
|
||
|
```
|
||
|
|
||
|
单元测试,只展示一个 `Post` 请求函数,基本与 `Get` 请求一致,其他代码详见文末 Github 地址。
|
||
|
|
||
|
```
|
||
|
// router("/") post 测试
|
||
|
func TestIndexPostRouter(t *testing.T) {
|
||
|
router := initRouter.SetupRouter()
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodPost, "/", nil)
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
assert.Equal(t, "hello gin post method", w.Body.String())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
此时运行单元测试,所有测试完美通过。
|
||
|
|
||
|
但是也有一个问题,所有的请求对应的路由内函数基本一样,只是有细微的差别,但是我们却每个路由里都完完整整的写了一遍,所以我们要将公共逻辑抽取出来。
|
||
|
|
||
|
```
|
||
|
func retHelloGinAndMethod(context *gin.Context) {
|
||
|
context.String(http.StatusOK, "hello gin "+strings.ToLower(context.Request.Method)+" method")
|
||
|
}
|
||
|
```
|
||
|
|
||
|
我们将方法的公共部分抽取出来,并通过 `context.Request.Method` 将请求的方法提取出来 ,并将其转化为小写。此时就可以改造我们的路由了,将原有的路由中的函数去掉换成我们所编写的新的函数。
|
||
|
|
||
|
```
|
||
|
// 添加 Get 请求路由
|
||
|
router.GET("/", retHelloGinAndMethod)
|
||
|
// 添加 Post 请求路由
|
||
|
router.POST("/", retHelloGinAndMethod)
|
||
|
// 添加 Put 请求路由
|
||
|
router.PUT("/", retHelloGinAndMethod)
|
||
|
// 添加 Delete 请求路由
|
||
|
router.DELETE("/", retHelloGinAndMethod)
|
||
|
// 添加 Patch 请求路由
|
||
|
router.PATCH("/", retHelloGinAndMethod)
|
||
|
// 添加 Head 请求路由
|
||
|
router.HEAD("/", retHelloGinAndMethod)
|
||
|
// 添加 Options 请求路由
|
||
|
router.OPTIONS("/", retHelloGinAndMethod)
|
||
|
```
|
||
|
|
||
|
此时运行单元测试,仍旧是完美通过。
|
||
|
|
||
|
# Handler 处理器
|
||
|
|
||
|
经过上面简单的例子的演示和操作,现在我们大概可以了解到路由需要传入两个参数,一个为路径,另一个为路由执行的方法,我们叫做它处理器 `Handler` ,而且,该参数是可变长参数。也就是说,可以传入多个 handler,形成一条 handler chain 。
|
||
|
|
||
|
同时对 handler 该函数有着一些要求,该函数需要传入一个 `Gin.Context` **指针**,同时要通过该指针进行值得处理。
|
||
|
|
||
|
Handler 函数可以对前端返回 字符串,Json,Html 等多种格式或形式文件,之后我们会慢慢逐一介绍。
|
||
|
|
||
|
# 获取路由路径中参数
|
||
|
|
||
|
知道了路由支持的方法和对应的处理器,那么接下来就应该了解如何从路由中获取参数。
|
||
|
|
||
|
编写一个新的路由,如下:
|
||
|
|
||
|
```
|
||
|
//省略其他代码
|
||
|
// 添加 user
|
||
|
router.GET("/user/:name",handler.Save)
|
||
|
// 省略其他代码
|
||
|
```
|
||
|
|
||
|
此时我们发现,在原来只有 `/` 分隔符的情况下出现了 `/:` 该符号就表示后面的字符串为一个占位符,用于将要进行的传值。,此时我们的路由为 `/user/{name}`
|
||
|
|
||
|
我们没有必要把所有的 Handler 都写到一个文件夹中,那样会臃肿不堪,所以我们新建一个文件夹`handler`,在文件夹下建立 `userHandler.go` 文件,编写该文件。
|
||
|
|
||
|
```
|
||
|
package handler
|
||
|
|
||
|
import (
|
||
|
"github.com/gin-gonic/gin"
|
||
|
"net/http"
|
||
|
)
|
||
|
|
||
|
func UserSave(context *gin.Context) {
|
||
|
username := context.Param("name")
|
||
|
context.String(http.StatusOK, "用户已经保存")
|
||
|
}
|
||
|
```
|
||
|
|
||
|
同样,我们用 `context.Param` 可以获取路由路径中的参数。
|
||
|
|
||
|
此时,就可以编写我们的单元测试。
|
||
|
|
||
|
新建立一个 `user_test.go` 文件。
|
||
|
|
||
|
```
|
||
|
func TestUserSave(t *testing.T) {
|
||
|
username := "lisi"
|
||
|
router := initRouter.SetupRouter()
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodGet, "/user/"+username, nil)
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
assert.Equal(t, "用户"+username+"已经保存", w.Body.String())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
运行单元测试,测试通过。同样我们可以运行我们的项目在浏览器中输入 `localhost:8080/user/lisi` 在浏览器页面上也可以看到 `用户lisi已经保存`
|
||
|
|
||
|
当然,获取参数的方法不止这一个。针对不同的路由,Gin 给出了不同的获取参数的方法,比如形如:`/user?name=lisi&age=18`。
|
||
|
|
||
|
我们再次添加一个 `Handler`,做为处理。在 `userHandler` 中添加下面的方法。
|
||
|
|
||
|
```
|
||
|
// 通过 query 方法进行获取参数
|
||
|
func UserSaveByQuery(context *gin.Context) {
|
||
|
username := context.Query("name")
|
||
|
age := context.Query("age")
|
||
|
context.String(http.StatusOK, "用户:"+username+",年龄:"+age+"已经保存")
|
||
|
}
|
||
|
```
|
||
|
|
||
|
同时对路由进行添加和完善。
|
||
|
|
||
|
```
|
||
|
router.GET("/user", handler.UserSaveByQuery)
|
||
|
```
|
||
|
|
||
|
完成路由之后,就可以重新编写单元测试,完善项目。
|
||
|
|
||
|
```
|
||
|
func TestUserSaveQuery(t *testing.T) {
|
||
|
username := "lisi"
|
||
|
age := 18
|
||
|
router := initRouter.SetupRouter()
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodGet, "/user?name="+username+"&age="+strconv.Itoa(age), nil)
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
assert.Equal(t, "用户:"+username+",年龄:"+strconv.Itoa(age)+"已经保存", w.Body.String())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
运行测试,测试通过。并且可以通过 浏览器访问`localhost:8080/user?name=lisi`,页面上打印出`用户:lisi,年龄:18已经保存` 。
|
||
|
|
||
|
当然,还可以通过 `context.DefaultQuery` 方法,在获取时,如果没有该值则赋给一个默认值。
|
||
|
|
||
|
重新修改获取年龄的代码,将其改为以下代码
|
||
|
|
||
|
```
|
||
|
age := context.DefaultQuery("age", "20")
|
||
|
```
|
||
|
|
||
|
重新编写我们的单元测试,并运行。
|
||
|
|
||
|
```
|
||
|
func TestUserSaveWithNotAge(t *testing.T) {
|
||
|
username := "lisi"
|
||
|
router := initRouter.SetupRouter()
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodGet, "/user?name="+username, nil)
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
assert.Equal(t, "用户:"+username+",年龄:20已经保存", w.Body.String())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
同样也可以通过浏览器访问 `/user?name=lisi` 可以看到浏览器上显示
|
||
|
|
||
|
`用户:lisi,年龄:20已经保存` 。
|
||
|
|
||
|
当然,还提供了其他参数获取方法, `QueryArray` 获取数组和 `QueryMap` 获取 map。
|
||
|
|
||
|
# 路由分组
|
||
|
|
||
|
此时我们再次看 `SetupRouter` 方法时,里面的路由基本可以分为两大类 `/` 和 `/user`,如果日后在进行功能的添加,那么势必会出现大量的路由,所以我们需要对路由进行一下管理,Gin 给我们提供了路由分组。
|
||
|
|
||
|
先把 `/` 的路由写到一起,运行 `index_test.go` 单元测试。
|
||
|
|
||
|
```
|
||
|
index := router.Group("/"){
|
||
|
// 添加 Get 请求路由
|
||
|
index.GET("", retHelloGinAndMethod)
|
||
|
// 添加 Post 请求路由
|
||
|
index.POST("", retHelloGinAndMethod)
|
||
|
// 添加 Put 请求路由
|
||
|
index.PUT("", retHelloGinAndMethod)
|
||
|
// 添加 Delete 请求路由
|
||
|
index.DELETE("", retHelloGinAndMethod)
|
||
|
// 添加 Patch 请求路由
|
||
|
index.PATCH("", retHelloGinAndMethod)
|
||
|
// 添加 Head 请求路由
|
||
|
index.HEAD("", retHelloGinAndMethod)
|
||
|
// 添加 Options 请求路由
|
||
|
index.OPTIONS("", retHelloGinAndMethod)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
通过 `router.Group` 返回一个新的分组路由,通过新的分组路由把之前的路由进行简单的修改。当然分组里面仍旧可以嵌套分组。
|
||
|
|
||
|
之前在请求方法中说到有一个 `Any` 函数可以通过任何请求,此时我们就可以把 `index` 里面所有的请求替换为 `Any`
|
||
|
|
||
|
```
|
||
|
index := router.Group("/"){
|
||
|
index.Any("", retHelloGinAndMethod)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
运行单元测试,测试都可以通过。
|
||
|
|
||
|
此时也发现了单元测试的好处,虽说之前花费了时间和经历编写了单元测试,但是日后的功能上修改,只需要进行运行单元测试就可以知道我们的功能是否正确,在后期的功能测试上大大减少了经历和时间。
|
||
|
|
||
|
同样我们把 `user` 也进行分组,分组不仅仅是将相同逻辑的代码放到一起,而且可以提供相同的路由前缀,修改后的路由仍旧和之前一致。运行单元测试 `user_test.go` ,单元测试可以完全通过,证明我们的代码没有问题。
|
||
|
|
||
|
```
|
||
|
userRouter := router.Group("/user"){
|
||
|
userRouter.GET("/:name", handler.UserSave)
|
||
|
userRouter.GET("", handler.UserSaveByQuery)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
# 总结
|
||
|
|
||
|
通过简单的路由的使用,基本明白了路由在 Gin 中的地位,也对一些常见的使用方式有了一些直观的认识。
|