大家好!我是 xcbeyond, xcbeyond 就是我,大家也可以叫我超哥!本文首发于我的博客: Gin 路由
Gin 是一个标准的 Web 服务框架,遵循 Restful API 接口规范,其路由库是基于 httproute 实现的。
本节将从 Gin 路由开始,详细讲述各种路由场景下,如何通过 Gin 来实现。
基本路由
Gin 支持 GET、POST、PUT、PATCH、DELETE、OPTIONS 等请求类型。
示例:
package mainimport (
"net/http"
"github.com/gin-gonic/gin")func main() {
route := gin.Default()
// 设置一个get请求,其URL为/hello,并实现简单的响应
route.GET("/get", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "this is a get method response!",
})
})
// 具体实现可单独定义一个函数
route.POST("/post", postHandler)
route.PUT("/put", func(c *gin.Context) {
})
route.PATCH("/patch", func(c *gin.Context) {
})
route.DELETE("/delete", func(c *gin.Context) {
})
// ……
route.Run()}func postHandler(c *gin.Context) {
c.JSON(http.StatusOK, "this is a post method response!")}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.
参数处理
path 参数
可通过 *gin.Context
的 Param 函数获取请求 Path 中的参数,Path 路径中参数以:
开头,如:/user/:name
,能够匹配路径 /user/xx
。
示例:
package mainimport (
"fmt"
"net/http"
"github.com/gin-gonic/gin")type User struct {
Username string `json:"username"`
Sex string `json:"sex"`
Age int `json:"age"`
Labels []string `json:"lalels"`}func main() {
route := gin.Default()
users := []User{
{
Username: "xcbeyond",
Sex: "男",
Age: 18,
Labels: []string{"年轻", "帅"},
},
}
// 请求path中存在参数
route.GET("/user/:name", func(c *gin.Context) {
// 获取请求path中的参数
name := c.Param("name")
for _, user := range users {
if user.Username == name {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusOK, fmt.Errorf("not found user [%s]", name))
})
route.Run()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.
查询参数
可通过 *gin.Context
的 Query 函数获取参数,如:/user?name=xcbeyond
。
示例:
package mainimport (
"net/http"
"github.com/gin-gonic/gin")type User struct {
Username string `json:"username"`
Sex string `json:"sex"`
Age int `json:"age"`
Labels []string `json:"lalels"`}func main() {
route := gin.Default()
users := []User{
{
Username: "xcbeyond",
Sex: "男",
Age: 18,
Labels: []string{"年轻", "帅"},
},
}
// 查询参数,如:/user?name=xcbeyond
route.GET("/user", func(c *gin.Context) {
// 获取中的参数
name := c.Query("name")
for _, user := range users {
if user.Username == name {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusOK, "not found user "+name)
})
route.Run()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.
……
路由分组
Gin 提供了路由分组的能力,方便管理分组管理路由,将具有相同路由 URL 前缀的进行分类处理,常见于不同版本的分组,如:/api/v1
、/api/v1
。
此外,还支持多层分组。
示例:
package mainimport (
"math/rand"
"net/http"
"github.com/gin-gonic/gin")type User struct {
Username string `json:"username"`
Sex string `json:"sex"`
Age int `json:"age"`
Labels []string `json:"lalels"`}func main() {
route := gin.Default()
users := []User{
{
Username: "xcbeyond",
Sex: "男",
Age: 18,
Labels: []string{"年轻", "帅"},
},
{
Username: "niki",
Sex: "女",
Age: 16,
Labels: []string{"漂亮"},
},
}
// api分组
api := route.Group("/api")
{
// v1分组
v1 := api.Group("/v1")
{
v1.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
for _, user := range users {
if user.Username == name {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusOK, "not found user :"+name)
})
}
// v2分组
v2 := api.Group("/v2")
{
v2.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
for _, user := range users {
if user.Username == name {
c.JSON(http.StatusOK, user)
return
}
}
// 如果没有查到,则随机返回一个
user := users[rand.Intn(len(users)-1)]
c.JSON(http.StatusOK, "not found user ["+name+"],but user ["+user.Username+"] is exist!")
})
}
}
route.Run()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.
路由拆分
上述路由示例中,都是将所有路由信息写在同一个源文件、函数中,但在实际项目中会涉及大量的接口,写在一起就显得太不合适了。
在实际项目中,我们更倾向于将路由代码拆分出来,可拆分为单独的包、多个路由源文件等。
根据实际项目的规模大小,可分不同粒度拆分。
(以下路由拆分仅供参考,可根据具体项目灵活调整!)
路由拆分为单独源文件或包
将路由实现分离到单独的包下,使得项目结构更加清晰。项目结构如下:
.
├── main.go
└── routes
└── routes.go1.2.3.4.
示例完整源码: route-split-v1
在 /routes/routes.go
文件中实现并注册路由信息:
package routesimport (
"net/http"
"github.com/gin-gonic/gin")type User struct {
Username string `json:"username"`
Sex string `json:"sex"`
Age int `json:"age"`
Labels []string `json:"lalels"`}var users []User// 为方便测试,则直接通过init方式赋值。实际项目中,一般通过数据库等其它方式查询获取数据。func init() {
users = []User{
{
Username: "xcbeyond",
Sex: "男",
Age: 18,
Labels: []string{"年轻", "帅"},
},
{
Username: "niki",
Sex: "女",
Age: 16,
Labels: []string{"漂亮"},
},
}}// SetupRouter 配置路由func SetupRouter() *gin.Engine {
route := gin.Default()
route.GET("/user/:name", querUserHandler)
// 其它更多路由
return route}// handlerfunc querUserHandler(c *gin.Context) {
name := c.Param("name")
for _, user := range users {
if user.Username == name {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusOK, "not found user :"+name)}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.
并在 main.go 中调用路由配置函数 SetupRouter 即可:
func main() {
route := routes.SetupRouter()
if err := route.Run(); err != nil {
fmt.Printf("startup server failed,err: %v", err)
}}1.2.3.4.5.6.
路由拆分为多个源文件
当随着项目的业务功能丰富,体量变大,所有的路由都写在一个 routes.go 源文件中,将会导致这个源文件越来越臃肿,不便于后期维护和阅读。
因此,可根据某种维度拆分为多个路由文件来实现不同业务功能。项目结构如下:
.
├── main.go
└── routes
├── auth.go
├── routes.go
└── user.go1.2.3.4.5.6.
示例完整源码: route-split-v2
在 routes 包下,根据某种维度拆分为多个路由实现文件,如:根据业务模块,可拆分为认证模块(auth.go)、用户模块(user.go)等,并在各自路由文件中实现具体的业务功能,并进行路由注册。
如,认证模块 auth.go:
package routesimport (
"github.com/gin-gonic/gin")// AuthRegister route registerfunc AuthRegister(e *gin.Engine) {
e.GET("/auth/login", loginHandler)
e.POST("/auth/logout", logoutUserHanler)
// ……}// loginHandlerfunc loginHandler(c *gin.Context) {}// logoutUserHanlerfunc logoutUserHanler(c *gin.Context) {}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
其中,定义 AuthRegister 函数将该模块下所有路由进行注册,注意该函数为大写字母开头,作为全局函数能够被包外 main.go 调用。
routes/routes.go 中,配置路由,并统一注册所有模块的路由:
package routesimport (
"github.com/gin-gonic/gin")// SetupRouter 配置路由func SetupRouter() *gin.Engine {
route := gin.Default()
// other config
// register all route.
UserRegister(route)
AuthRegister(route)
// ……
return route}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
main.go 和上一版本一样,作为程序等入口:
func main() {
route := routes.SetupRouter()
if err := route.Run(); err != nil {
fmt.Printf("startup server failed,err: %v", err)
}}1.2.3.4.5.6.