如何借助Julien Schmidt的httprouter避免Go中的全局变量?
嘿,我刚好在使用httprouter开发Go REST API时遇到过完全一样的场景,下面给你几个实用的方案,既能规避全局变量,又能在处理器里优雅地获取currUser对象:
方案1:用结构体封装依赖 + 请求上下文传递currUser
核心思路是依赖注入:把配置、securecookie实例这些原本的全局变量,封装到一个自定义的服务器结构体里,通过接收器方法访问这些依赖;同时用Go的context.Context在中间件和处理器之间传递每个请求的currUser。
示例代码:
// 定义你的应用配置结构体 type AppConfig struct { // 比如数据库地址、端口等配置项 DBAddr string Port int } // 自定义API服务器结构体,封装所有依赖 type APIServer struct { router *httprouter.Router secureCookie *securecookie.SecureCookie config *AppConfig } // 创建API服务器实例,注入所有依赖 func NewAPIServer(config *AppConfig, sc *securecookie.SecureCookie) *APIServer { s := &APIServer{ router: httprouter.New(), secureCookie: sc, config: config, } s.setupRoutes() return s } // 注册路由,所有处理器都是结构体的接收器方法 func (s *APIServer) setupRoutes() { // 给需要认证的路由添加中间件 s.router.GET("/api/profile", s.authMiddleware(s.getProfileHandler)) } // 认证中间件:解析cookie得到currUser,并存入请求上下文 func (s *APIServer) authMiddleware(next httprouter.Handle) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { // 用结构体里的secureCookie实例解析用户信息 currUser := &User{} // 你的User结构体 err := s.secureCookie.Decode("user", r.Cookies()[0].Value, currUser) if err != nil { http.Error(w, "未授权", http.StatusUnauthorized) return } // 将currUser存入请求上下文 ctx := context.WithValue(r.Context(), "currUser", currUser) // 传递新的请求给下一个处理器 next(w, r.WithContext(ctx), p) } } // 业务处理器:从上下文直接获取currUser func (s *APIServer) getProfileHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) { // 从上下文取出currUser currUser, ok := r.Context().Value("currUser").(*User) if !ok { http.Error(w, "用户信息获取失败", http.StatusInternalServerError) return } // 直接使用currUser,同时还能访问s.config、s.secureCookie等依赖,完全不需要全局变量 w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(currUser) }
方案2:自定义Context Key避免魔法字符串
方案1里用字符串"currUser"作为上下文的key,可能会有命名冲突的风险。可以自定义一个私有类型的key来规避这个问题:
// 定义私有类型的context key,避免和其他包的key冲突 type contextKey string const currUserKey contextKey = "currUser" // 中间件里存入上下文时使用这个key ctx := context.WithValue(r.Context(), currUserKey, currUser) // 处理器里取出时使用同样的key currUser, ok := r.Context().Value(currUserKey).(*User)
这样的写法更安全,也更符合Go的最佳实践。
方案3:自定义Handler类型直接传递currUser
如果觉得从上下文取值有点繁琐,可以自定义一个带currUser参数的Handler类型,让中间件直接把用户传递给处理器:
// 自定义带用户参数的Handler类型 type UserHandler func(http.ResponseWriter, *http.Request, httprouter.Params, *User) // 适配httprouter.Handle的中间件 func (s *APIServer) authMiddleware(next UserHandler) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { currUser, err := s.parseUserFromCookie(r) // 复用之前的解析逻辑 if err != nil { http.Error(w, "未授权", http.StatusUnauthorized) return } // 直接把currUser作为参数传给处理器 next(w, r, p, currUser) } } // 业务处理器直接接收currUser参数 func (s *APIServer) getProfileHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params, currUser *User) { // 不用再从上下文取值,直接使用currUser json.NewEncoder(w).Encode(currUser) } // 注册路由时的写法 s.router.GET("/api/profile", s.authMiddleware(s.getProfileHandler))
这种方式让处理器的参数更明确,代码可读性更高,尤其适合需要频繁使用currUser的场景。
总结
这几个方案的核心都是:
- 用结构体封装依赖替代全局变量,实现依赖注入,让代码更易测试和维护;
- 用请求上下文或自定义Handler参数传递每个请求的
currUser,避免全局变量带来的并发安全问题。
你可以根据自己的项目复杂度选择最适合的方案,一般来说方案1+方案2是最通用的组合。
内容的提问来源于stack exchange,提问作者ekamwalia
相关产品推荐
相关产品推荐

