You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:18:10