路由注册:RouterGroup(routergroup.go) 1.1RouterGroup结构源码位置:routergroup.go:53-62type RouterGroup struct { Handlers HandlersChain // 该组所有中间件 basePath string // 路径前缀 engine *Engine // 反向引用 Engine root bool // 是否是根组(Engine 自身) }设计意图:RouterGroup是个非常轻的对象(4 个字段)。它存在的意义是:提供前缀 公共中间件的抽象,让r.Group(/api)这种写法很自然让Engine通过内嵌复用所有路由注册方法(r.GET(...)实际是r.RouterGroup.GET(...))它不存路由树——树是Engine的字段。RouterGroup 只负责算路径 拼中间件 转交 Engine。接口层次// routergroup.go:26-30 type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } // routergroup.go:33-51 type IRoutes interface { Use(...HandlerFunc) IRoutes Handle(string, string, ...HandlerFunc) IRoutes Any(string, ...HandlerFunc) IRoutes GET(string, ...HandlerFunc) IRoutes POST(string, ...HandlerFunc) IRoutes // ... DELETE / PATCH / PUT / OPTIONS / HEAD / Match StaticFile(string, string) IRoutes StaticFileFS(string, string, http.FileSystem) IRoutes Static(string, string) IRoutes StaticFS(string, http.FileSystem) IRoutes }什么分两层?IRouter比IRoutes多了Group方法。子组可以再Group,但根Engine的Group方法返回*RouterGroup(不是IRoutes)。这种拆分让类型清晰:能 Group 的就是 IRouter,只能注册路由的就是 IRoutes。1.2 中间件:Use源码位置:routergroup.go:64-68func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers append(group.Handlers, middleware...) return group.returnObj() }就这么简单——把中间件追加到group.Handlers切片末尾。returnObj()见routergroup.go:254-259:func (group *RouterGroup) returnObj() IRoutes { if group.root { return group.engine // 根组返回 Engine(IRoutes 实现) } return group // 子组返回自己 }设计意图:让链式 API 在不同上下文返回合适类型。全局r.Use(...).GET(...)直接返回*Engine,方便继续操作;子组g.Use(...).GET(...)返回*RouterGroup。1.3 创建子组:Group源码位置:routergroup.go:70-78func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return RouterGroup{ Handlers: group.combineHandlers(handlers), basePath: group.calculateAbsolutePath(relativePath), engine: group.engine, } }3 个动作:combineHandlers(handlers)— 合并父组的中间件 传入的中间件calculateAbsolutePath(relativePath)— 父组的 basePath 相对路径engine: group.engine— 共享同一个 Engine(也就是同一棵路由树)注意:子组没有root字段——它的 zero value 是false,正是我们想要的。1.3.1combineHandlers—— 中间件合并的核心源码位置:routergroup.go:241-248func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize : len(group.Handlers) len(handlers) assert1(finalSize int(abortIndex), too many handlers) mergedHandlers : make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }关键点:finalSize abortIndex校验:abortIndex math.MaxInt8 1 63,所以单个请求的中间件总数不能超过 63(包含业务 handler)。合并是新建切片,不是原地修改——这样子组修改中间件不会影响父组。父组中间件在前,新中间件在后,执行顺序就是从父到子。1.3.2calculateAbsolutePathfunc (group *RouterGroup) calculateAbsolutePath(relativePath string) string { return joinPaths(group.basePath, relativePath) }joinPaths在path.go实现(下一章细讲),做路径拼接。1.3.3 嵌套示例r : gin.Default() // basePath/, Handlers[] api : r.Group(/api, AuthMiddleware) // basePath/api, Handlers[AuthMiddleware] v1 : api.Group(/v1, RateLimitMiddleware) // basePath/api/v1, Handlers[AuthMiddleware, RateLimitMiddleware] v1.GET(/users, h) // 注册到 /api/v1/users,handlers[AuthMiddleware, RateLimitMiddleware, h]1.4 注册路由:handle源码位置:routergroup.go:86-91func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath : group.calculateAbsolutePath(relativePath) // ① 拼绝对路径 handlers group.combineHandlers(handlers) // ② 合并中间件 group.engine.addRoute(httpMethod, absolutePath, handlers) // ③ 加入路由树 return group.returnObj() }这是所有 GET/POST/PUT/... 的共同终点。三步:算绝对路径把当前组的中间件和传入 handlers 合并交给 Engine 的addRoute(第 2 章已分析)1.4.1 快捷方法源码位置:routergroup.go:103-153func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { if matched : regEnLetter.MatchString(httpMethod); !matched { panic(http method httpMethod is not valid) } return group.handle(httpMethod, relativePath, handlers) } func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPost, relativePath, handlers) } func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodGet, relativePath, handlers) } // ... DELETE / PATCH / PUT / OPTIONS / HEAD 同构 func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { for _, method : range anyMethods { group.handle(method, relativePath, handlers) } return group.returnObj() } func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes { for _, method : range methods { group.handle(method, relativePath, handlers) } return group.returnObj() }每个 HTTP 方法的注册函数都是一行包装,核心都在handle。anyMethods(routergroup.go:18-24):anyMethods []string{ http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect, http.MethodTrace, }注意:Any不包含扩展方法(如 WebDAV 的 PROPFIND)。如需自定义方法,用Handle(PROPFIND, ...)。1.5 静态文件1.5.1StaticFile—— 单个文件源码位置:routergroup.go:164-188func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { return group.staticFileHandler(relativePath, func(c *Context) { c.File(filepath) }) } func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes { if strings.Contains(relativePath, :) || strings.Contains(relativePath, *) { panic(URL parameters can not be used when serving a static file) } group.GET(relativePath, handler) group.HEAD(relativePath, handler) return group.returnObj() }特点:静态路径不能带参数(panic)同时注册 GET 和 HEAD(后者用于客户端只取 headers)1.5.2Static/StaticFS—— 整个目录源码位置:routergroup.go:190-239func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes { if strings.Contains(relativePath, :) || strings.Contains(relativePath, *) { panic(URL parameters can not be used when serving a static folder) } handler : group.createStaticHandler(relativePath, fs) urlPattern : path.Join(relativePath, /*filepath) group.GET(urlPattern, handler) group.HEAD(urlPattern, handler) return group.returnObj() } func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath : group.calculateAbsolutePath(relativePath) fileServer : http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { if _, noListing : fs.(*OnlyFilesFS); noListing { c.Writer.WriteHeader(http.StatusNotFound) } file : c.Param(filepath) f, err : fs.Open(file) if err ! nil { c.Writer.WriteHeader(http.StatusNotFound) c.handlers group.engine.noRoute c.index -1 // ★ 重置中间件链,走 noRoute return } f.Close() fileServer.ServeHTTP(c.Writer, c.Request) } }巧妙之处:路由模式/assets/*filepath(用通配符)复用标准库http.FileServer做实际服务文件不存在时,手动改写c.handlers和c.index,让流程走noRoute兜底c.index -1这一行很关键,详见第 6 章对Next的分析。1.6IRoutervsIRoutes:为什么这么设计?// IRouter 内嵌 IRoutes,并加了 Group type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup }设计意图:让用户写的辅助函数能接收任意路由组。// 注册一组路由:既可以是 Engine,也可以是 RouterGroup func RegisterUserRoutes(r gin.IRoutes) { r.GET(/users, listUsers) r.POST(/users, createUser) } // 用法 1:全局 RegisterUserRoutes(r) // 用法 2:子组 v1 : r.Group(/v1) RegisterUserRoutes(v1)如果只用*Engine或*RouterGroup,这种通用性就做不到。1.7 看不见的细节1.7.1 注册顺序很重要r.GET(/users/me, meH) // 先注册静态 r.GET(/users/:id, idH) // 后注册参数由于 radix tree 的静态优先机制,顺序不影响最终匹配结果。但如果是同一段内冲突(:idvs:name),启动时会 panic。1.7.2 注册时的 debug 输出addRoute会调debugPrintRoute,在 debug 模式下打印:[GIN-debug] GET /users/:id -- main.idH (3 handlers)第三个数字(3 handlers)表示合并后的 handlers 长度。如果你看到(1 handlers),意味着没有任何中间件——通常意味着gin.New()没挂 Logger/Recovery。1.7.3combineHandlers的容量校验assert1(finalSize int(abortIndex), too many handlers)abortIndex math.MaxInt8 1 63,所以单个请求的中间件 handler 总数 ≤ 62。对绝大多数项目远超够用,但写中间件堆叠特别多的代码要注意。1.8 完整流程示例r : gin.Default() // basePath/, Handlers[Logger, Recovery] api : r.Group(/api, Auth) // basePath/api, Handlers[Logger, Recovery, Auth] v1 : api.Group(/v1) // basePath/api/v1, Handlers[Logger, Recovery, Auth] v1.GET(/users/:id, getUser) // 注册路由最后一步v1.GET(/users/:id, getUser)内部:handle(GET, /users/:id, [getUser])calculateAbsolutePath→/api/v1/users/:idcombineHandlers→[Logger, Recovery, Auth, getUser](4 个)engine.addRoute(GET, /api/v1/users/:id, [Logger, Recovery, Auth, getUser])engine.trees.get(GET)找到 GET 树root.addRoute(/api/v1/users/:id, handlers)插入 radix tree(详见第 3 章)请求/api/v1/users/42进入时:engine.handleHTTPRequest找到 GET 树root.getValue(/api/v1/users/42, ...)返回handlers[Logger, Recovery, Auth, getUser]和params[{id, 42}]c.handlers value.handlers,c.Params [{id, 42}]c.Next()依次执行 Logger → Recovery → Auth → getUser1.9 小结✅RouterGroup是个轻对象,只负责算路径 拼中间件 转交 Engine✅Engine内嵌RouterGroup,所以r.GET/r.Use都能用✅Group创建子组时,会复制父组的中间件(不污染父组)✅ 所有 HTTP 方法的注册函数最终走handle→engine.addRoute✅ 静态文件复用标准库http.FileServer,只是套了通配符路由