Golang Go语言中 新手求助,在 Gorm 的 Callback 里面怎样获取 gin.Context?

发布于 1周前 作者 wuwangju 来自 Go语言

我想做的是,每次创建数据,自动插入当前用户 ID 。

我在中间件里面设置了当前登录用户 ID:

c.Set("uid", 1)

在初始化 Gorm 连接的时候,注册了 Callback 回调:

DB.Callback().Create().Before("gorm:before_create").Register("beforeCreateCallback", beforeCreateCallback)
func beforeCreateCallback(db *gorm.DB) {
	userID := 获取 gin.Context 里的内容
	if _, ok := db.Statement.Schema.FieldsByName["user_id"]; ok {
	    db.Statement.SetColumn("user_id", userID)
	}
}

我想的是定义一个全局变量,在中间件设置完 uid 后,把 gin.Context 赋值给全局变量,请问这样设置有没有什么问题,或者有什么更好的获取方式?


Golang Go语言中 新手求助,在 Gorm 的 Callback 里面怎样获取 gin.Context?

更多关于Golang Go语言中 新手求助,在 Gorm 的 Callback 里面怎样获取 gin.Context?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

19 回复

同样是新手,没太理解你这操作

更多关于Golang Go语言中 新手求助,在 Gorm 的 Callback 里面怎样获取 gin.Context?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


https://gorm.io/docs/context.html#Context-in-Hooks-x2F-Callbacks

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// …
return
}


我之前有在中间件里面设置,但在 Callback 里面拿到的是 nil

GO<br>DB.WithContext(context.WithValue(context.Background(), constants.ContextKey{}, <a target="_blank" href="http://userModel.ID" rel="nofollow noopener">userModel.ID</a>))<br>

GO<br>uid := DB.Statement.Context.Value(constants.ContextKey{})<br>

package main

import (
“log”

github.com/gin-gonic/gin
gorm.io/driver/mysql
gorm.io/gorm
)

type User struct {
gorm.Model
Name string
Email string
}

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
if u.Email == “” {
ctx := tx.Statement.Context
if v, ok := ctx.Value(“Email”).(string); ok {
u.Email = v
}
}
return
}

func main() {
// 初始化 Gin 路由器实例
r := gin.Default()

// 初始化 Gorm 数据库连接
dsn := “root:root@tcp(127.0.0.1:3306)/gorm-example?charset=utf8mb4&parseTime=True&loc=Local”
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(“failed to connect database”)
}

// 自动迁移模型结构体到数据库表
db.AutoMigrate(&User{})

// 创建处理程序函数
createUser := func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{“error”: err.Error()})
return
}
log.Println(“Email:”, c.GetString(“Email”))
db.WithContext©.Create(&user)
c.JSON(200, user)
}

// 自定义中间件,设置 example 变量为 123
r.Use(func(c *gin.Context) {
c.Set(“Email”, “[email protected]”)
c.Next()
})
// 在路由器实例中注册处理程序函数

r.POST("/users", createUser)

// 启动 Gin 服务器,监听 HTTP 请求
r.Run(":8080")
}
我试了一下,这样可以,你对照着看下呢

DB.Callback().Create().Before(“gorm:before_create”).Register(“beforeCreateCallback”, beforeCreateCallback©)

func beforeCreateCallback(c *gin.Context) func(db *gorm.DB) {
return func(db *gorm.DB) {
userID := c.Get(“xxx”)
if _, ok := db.Statement.Schema.FieldsByName[“user_id”]; ok {
db.Statement.SetColumn(“user_id”, userID)
}
}
}

这样确实是可以的,但我不可能每个模型都写一个 BeforeCreate 去设置

调 beforeCreateCallback 这个方法的 c 没有啊,这个回调是在数据库初始化的时候就注册了,我现在是通过定义一个全局变量保存 gin.Context 来实现,就是不知道后面有没有坑

#7 你这个上下文全局变量肯定不行啊,请求并发全炸了

#7 创建数据的时候直接把用户 id set 进去就好了吧,这种全局 callback 看起来不是做这种事情的

不能。直接放到 user 对象里不好么

把 gin.Context 赋值给全局变量 !
WTF???

好吧,主要每张表都有创建人 ID

查了一下,确实不能放全局,只有重新找其它方式实现

老老实实一个字段一个字段写,没多大工作量,可维护性高很多。

你这里逻辑就有问题,数据库初始化怎么可以和用户 id 绑定呢?用户 id 是数据库查询的条件之一,是具体业务时候添加的条件。

多层调用或多模块间传递变量,又不想老老实实写接口参数,投机的方案就是用全局变量。

当然,为了保证调用上下文的一致性,可以根据逻辑作用域不同,把全局变量分成:全局进程变量、全局线程变量、全局协程变量。

这里,全局协程变量,顾名思义,保证同协程内全局访问到同一个变量,又防止多协程间的访问冲突,当协程结束了,对应的全局协程变量也就回收了。

你可以把 gin.Context 赋值给全局协程变量,前提是得保证用到的地方是同协程内调用,注意下这里的回调会不会切换协程了。

这个投机方案,不知道是否对你有用。

要不设置值用 gin 的 c.Set(“Email”, “123”) 取值可以直接用 db.Statement.Context.(“Email”).(string) 因为 gin 的 context 是自己的实现的,gin 的 ContextWithFallback 参数默认 false ,会直接返回 nil

在Golang中使用Gorm时,尝试在Gorm的Callback中获取gin.Context是一个比较常见的需求,但需要注意的是,Gorm的Callback设计初衷是用于处理数据库操作的前后逻辑,而gin.Context则与HTTP请求处理紧密相关。因此,这两者本质上属于不同的处理流程层次。

直接在Gorm Callback中获取gin.Context是不可行的,因为Callback在数据库层面触发,而gin.Context仅在HTTP请求处理中存在。为了将这两者结合起来,你通常需要采取以下策略之一:

  1. 中间件或处理器中传递数据:在HTTP请求处理器中,先将gin.Context中的数据(如用户信息、请求参数等)提取出来,然后通过参数传递给Gorm的操作函数。这些操作函数内部再执行数据库操作时,就可以使用这些数据,而无需直接访问gin.Context

  2. 使用上下文(context.Context):创建一个context.Context,在HTTP请求处理器中将其与gin.Context关联的数据一起传递。然后,在Gorm操作中,通过传递的context.Context来获取所需的数据。

推荐的做法是使用第二种方法,因为context.Context是Go标准库提供的,用于在不同Goroutine之间传递请求范围的值、取消信号以及其他跨API和进程边界的请求作用域数据。这不仅能解决你的问题,还能保持代码的清晰和可维护性。

回到顶部