1. 第一个Gin服务中的路由
我们的第一个gin服务源码如下:
go
package main
import (
"net/http"
// 导入gin框架
"github.com/gin-gonic/gin"
)
func main() {
// 创建默认的gin路由
router := gin.Default()
// 定义一个简单的GET端点
router.GET("/ping", func(c *gin.Context) {
// 返回JSON数据
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// 启动服务(默认端口8080)
err := router.Run()
if err != nil {
return
}
}
代码中,我们使用router.GET方法为我们的gin服务定义了一个简单的/ping路由。
2. 设置路由Method
除了GET方式的路由外,Gin框架支持定义各种HTTP Method的路由!
2.1. GET、POST、PUT...
Gin框架直接提供了大部分HTTP Method的路由定义方法:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
- HEAD
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
router.GET("/ping", handler.GetPing)
router.POST("/ping", handler.PostPing)
router.PUT("/ping", handler.PutPing)
router.DELETE("/ping", handler.DeletePing)
router.PATCH("/ping", handler.PatchPing)
router.OPTIONS("/ping", handler.OptionsPing)
router.HEAD("/ping", handler.HeadPing)
return router
}
注意: 使用这些方法定义的路由,只能使用对应的HTTP Method访问。如果使用错误的HTTP Method访问接口,默认会404。
2.2. 匹配所有Methods(Any)
Gin框架提供了Any方法来定义匹配所有HTTP Methods的路由。
源码:
go
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
// 可以看到,实际上是为anyMethods中定义的所有HTTP Method都注册了路由
for _, method := range anyMethods {
group.handle(method, relativePath, handlers)
}
return group.returnObj()
}
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
router.Any("/ping/any", handler.AnyPing)
return router
}
2.3. 更灵活的Method(Handle)
Handle方法以指定的路径和method注册路由。
- 对于 GET、POST、PUT、PATCH 和 DELETE 请求,可以使用相应的快捷函数。
Handle方法旨在用于批量加载,且它运行使用不常用的、非标准化的或者自定义的HTTP Method。
Handle方法源码如下:
go
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
// 可以看到,http method只做了简单的正则校验
if matched := regEnLetter.MatchString(httpMethod); !matched {
panic("http method " + httpMethod + " is not valid")
}
return group.handle(httpMethod, relativePath, handlers)
}
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
// look,这里的HTTP Method竟然是HELLO
router.Handle("HELLO", "/ping", handler.HelloPing)
return router
}
2.4. 匹配多种Methods(Match)
Match方法用于定义匹配多种HTTP Methods的路由。
源码:
go
// 可以看到methods参数是一个数组
func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
for _, method := range methods {
group.handle(method, relativePath, handlers)
}
return group.returnObj()
}
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
// 这里匹配GET和POST
router.Match([]string{"GET", "POST"}, "/match", handler.MatchPing)
return router
}
2.5. MethodNotAllowed处理
2.5.1. 默认行为404
在上面的章节中,我们介绍了各种定义路由HTTP Method的方式。
当我们定义了一个路由应该使用的HTTP Method后,如果请求的HTTP Method不匹配,默认会应答404.
示例:
go
router.GET("/ping", handler.GetPing)
// 使用GET方式访问:正常应答
// 使用POST方式访问:应答404
2.5.2. HandleMethodNotAllowed配置
gin.Engine中有一个HandleMethodNotAllowed属性,可以用于修改Method不匹配时的默认行为。
go
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405.
// If no other Method is allowed, the request is delegated to the NotFound
// handler.
HandleMethodNotAllowed bool
- 当HandleMethodNotAllowed开启时,如果请求的路径存在但HTTP方法不匹配,gin会检查该路径是否注册了其他HTTP方法。如果存在其他方法,则返回405(Method Not Allowed);如果该路径没有任何方法注册,则仍然返回404(Not Found)。
示例:
go
router.HandleMethodNotAllowed = true
router.GET("/ping", handler.GetPing)
// GET方式访问/ping:正常应答
// POST方式访问/ping:应答405
2.5.3 自定义MethodNotAllowed处理
上文提到,如果请求路径正确但请求Method不匹配(MethodNotAllowed场景):
- 如果HandleMethodNotAllowed=false:默认应答404(Not Found)。
- 如果HandleMethodNotAllowed=true:默认应答405(Method Not Allowed)。
除此之外,gin还提供了NoMethod方法来自定义处理逻辑:
go
// 用于设置当 Engine.HandleMethodNotAllowed = true 时调用的处理程序。
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers
engine.rebuild405Handlers()
}
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
// 开启HandleMethodNotAllowed
router.HandleMethodNotAllowed = true
router.GET("/ping", handler.GetPing)
// 定义处理函数
router.NoMethod(handler.NoMethodPing)
return router
}
func NoMethodPing(c *gin.Context) {
// 通常应返回405状态码,但可根据需求自定义
c.JSON(http.StatusMethodNotAllowed, gin.H{
"message": "method not allowed",
})
}
// GET方式访问/ping:正常应答
// POST方式访问/ping:no method pong
3. 路由组
3.1. 路由组
gin框架支持路由分组:即将具有相同路径前缀或者需要相同中间件的路由放在同一个路由组中。
Group方法用于路由分组,其源码如下:
go
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
Group方法用于创建一个新的路由组:
- 参数
relativePath用于定义组中路由的通用路径。 - 参数
handlers用于定义组中路由的通用中间件。 Group方法创建一个新的路由组,我们可以将具有相同路径前缀或者要使用相同中间件的路由划分为一个组。
示例:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
testRouter := router.Group("/test")
{
testRouter.Any("/ping", handler.TestPing)
testRouter.Any("/hello", handler.TestHello)
}
platRouter := router.Group("/plat", middleware.AuthMiddleware())
{
platRouter.Any("/login", handler.PlatLogin)
platRouter.Any("/logout", handler.PlatLogout)
}
return router
}
// 上面接口的访问路径分别为
// /test/ping
// /test/hello
// /plat/login
// /plat/logout
3.2. 嵌套路由组
Gin框架支持多级路由组嵌套,允许更细粒度的路由组织。例如:
go
func NewHttpRouter() *gin.Engine {
router := gin.Default()
api := router.Group("/api")
{
v1 := api.Group("/v1")
{
v1.GET("/users", handler.GetUsers)
v1.POST("/users", handler.CreateUser)
}
v2 := api.Group("/v2")
{
v2.GET("/users", handler.GetUsersV2)
}
}
return router
}
这样,/api/v1/users和/api/v2/users将分别由不同的处理函数处理。
此外,父路由组的中间件会自动应用到所有子路由组。例如,如果api组配置了认证中间件,则v1和v2组的所有路由都将应用该中间件。
4. 最佳实践
在实际项目开发中,合理使用Gin框架的路由和路由组功能能够大大提高代码的可维护性和可扩展性。以下是一些推荐的最佳实践:
4.1. 按业务模块划分路由组
将不同业务模块的路由划分到不同的路由组中,有助于保持代码结构清晰:
go
func SetupRoutes(router *gin.Engine) {
// 用户相关路由
userGroup := router.Group("/user")
{
userGroup.POST("/register", user.Register)
userGroup.POST("/login", user.Login)
userGroup.GET("/profile", authMiddleware(), user.GetProfile)
}
// 订单相关路由
orderGroup := router.Group("/order")
{
orderGroup.GET("/:id", order.GetOrder)
orderGroup.POST("/", authMiddleware(), order.CreateOrder)
orderGroup.PUT("/:id", authMiddleware(), order.UpdateOrder)
}
// 管理后台路由
adminGroup := router.Group("/admin", adminAuthMiddleware())
{
adminGroup.GET("/dashboard", admin.Dashboard)
adminGroup.GET("/users", admin.ListUsers)
adminGroup.DELETE("/users/:id", admin.DeleteUser)
}
}
4.2. 合理使用中间件
在路由组上应用共享的中间件,避免重复代码:
go
// API版本控制
v1 := router.Group("/api/v1")
v1.Use(loggingMiddleware(), corsMiddleware())
authGroup := v1.Group("/auth")
authGroup.Use(rateLimitMiddleware()) // 特定路由组的额外中间件
{
authGroup.POST("/login", auth.Login)
authGroup.POST("/register", auth.Register)
}
userGroup := v1.Group("/user", authRequiredMiddleware()) // 需要认证的路由组
{
userGroup.GET("/profile", user.GetProfile)
userGroup.PUT("/profile", user.UpdateProfile)
}
4.3. 统一错误处理
通过NoRoute和NoMethod统一处理404和405错误:
go
func SetupRouter() *gin.Engine {
router := gin.Default()
// 全局404处理
router.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": "Page not found",
"path": c.Request.URL.Path,
})
})
// 全局405处理
router.HandleMethodNotAllowed = true
router.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"error": "Method not allowed",
"method": c.Request.Method,
"path": c.Request.URL.Path,
})
})
// 路由定义...
return router
}
4.4. RESTful风格设计
遵循RESTful API设计原则,合理使用HTTP方法:
go
// 推荐的RESTful风格路由设计
api := router.Group("/api/v1")
{
// 资源集合操作
api.GET("/posts", post.ListPosts) // 获取文章列表
api.POST("/posts", post.CreatePost) // 创建新文章
// 单个资源操作
api.GET("/posts/:id", post.GetPost) // 获取特定文章
api.PUT("/posts/:id", post.UpdatePost) // 完整更新文章
api.PATCH("/posts/:id", post.PatchPost) // 部分更新文章
api.DELETE("/posts/:id", post.DeletePost) // 删除文章
// 子资源操作
api.GET("/posts/:id/comments", comment.ListComments) // 获取文章评论
api.POST("/posts/:id/comments", comment.CreateComment) // 添加评论
}
4.5. 路由版本控制
通过路由组实现API版本控制:
go
func SetupRoutes(router *gin.Engine) {
// v1版本API
v1 := router.Group("/api/v1")
{
v1.GET("/users", userHandler.ListUsersV1)
v1.GET("/users/:id", userHandler.GetUserV1)
}
// v2版本API
v2 := router.Group("/api/v2")
{
v2.GET("/users", userHandler.ListUsersV2)
v2.GET("/users/:id", userHandler.GetUserV2)
v2.GET("/users/:id/profile", userHandler.GetUserProfileV2)
}
}
4.6. 参数验证和路由分离
将路由定义与业务逻辑分离,使代码更加清晰:
go
// routes/setup.go
func SetupUserRoutes(router *gin.Engine) {
userGroup := router.Group("/users")
{
userGroup.GET("/", listUsers)
userGroup.POST("/", createUser)
userGroup.GET("/:id", getUser)
userGroup.PUT("/:id", updateUser)
userGroup.DELETE("/:id", deleteUser)
}
}
// handlers/user_handler.go
func listUsers(c *gin.Context) {
// 处理获取用户列表逻辑
}
func createUser(c *gin.Context) {
// 处理创建用户逻辑
}
func getUser(c *gin.Context) {
id := c.Param("id")
// 处理获取单个用户逻辑
}
func updateUser(c *gin.Context) {
id := c.Param("id")
// 处理更新用户逻辑
}
func deleteUser(c *gin.Context) {
id := c.Param("id")
// 处理删除用户逻辑
}