我在Golang上编写了API,遇到了一个错误.在一次请求服务器返回错误后,SQL数据库被关闭.我想通过上下文传输数据库连接.

玩.

func main() {
    app := fiber.New()

    db, err := sqlx.Connect("pgx", os.Getenv("POSTGRESQL_URL"))

    if err != nil {
        panic(err)
    }

    if err = db.Ping(); err != nil {
        panic(err)
    }

    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)
    db.SetConnMaxIdleTime(5 * time.Minute)

    defer db.Close()

    configure_router.ConfigureRouter(app, db)

    if err = app.Listen(os.Getenv("PORT")); err != nil {
        log.Fatalln(err)
    }
}

CONFigure_router.go

func ConfigureRouter(app *fiber.App, db *sqlx.DB) {
    //Middlewares
    app.Use(logger.New(logger.Config{
        Format: "[${ip}]:${port} ${time} ${status} - ${method} ${path}\n",
    }))

    app.Use(cors.New(cors.Config{
        //AllowOrigins: "http://localhost:3000",
        AllowHeaders: "Origin, Content-Type, Accept",
    }))

    app.Use("/api", func(ctx *fiber.Ctx) error {
        ctx.Context().SetUserValue("dbConn", db)
        return ctx.Next()
    })

    //Authentication endpoints
    app.Post("api/register", register.Register)
    app.Post("api/auth/login", login.Login)
}

Register.go

func Register(ctx *fiber.Ctx) error {
    conn := ctx.Context().UserValue("dbConn").(*sqlx.DB)

    var in In

    if err := ctx.BodyParser(&in); err != nil {
        return make_response.MakeInfoResponse(ctx, fiber.StatusUnprocessableEntity, 1, err.Error())
    }

    if in.Email == "" || in.Password == "" {
        return make_response.MakeInfoResponse(ctx, fiber.StatusBadRequest, 1, "Incorrect data input")
    }

    elementExist := false
    err := conn.Get(&elementExist, "select exists(select email from users where email = $1)", in.Email)

    // Here programm fall in second request
    if err != nil {
        return make_response.MakeInfoResponse(ctx,fiber.StatusInternalServerError, 1, err.Error())
    }


    if elementExist {
        return make_response.MakeInfoResponse(ctx, fiber.StatusBadRequest, 1, "User already registered!")
    }

    passwordHash, err := hash_passwords.HashPassword(in.Password)
    if err != nil {
        return err
    }

    _, err = conn.Exec("insert into users (email, password_hash) values ($1, $2)", in.Email, passwordHash)

    if err != nil {
        return make_response.MakeInfoResponse(ctx, fiber.StatusInternalServerError, 1, err.Error())
    }

    return make_response.MakeInfoResponse(ctx, fiber.StatusOK, 0, "Registration was successful!")
}

如果我在/API/REGISTER中发送请求,并且用户已经在第一个请求中注册,我会收到

要求:

{
  "email": "test@gmail.com",
  "password": "123123123"
}

第一个回应:

{
   "error_code": 0,
   "message": "User already registered!"
}

但如果我想发送另一个请求,我会收到:

{
   "error_code": 1,
   "message": "sql: database is closed",
}

推荐答案

我想通过上下文传输数据库连接.

Don't.这不仅是糟糕的做法,而且实际上是fasthttp.RequestCtx本身在每次请求后关闭您的数据库.上下文应该只包含request specific个值.全局数据库连接几乎不是特定于请求的.

参见SetUserValue号文件,特别是最后一段:

从顶层的RequestHandler返回后,所有值都从CTX中删除.此外,在从CTX中删除值之前,对实现io.Closer的每个值调用Close方法.


一种快速的解决方法是在闭包中捕获数据库:

func Register(db *sqlx.DB) (fn func(*fiber.Ctx) error) {
    return func(ctx *fiber.Ctx) error {
        // ...
        elementExist := false
        err := db.Get(&elementExist, "select exists(select email from users where email = $1)", in.Email)
        // ...
    }
}

// ...

// delete this or comment it out
// app.Use("/api", func(ctx *fiber.Ctx) error {
//    ctx.Context().SetUserValue("dbConn", db)
//    return ctx.Next()
// })

app.Post("api/register", register.Register(db))

Database相关问答推荐

为什么Hibernate会为@JoinTable的双向@OneToMany关系生成一个复杂的子查询?

如何在没有sqlmock的情况下模拟db ping

如何在 ubuntu 中使用脚本添加带有连字符的数据库名称

sql-dump 有什么用?

Ruby on Rails:before_save 字段为小写

将光标中找到的值输出到logcat?

JOIN 三张表

使用 SQLAlchemy Core 批量插入列表值

为什么null不等于null false

在数据库中存储枚举值的最佳方式 - String 或 Int

将所有数据库列设置为 NOT NULL 是一种好习惯吗?

如何在 Rails 中不启动事务的情况下运行迁移?

没有自动增量的sqlalchemy主键

A QuerySet 按聚合字段值

传递依赖有什么问题?

如何使用 JPA 注释创建连接表?

多币种 - 存储什么以及何时转换?

什么是提交日志(log)?

如何将空值传递给外键字段?

从 SQLite 导出到 SQL Server