Golang中如何将中间件的r.Context()传递给数据库查询

Golang中如何将中间件的r.Context()传递给数据库查询 我已经创建了一个中间件。

func ContextTimeOut(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		contex, cancel := context.WithTimeout(r.Context(), 3*time.Second)
		defer cancel()

		next.ServeHTTP(w, r.WithContext(contex))
	})
}

现在我需要将这个上下文用于 db.QueryContext()

func (a *Handler) AddHandler(w http.ResponseWriter, r *http.Request) {
   .... 从 r 获取数据 ....
   ..... 验证数据 ....
    a.repo.Add(r, validatedData)

我必须每次都将其作为参数传递。如果不将其作为函数参数传递,我可以将其设为全局变量吗?我该怎么做?

根据上面的例子,我想在仓库中直接访问它。


更多关于Golang中如何将中间件的r.Context()传递给数据库查询的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我正在使用 chi。 每个处理程序函数都必须传递那个 r.Context()

db, err := database.DatabaseContext()

Repo 负责处理数据库请求。

repo := repo.Repo {
    Db: db,
}

这些代码在 main.go 中。 有没有办法将上下文保存在 repo 中?

更多关于Golang中如何将中间件的r.Context()传递给数据库查询的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


可以将其作为全局变量吗?

不行,它可能会过时,或者您可能需要多个上下文,它可能会从“错误的” Go 协程中被访问。您可以为您需要上下文的处理器定义一个新的接口,该接口接受一个上下文并传递所需的上下文。

也许您的处理器只是获取 r.Context 并将其传递给数据库。

gin 将所有与请求/响应相关的信息放在它自己的上下文中,并定义其处理器只接受一个 gin 上下文。

在Golang中,将中间件的r.Context()传递给数据库查询的正确做法是通过上下文传递,而不是使用全局变量。以下是几种实现方式:

1. 通过函数参数传递(推荐)

这是最直接和类型安全的方式:

// 仓库层接口
type Repository interface {
    Add(ctx context.Context, data ValidatedData) error
}

// 仓库实现
type UserRepository struct {
    db *sql.DB
}

func (r *UserRepository) Add(ctx context.Context, data ValidatedData) error {
    query := `INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id`
    
    // 使用传入的上下文
    err := r.db.QueryRowContext(ctx, query, data.Name, data.Email).Scan(&data.ID)
    if err != nil {
        return fmt.Errorf("insert user: %w", err)
    }
    return nil
}

// 处理器
func (a *Handler) AddHandler(w http.ResponseWriter, r *http.Request) {
    // 从 r 获取数据
    // 验证数据
    
    // 传递上下文到仓库
    err := a.repo.Add(r.Context(), validatedData)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusCreated)
}

2. 使用上下文存储中间件数据

如果需要在上下文中存储额外数据:

// 定义上下文键类型
type contextKey string

const (
    userKey contextKey = "user"
    dbKey   contextKey = "db"
)

// 中间件设置数据
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 模拟获取用户信息
        user := map[string]interface{}{
            "id":    123,
            "name":  "john",
            "email": "john@example.com",
        }
        
        // 将数据存入上下文
        ctx := context.WithValue(r.Context(), userKey, user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 仓库中获取上下文数据
func (r *UserRepository) AddWithUser(ctx context.Context, data ValidatedData) error {
    // 从上下文中获取用户信息
    if user, ok := ctx.Value(userKey).(map[string]interface{}); ok {
        userID := user["id"].(int)
        query := `INSERT INTO users (name, email, created_by) VALUES ($1, $2, $3)`
        _, err := r.db.ExecContext(ctx, query, data.Name, data.Email, userID)
        return err
    }
    
    query := `INSERT INTO users (name, email) VALUES ($1, $2)`
    _, err := r.db.ExecContext(ctx, query, data.Name, data.Email)
    return err
}

3. 避免全局变量的原因

全局变量会导致:

  • 并发安全问题
  • 测试困难
  • 代码耦合度高
  • 难以追踪数据流

4. 完整示例

// main.go
func main() {
    db := setupDatabase()
    repo := NewUserRepository(db)
    handler := NewHandler(repo)
    
    mux := http.NewServeMux()
    mux.HandleFunc("/add", handler.AddHandler)
    
    // 应用中间件
    wrappedMux := ContextTimeOut(mux)
    
    http.ListenAndServe(":8080", wrappedMux)
}

// handler.go
type Handler struct {
    repo Repository
}

func NewHandler(repo Repository) *Handler {
    return &Handler{repo: repo}
}

func (h *Handler) AddHandler(w http.ResponseWriter, r *http.Request) {
    // 解析请求数据
    var data ValidatedData
    if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // 验证数据
    if err := validateData(data); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // 使用上下文调用仓库
    if err := h.repo.Add(r.Context(), data); err != nil {
        // 检查是否为超时错误
        if errors.Is(err, context.DeadlineExceeded) {
            http.Error(w, "request timeout", http.StatusGatewayTimeout)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(map[string]interface{}{
        "status": "success",
        "data":   data,
    })
}

// repository.go
type UserRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) Add(ctx context.Context, data ValidatedData) error {
    // 使用传入的上下文执行查询
    query := `
        INSERT INTO users (name, email, created_at) 
        VALUES ($1, $2, $3) 
        RETURNING id, created_at
    `
    
    row := r.db.QueryRowContext(ctx, query, 
        data.Name, 
        data.Email, 
        time.Now(),
    )
    
    return row.Scan(&data.ID, &data.CreatedAt)
}

通过函数参数传递上下文是最佳实践,它保持了代码的清晰性、可测试性和类型安全性。中间件设置的超时上下文会自动传播到所有使用该上下文的数据库操作中。

回到顶部