Go和Gin:为数据库上下文传递结构体?

6ie5vjzr  于 2023-10-14  发布在  Go
关注(0)|答案(5)|浏览(138)

我刚刚开始尝试Go,我希望用它重新实现一个用node编写的API服务器。
我在尝试使用依赖注入来传递数据库上下文作为GIN中间件时遇到了一个障碍。到目前为止,我把它设置成这样:
main.go:

package main

import (
        "fmt"
        "runtime"
        "log"
        "github.com/gin-gonic/gin"
        "votesforschools.com/api/public"
        "votesforschools.com/api/models"
)

type DB struct {
        models.DataStore
}

func main() {
        ConfigRuntime()
        ConfigServer()
}

func Database(connectionString string) gin.HandlerFunc {
        dbInstance, err := models.NewDB(connectionString)
        if err != nil {
                log.Panic(err)
        }

        db := &DB{dbInstance}

        return func(c *gin.Context) {
                c.Set("DB", db)
                c.Next()
        }
}

func ConfigRuntime() {
        nuCPU := runtime.NumCPU()
        runtime.GOMAXPROCS(nuCPU)
        fmt.Printf("Running with %d CPUs\n", nuCPU)
}

func ConfigServer() {

        gin.SetMode(gin.ReleaseMode)

        router := gin.New()
        router.Use(Database("<connectionstring>"))
        router.GET("/public/current-vote-pack", public.GetCurrentVotePack)
        router.Run(":1000")
}

models/db.go

package models

import (
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
)

type DataStore interface {
        GetVotePack(id string) (*VotePack, error)
}

type DB struct {
        *sql.DB
}

func NewDB(dataSource string) (*DB, error) {
        db, err := sql.Open("mysql", dataSource)
        if err != nil {
                return nil, err
        }
        if err = db.Ping(); err != nil {
                return nil, err
        }
        return &DB{db}, nil
}

models/votepack.go

package models

import (
        "time"
        "database/sql"
)

type VotePack struct {
        id string
        question string
        description string
        startDate time.Time
        endDate time.Time
        thankYou string
        curriculum []string
}

func (db *DB) GetVotePack(id string) (*VotePack, error) {

        var votePack *VotePack

        err := db.QueryRow(
                "SELECT id, question, description, start_date AS startDate, end_date AS endDate, thank_you AS thankYou, curriculum WHERE id = ?", id).Scan(
                &votePack.id, &votePack.question, &votePack.description, &votePack.startDate, &votePack.endDate, &votePack.thankYou, &votePack.curriculum)

        switch {
        case err == sql.ErrNoRows:
                return nil, err
        case err != nil:
                return nil, err
         default:
                return votePack, nil
        }
}

因此,通过以上所有内容,我希望将models.DataSource作为中间件传递,以便可以像这样访问它:
public/public.go

package public

import (
        "github.com/gin-gonic/gin"
)

func GetCurrentVotePack(context *gin.Context) {
        db := context.Keys["DB"]

        votePack, err := db.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
        if err != nil{
                context.String(404, "Votepack Not Found")
        }
        context.JSON(200, votePack)
}

但是我得到public\public.go:10: db.GetVotePack undefined (type interface {} is interface with no methods)
当我在调试器中检查时(使用Webstorm和插件),db只是一个空对象。我试着做得很好,避免使用全局变量

nr7wwzry

nr7wwzry1#

我不认为context应该用作DI容器:https://golang.org/pkg/context/
包上下文定义了上下文类型,该类型跨API边界和进程之间携带期限、取消信号和其他请求范围的值。
我宁愿使用:用途:

package public

type PublicController struct {
        Database *DB
}

func (c *PublicController) GetCurrentVotePack(context *gin.Context) {
        votePack, err := c.Database.GetVotePack("c5039ecd-e774-4c19-a2b9-600c2134784d")
        if err != nil{
                context.String(404, "Votepack Not Found")
        }
        context.JSON(200, votePack)
}

然后在main中配置你的控制器:

func main() {
        pCtrl := PublicController { Database: models.NewDB("<connectionstring>") }

        router := gin.New()
        router.GET("/public/current-vote-pack", pCtrl.GetCurrentVotePack)
        router.Run(":1000")
}
wwodge7n

wwodge7n2#

context.Keys中的值都是interface{}类型,因此db将无法调用*DB类型的方法,直到它被转换回该类型。
安全的方法:

db, ok := context.Keys["DB"].(*DB)
if !ok {
        //Handle case of no *DB instance
}
// db is now a *DB value

不太安全的方法,如果context.Keys["DB"]不是*DB类型的值,则会死机:

db := context.Keys["DB"].(*DB)
// db is now a *DB value

Effective Go有一个关于这个的部分。

ruoxqz4g

ruoxqz4g3#

您需要类型Assert来将接口(db:= context.keys [“DB”])转换为有用的内容。例如这篇文章:convert interface{} to int in Golang

9gm1akwq

9gm1akwq4#

还有另一种方法,在启动期间将DB设置为上下文。

db := ctx.MustGet("DB").(*gorm.DB)

如果给定的键存在,MustGet将返回该键的值,否则将死机。

a64a0gku

a64a0gku5#

关于这个主题的优秀文章:Organising Database Access in Go涵盖了许多可能性的优点和缺点。
关于使用上下文传递数据库连接:
[...]request context应该仅用于存储在单个请求周期中创建的值,并且在请求完成后不再需要。它并不真正打算存储长期的处理程序依赖项,如连接池、日志记录器或模板缓存。
也描述了由Jumukasz Marsza提出的解决方案,它非常适合我。“ Package 连接池”可能对某些人更有吸引力,但在我看来,它增加了太多的复杂性(例如:意味着使用模型)-如果这些结构已经存在于您的项目中,这不是问题。

相关问题