Mongo客户端在main函数中设置,其他模块中的函数接收nil值

Mongo client set in main function, functions in other modules receive nil value

我有一个 restful API 使用多路复用器和 mongo 驱动程序。按照教程,我尝试在主包中像这样设置服务器和 mongo 客户端:

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"

    "github.com/gorilla/mux"
    c "github.com/moonlightfight/elo-backend/config"
    "github.com/moonlightfight/elo-backend/routes/admin"
    "github.com/moonlightfight/elo-backend/routes/tournament"
    "github.com/spf13/viper"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

var client *mongo.Client

func main() {
    // Set the file name of the configurations file
    viper.SetConfigName("config")

    // Set the path to look for the configurations file
    viper.AddConfigPath(".")

    // Enable VIPER to read Environment Variables
    viper.AutomaticEnv()

    viper.SetConfigType("yml")
    var configuration c.Configurations

    if err := viper.ReadInConfig(); err != nil {
        fmt.Printf("Error reading config file, %s", err)
    }

    err := viper.Unmarshal(&configuration)
    if err != nil {
        fmt.Printf("Unable to decode into struct, %v", err)
    }
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb+srv://%s:%s@cluster0.ucnph.mongodb.net/%s?retryWrites=true&w=majority", configuration.Database.DBUser, configuration.Database.DBPass, configuration.Database.DBName))
    port := fmt.Sprintf(":%d", configuration.Server.Port)
    mongo.Connect(ctx, clientOptions)
    router := mux.NewRouter()
    router.HandleFunc("/api/admin", admin.CreateAdminEndpoint).Methods("POST")
    router.HandleFunc("/api/admin/login", admin.AdminLoginEndpoint).Methods("POST")
    router.HandleFunc("/api/tournament/getfromweb", tournament.GetTournamentData).Methods("GET")
    fmt.Printf("server listening on http://localhost%v", port)
    http.ListenAndServe(port, router)
}

现在,为了更简洁地管理我的代码,我设置了模块(如您在 main 的导入中所见)来处理 mux 将用于端点的函数。

在一个特定案例中(处理“/api/admin”端点:

package admin

import (
    "context"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "time"

    "github.com/dgrijalva/jwt-go"
    c "github.com/moonlightfight/elo-backend/config"
    m "github.com/moonlightfight/elo-backend/models"
    "github.com/spf13/viper"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "golang.org/x/crypto/bcrypt"
)

var client *mongo.Client

// other code here

func CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
    response.Header().Set("content-type", "application/json")
    var admin m.Admin
    err := json.NewDecoder(request.Body).Decode(&admin)
    if err != nil {
        log.Println(err)
    }
    // encrypt user password
    admin.Password = HashPassword(admin.Password)
    fmt.Println(client)
    collection := client.Database("test").Collection("Admin")
    ctx, ctxErr := context.WithTimeout(context.Background(), 5*time.Second)

    if ctxErr != nil {
        log.Println(ctxErr)
    }
    result, resErr := collection.InsertOne(ctx, admin)
    if resErr != nil {
        log.Println(resErr)
    }
    json.NewEncoder(response).Encode(result)
}

当 运行 时,我收到以下错误:

2021/06/05 02:02:39 http: panic serving [::1]:53359: runtime error: invalid memory address or nil pointer dereference

这指向我在端点函数中定义集合的行,该行记录为具有 nil 值。我显然没有在模块中正确定义 mongo 客户端,并且不确定跨多个模块维护此客户端连接的最佳实践。

在避免全局变量的情况下执行此操作的标准方法是定义一个代表您的服务器的 struct,其方法将是处理程序。然后这些方法共享结构的数据,并且您将 mongo 客户端之类的东西放在那里。

像这样的东西(在你的 admin 包中):

type Server struct {
  client *mongo.Client
}

func NewServer(client *mongo.Client) *Server {
  return &Server{client: client}
}

func (srv *Server) CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
  // ...
  // use srv.client here
  //
}

现在在 main 中,您可以这样创建服务器:

 client, err := mongo.Connect(...)
 // handle err!
 srv := admin.NewServer(client)
 
 router := mux.NewRouter()
 router.HandleFunc("/api/admin", srv.CreateAdminEndpoint).Methods("POST")