使用 gorm-Scopes 函數複用查詢邏輯

今天要學習的是gorm.Scopes函數的使用。該函數的作用就是複用查詢條件。

gorm Scopes 是什麼

在項目中,你一定會遇到過很多需要複用的查詢條件。比如常用的場景有分頁、查詢時判定數據權限等操作。

比如,我們有兩個數據資源:用戶列表和部門列表。那麼,在查詢列表的時候都會涉及到分頁。當然可以在每個列表中都增加上列表相關的查詢。同時,也可以將分頁的查詢抽取出來,做成公共的函數。

那怎麼將抽取出來的分頁條件在每個列表中都能複用呢?那就是使用gorm.Scopes函數。

我們先看一個使用 gorm.Scopes 函數使用的簡單例子,這個例子只是爲了說明gorm.Scopes函數的使用。如下:

func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?""C")
}

func PaidWithCod(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?""C")
}

func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    return db.Where("status IN (?)", status)
  }
}

// 查找所有金額大於 1000 的信用卡訂單
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)

// 查找所有金額大於 1000 的 COD 訂單
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)

// 查找所有金額大於1000 的已付款或已發貨訂單
db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid""shipped"})).Find(&orders)

通過上述例子可以知道,前 4 個函數都是func (db *gorm.DB) *gorm.DB 類型的函數,即輸入一個 db,然後返回一個 db。在該函數中的業務邏輯其實就是最常見的db.Wheredb.Offset等常用的查詢條件語句而已。只不過是對這種公共的查詢語句進行了提取並進行復用而已。

然後將這樣的函數傳遞給ScopesScopes函數只是簡單的將func (db *gorm.DB) *gorm.DB放到Statement.scopes這個切片中。

最後,在最終執行的時候,會循環遍歷Statement.scopes切片,依次執行該切片中的每一個func (db *gorm.DB) *gorm.DB函數。這樣,就把提取出來的公共的查詢條件融合在一起了。

使用場景 1 -- 分頁

當然,我們在查詢時最常用的就是分頁功能。那麼,如何使用gorm.Scopes實現分頁查詢的複用呢。如下:

func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    q := r.URL.Query()
    page, _ := strconv.Atoi(q.Get("page"))
    if page <= 0 {
      page = 1
    }

    pageSize, _ := strconv.Atoi(q.Get("page_size"))
    switch {
    case pageSize > 100:
      pageSize = 100
    case pageSize <= 0:
      pageSize = 10
    }

    offset := (page - 1) * pageSize
    return db.Offset(offset).Limit(pageSize)
  }
}

db.Scopes(Paginate(r)).Find(&users)
db.Scopes(Paginate(r)).Find(&articles)

你看,先定義了一個分頁的函數Paginate函數,該函數接收一個*http.Request參數,然後返回一個func(db *gorm.DB) *gorm.DB的函數。因爲gorm.Scopes函數只接受func(db *gorm.DB) *gorm.DB類型的函數。最後,將Paginate函數傳遞給Scopes函數即可。

使用場景 2 -- 數據權限

在 go-admin 開源項目中,我們還發現了一個典型的應用,就是數據權限。在我們的系統中,會遇到這樣的場景:一些數據只能自己查看或操作;或者你的上級也能查看或操作;或者同部門的人員能查看或操作自己部門的數據,但不能查看或操作其他部門的權限;又或者只能查看同部門的數據但不能操作同部門的數據等等。

在 go-admin 中,就使用了gorm.Scopes函數來統一使用權限查詢條件。在每個操作中,都通過Scopes函數傳入了一個Permission函數。Permissioin函數是根據不同角色擁有的權限,轉換成對應的 sql 語句。如下:

// DeleteAction 通用刪除動作
func DeleteAction(control dto.Control) gin.HandlerFunc {
 return func(c *gin.Context) {
  db, err := pkg.GetOrm(c)

        ...//省略了一些邏輯
        
  //數據權限檢查
  p := GetPermissionFromContext(c)

        // 將Permission函數傳入Scopes函數
  db = db.WithContext(c).Scopes(
   Permission(object.TableName(), p),
  ).Where(req.GetId()).Delete(object)

        ...//省略了一些邏輯
        
        // 檢查操作的行數,如果操作的行數是0,說明沒有權限
  if db.RowsAffected == 0 {
   response.Error(c, http.StatusForbidden, nil, "無權刪除該數據")
   return
  }
  response.OK(c, object.GetId()"刪除成功")
  c.Next()
 }
}

// Permission函數的邏輯
// 根據不同 的數據範圍枚舉值,轉換成不同的Where條件
func Permission(tableName string, p *DataPermission) func(db *gorm.DB) *gorm.DB {
 return func(db *gorm.DB) *gorm.DB {
  if !config.ApplicationConfig.EnableDP {
   return db
  }
  switch p.DataScope {
  case "2":
   return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", p.RoleId)
  case "3":
   return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", p.DeptId)
  case "4":
   return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))""%/"+pkg.IntToString(p.DeptId)+"/%")
  case "5":
   return db.Where(tableName+".create_by = ?", p.UserId)
  default:
   return db
  }
 }
}

總結

gorm Scopes 是一個非常強大的特性,它可以讓你複用你的邏輯,在查詢時實現更爲複雜的查詢邏輯。在使用 gorm Scope 時,你需要定義一個 Scope 函數,並在查詢時應用它。Scope 函數可以被鏈式調用,並且可以接收參數。學習並掌握這個特性將會使你在編寫 gorm 查詢時事半功倍。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BihVWnkjAyo8G8Md3Mlkew