V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Dcynsd
V2EX  ›  Go 编程语言

求助,新手使用 Golang ,在 Gorm 的 Callback 里面怎样获取 gin.Context?

  •  
  •   Dcynsd · 2023-10-11 14:19:17 +08:00 · 1842 次点击
    这是一个创建于 393 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想做的是,每次创建数据,自动插入当前用户 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 赋值给全局变量,请问这样设置有没有什么问题,或者有什么更好的获取方式?

    18 条回复    2023-10-12 14:55:13 +08:00
    lilei2023
        1
    lilei2023  
       2023-10-11 14:28:26 +08:00
    同样是新手,没太理解你这操作
    cloverzrg2
        2
    cloverzrg2  
       2023-10-11 14:28:39 +08:00
    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
    }
    Dcynsd
        3
    Dcynsd  
    OP
       2023-10-11 14:40:34 +08:00
    @cloverzrg2
    我之前有在中间件里面设置,但在 Callback 里面拿到的是 nil

    ```GO
    DB.WithContext(context.WithValue(context.Background(), constants.ContextKey{}, userModel.ID))
    ```

    ```GO
    uid := DB.Statement.Context.Value(constants.ContextKey{})
    ```
    jeffmingup
        4
    jeffmingup  
       2023-10-11 16:52:11 +08:00
    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(c).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")
    }
    我试了一下,这样可以,你对照着看下呢
    jahanngauss414
        5
    jahanngauss414  
       2023-10-11 17:16:22 +08:00
    DB.Callback().Create().Before("gorm:before_create").Register("beforeCreateCallback", beforeCreateCallback(c))

    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)
    }
    }
    }
    Dcynsd
        6
    Dcynsd  
    OP
       2023-10-11 17:24:41 +08:00
    @jeffmingup 这样确实是可以的,但我不可能每个模型都写一个 BeforeCreate 去设置
    Dcynsd
        7
    Dcynsd  
    OP
       2023-10-11 17:27:04 +08:00
    @jahanngauss414 调 beforeCreateCallback 这个方法的 c 没有啊,这个回调是在数据库初始化的时候就注册了,我现在是通过定义一个全局变量保存 gin.Context 来实现,就是不知道后面有没有坑
    jahanngauss414
        8
    jahanngauss414  
       2023-10-11 17:29:32 +08:00
    @Dcynsd #7 你这个上下文全局变量肯定不行啊,请求并发全炸了
    jahanngauss414
        9
    jahanngauss414  
       2023-10-11 17:31:01 +08:00
    @Dcynsd #7 创建数据的时候直接把用户 id set 进去就好了吧,这种全局 callback 看起来不是做这种事情的
    rrfeng
        10
    rrfeng  
       2023-10-11 17:31:59 +08:00
    不能。直接放到 user 对象里不好么
    stiangao
        11
    stiangao  
       2023-10-11 17:56:46 +08:00
    把 gin.Context 赋值给全局变量 !
    WTF???
    Dcynsd
        12
    Dcynsd  
    OP
       2023-10-11 18:06:55 +08:00
    @jahanngauss414 好吧,主要每张表都有创建人 ID
    Dcynsd
        13
    Dcynsd  
    OP
       2023-10-11 18:35:59 +08:00
    查了一下,确实不能放全局,只有重新找其它方式实现
    mshadow
        14
    mshadow  
       2023-10-11 20:51:40 +08:00 via Android
    老老实实一个字段一个字段写,没多大工作量,可维护性高很多。
    langhuishan
        15
    langhuishan  
       2023-10-12 08:57:47 +08:00
    你这里逻辑就有问题,数据库初始化怎么可以和用户 id 绑定呢?用户 id 是数据库查询的条件之一,是具体业务时候添加的条件。
    AnroZ
        16
    AnroZ  
       2023-10-12 14:20:48 +08:00
    多层调用或多模块间传递变量,又不想老老实实写接口参数,投机的方案就是用全局变量。

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

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

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

    这个投机方案,不知道是否对你有用。
    jeffmingup
        17
    jeffmingup  
       2023-10-12 14:48:58 +08:00
    jeffmingup
        18
    jeffmingup  
       2023-10-12 14:55:13 +08:00
    要不设置值用 gin 的 c.Set("Email", "123") 取值可以直接用 db.Statement.Context.("Email").(string) 因为 gin 的 context 是自己的实现的,gin 的 ContextWithFallback 参数默认 false ,会直接返回 nil
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1293 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 17:48 · PVG 01:48 · LAX 09:48 · JFK 12:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.