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 · 199 天前 · 1600 次点击
    这是一个创建于 199 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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