上节课我们实现了商品模块中商品分类相关的功能,这节我们继续商品模块的开发来实现商品详细相关的功能,这些功能在我们梳理出来的功能用例中,我用标记了出来。
从功能用例中我们能看到与商品相关的主要功能有:
我们会实现商品模块的主要功能接口,在其中会实际应用一下我们在搭建项目定制化的响应组件中的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 这个返回值。
比如加入项目访问https://github.com/go-study-lab/go-mall/compare/c14.1...c14.2能查看本节的详细代码更新。