Go項目實戰-關于列表分頁的封裝和簡化
我們實現了商品模塊中商品分類相關的功能,這節我們繼續商品模塊的開發來實現商品詳細相關的功能,這些功能在我們梳理出來的功能用例中,我標記了出來。
圖片
從功能用例中我們能看到與商品相關的主要功能有:
- 商品列表
- 商品搜索
- 商品詳情
我們會實現商品模塊的主要功能接口,在其中會實際應用一下我們在搭建項目定制化的響應組件中的Pagination,來簡化分頁查詢相關的操作,在代碼實現上也比普通的方式更優雅一些。
商品列表
接下來我們來實現商品列表功能的接口, 當然真正商用級別的購物App,商品列表應該是通過 Lucene或者是ElasticSearch來實現的查找的。我們這里沒有這個硬件條件,就先給大家講一下通過數據庫查詢實現功能的邏輯吧。
在購物網站上,我們點擊每個分類的時候,會展示分類下的商品列表。
這個時候有個內部邏輯,商品都是掛在到三級分類上的,也就是分類的葉子節點上。 那么此時我們開發功能時要能夠兼顧下面幾點:
- 產品的交互邏輯上可能允許用戶點擊一級或者二級分類查看商品列表。
- 因不同公司產品邏輯而定,沒有絕對,但是我們的功能實現時要支持上面的情況,假設用戶選擇了一級或者二級分類,我們的程序需要先查出下面的三級分類,再通過這些三級分類查找對應的商品。
以上是業務方面的邏輯,在做本功能的時候我還會演示怎么通過我們之前定義分頁組件Pagination,以一個相對優雅的寫法寫數據庫的分頁查詢。
在 api/controller/commodity.go 中添加商品列表的Controller方法。
// CommoditiesInCategory 分類商品列表
func CommoditiesInCategory(c *gin.Context) {
categoryId, _ := strconv.ParseInt(c.Query("category_id"), 10, 64)
pagination := app.NewPagination(c)
svc := appservice.NewCommodityAppSvc(c)
commodityList, err := svc.GetCategoryCommodityList(categoryId, pagination)
if err != nil {
if errors.Is(err, errcode.ErrParams) {
app.NewResponse(c).Error(errcode.ErrParams)
} else {
app.NewResponse(c).Error(errcode.ErrServer.WithCause(err))
}
return
}
app.NewResponse(c).SetPagination(pagination).Success(commodityList)
}
我們在Controller方法中除了從URL查詢字符串上獲取商品的分類ID外,還要獲取分頁相關的請求參數,用它們創建Pagination對象。Pagination 對象會隨著我們的調用一直往下傳遞,傳到DomainService中,在需要的時候通過其上的方法來獲取offset 和 limit 等信息。
DomainService 中查詢商品列表的邏輯如下:
// logic/domainservice/commodity.go
// GetCommodityListInCategory 獲取分類下的商品列表
func (cds *CommodityDomainSvc) GetCommodityListInCategory(categoryInfo *do.CommodityCategory, pagination *app.Pagination) ([]*do.Commodity, error) {
offset := pagination.Offset()
size := pagination.GetPageSize()
thirdLevelCategoryIds, err := cds.commodityDao.GetThirdLevelCategories(categoryInfo)
if err != nil {
returnnil, errcode.Wrap("GetCommodityListInCategoryError", err)
}
commodityModelList, totalRows, err := cds.commodityDao.GetCommoditiesInCategory(thirdLevelCategoryIds, offset, size)
if err != nil {
returnnil, errcode.Wrap("GetCommodityListInCategoryError", err)
}
pagination.SetTotalRows(int(totalRows))
commodityList := make([]*do.Commodity, 0, len(commodityModelList))
err = util.CopyProperties(&commodityList, &commodityModelList)
if err != nil {
returnnil, errcode.ErrCoverData.WithCause(err)
}
return commodityList, nil
}
commodityDao 的 GetThirdLevelCategories 方法就是我們上面說的產品邏輯,拿到一個分類信息后先去獲取一下所有的三級分類。
// GetThirdLevelCategories 查找分類下的所有三級分類ID
func (cd *CommodityDao) GetThirdLevelCategories(categoryInfo *do.CommodityCategory) (categoryIds []int64, err error) {
if categoryInfo.Level == 3 {
return []int64{categoryInfo.ID}, nil
} elseif categoryInfo.Level == 2 {
categoryIds, err = cd.getSubCategoryIdList([]int64{categoryInfo.ID})
return
} elseif categoryInfo.Level == 1 {
var secondCategoryId []int64
secondCategoryId, err = cd.getSubCategoryIdList([]int64{categoryInfo.ID})
if err != nil {
return
}
categoryIds, err = cd.getSubCategoryIdList(secondCategoryId)
return
}
return
}
如果分類本身就是三級分類則直接返回,否則還是按照上面說的邏輯把分類下的所有三級分類先找出來。
分頁查詢簡化
查詢商品信息時因為需要分頁,所以我們在CommodityDomainSvc 里先用Pagination獲取分頁數據需要的offset 和 limit 參數。
func (cds *CommodityDomainSvc) GetCommodityListInCategory(categoryInfo *do.CommodityCategory, pagination *app.Pagination) ([]*do.Commodity, error) {
offset := pagination.Offset()
size := pagination.GetPageSize()
.....
commodityModelList, totalRows, err := cds.commodityDao.GetCommoditiesInCategory(thirdLevelCategoryIds, offset, size)
if err != nil {
return nil, errcode.Wrap("GetCommodityListInCategoryError", err)
}
pagination.SetTotalRows(int(totalRows))
......
}
商品數據查詢的Dao方法除了返回商品列表數據,還會返回滿足條件的總行數,這樣我們把總行數再設置到Pagination對象上,因為Pagination是指針類型,它創建子Controller方法,所以我們在Controller中返回響應時直接使用 app.NewResponse(c).SetPagination(pagination) 就能把分頁查詢需要的信息都寫入到響應中。
通過這種方式我們能避免需要分頁查詢數據的接口對應的AppService、DomainService、Dao方法都帶上page、pageSize等參數,只需要在調用Dao方法查詢數據前從Pagination對象對應的方法中取出這兩個值即可。同理所有方法的返回值中也不用都帶著totalRow 這個返回值。