up:: SpringBoot电商项目商品分类模块后台的分类列表平铺接口

说明:

(1) 本篇博客的核心是就一条:递归查询的解决套路;主要有两点:

● 由于接口文档,要求是返回的数据,需要是递归嵌套的数据;所以,要创建CategoryVO这个bean,去包装查到的数据;以满足接口对返回数据的格式的要求;

● 递归查询的逻辑;(这条尤为重要);

(2) 本篇博客内容其实不难,但可能存在一定的理解难度;静下心来,仔细看,就很容易理解的;


一:先看下接口文档和接口说明

(1)前台的【分类列表(递归)】接口:在前台页面上的显示效果;

可以看到,我们这儿的【商品分类】需要按照层级关系,递归的查询出来;


(2)前台的【分类列表(递归)】接口:接口文档;

因为,这个接口是前台的,所以这个接口不需要是会员(包括管理员)登录;即,这个接口的url是【/category/list】,没有admin;

该接口返回的内容:

{
	"status":  10000,
	    "msg":   "SUCCESS",
	    "data":  [
            {
					"id":  3,
		            "name":   "新鲜水果",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  1,
		            "childCategory":  [
                        {
								"id":  4,
			                    "name":   "橘子橙子",
			                    "type":  2,
			                    "parentId":  3,
			                    "orderNum":  1,
			                    "childCategory":  [
                                    {
											"id":  19,
				                            "name":   "果冻橙",
				                            "type":  3,
				                            "parentId":  4,
				                            "orderNum":  1,
				                            "childCategory":  []
									}
                                ]
						},
                        {
								"id":  11,
			                    "name":   "草莓",
			                    "type":  2,
			                    "parentId":  3,
			                    "orderNum":  2,
			                    "childCategory":  []
						},
                        {
								"id":  12,
			                    "name":   "奇异果",
			                    "type":  2,
			                    "parentId":  3,
			                    "orderNum":  3,
			                    "childCategory":  []
						},
                        {
								"id":  14,
			                    "name":   "车厘子",
			                    "type":  2,
			                    "parentId":  3,
			                    "orderNum":  4,
			                    "childCategory":  []
						},
                        {
								"id":  28,
			                    "name":   "其他水果",
			                    "type":  2,
			                    "parentId":  3,
			                    "orderNum":  4,
			                    "childCategory":  []
						}
                    ]
				},
            	{
					"id":  5,
		            "name":   "海鲜水产",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  2,
		            "childCategory":  [
                        {
								"id":  7,
			                    "name":   "螃蟹",
			                    "type":  2,
			                    "parentId":  5,
			                    "orderNum":  1,
			                    "childCategory":  []
						},
                        {
								"id":  8,
			                    "name":   "鱼类",
			                    "type":  2,
			                    "parentId":  5,
			                    "orderNum":  2,
			                    "childCategory":  []
						},
                        {
								"id":  13,
			                    "name":   "海参",
			                    "type":  2,
			                    "parentId":  5,
			                    "orderNum":  3,
			                    "childCategory":  []
						}
                    ]
				},
            	{
					"id":  6,
		            "name":   "精选肉类",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  3,
		            "childCategory":  [
                        {
								"id":  16,
			                    "name":   "牛羊肉",
			                    "type":  2,
			                    "parentId":  6,
			                    "orderNum":  1,
			                    "childCategory":  []
						}
                    ]
			},
            {
					"id":  9,
		            "name":   "冷饮冻食",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  4,
		            "childCategory":  [
                        {
								"id":  17,
			                    "name":   "冰淇淋",
			                    "type":  2,
			                    "parentId":  9,
			                    "orderNum":  1,
			                    "childCategory":  []
						}
                    ]
				},
            	{
					"id":  10,
		            "name":   "蔬菜蛋品",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  5,
		            "childCategory":  [
                        {
								"id":  18,
			                    "name":   "蔬菜综合",
			                    "type":  2,
			                    "parentId":  10,
			                    "orderNum":  1,
			                    "childCategory":  []
						}
                    ]
			},
            {
					"id":  27,
		            "name":   "美味菌菇",
		            "type":  1,
		            "parentId":  0,
		            "orderNum":  7,
		            "childCategory":  [
                        {
								"id":  15,
			                    "name":   "火锅食材",
			                    "type":  2,
			                    "parentId":  27,
			                    "orderNum":  5,
			                    "childCategory":  []
						}
                    ]
				}
       	 ]
}

接口返回中,data数据分析:

所以,这就要求:我们在查询某个一级目录时,要查查该一级目录下是否有所属的二级目录;然后如果有二级目录的话,要查查该二级目录下是否有所属的三级目录;

这在接口文档中,定义了规范:


(3)接口文档分析:结果;(递归数据的分析;创建,承载递归数据的实体Bean:CategoryVO)

通过上面的分析,可以得到下面的逻辑分析:【接口文档中,规定了后端返回的数据格式】→【其数据格式,明显体现了递归的思想】→【后端为了能够组织出,这样的、充满递归思想的、符合接口要求的、数据格式:需要创建一种充满递归思想的实体Bean】→【因为要符合接口要求,在这个实体Bean中,应需要有个属性是List集合;;然后,该集合中是数据类型也是该实体Bean】→【所以,这儿我们就创建了CategoryVO这个实体Bean】;

CategoryVO:

 
     package com.imooc.mall.model.vo;
 
     import java.util.ArrayList;
     import java.util.Date;
     import java.util.List;
 
     public class CategoryVO {
         private Integer id;
 
         private String name;
 
         private Integer type;
 
         private Integer parentId;
 
         private Integer orderNum;
 
         private Date createTime;
 
         private Date updateTime;
 
         private List<CategoryVO> childCategory = new ArrayList<>();
 
         public List<CategoryVO> getChildCategory() {
             return childCategory;
         }
 
         public void setChildCategory(List<CategoryVO> childCategory) {
             this.childCategory = childCategory;
         }
 
         public Integer getId() {
             return id;
         }
 
         public void setId(Integer id) {
             this.id = id;
         }
 
         public String getName() {
             return name;
         }
 
         public void setName(String name) {
             this.name = name;
         }
 
         public Integer getType() {
             return type;
         }
 
         public void setType(Integer type) {
             this.type = type;
         }
 
         public Integer getParentId() {
             return parentId;
         }
 
         public void setParentId(Integer parentId) {
             this.parentId = parentId;
         }
 
         public Integer getOrderNum() {
             return orderNum;
         }
 
         public void setOrderNum(Integer orderNum) {
             this.orderNum = orderNum;
         }
 
         public Date getCreateTime() {
             return createTime;
         }
 
         public void setCreateTime(Date createTime) {
             this.createTime = createTime;
         }
 
         public Date getUpdateTime() {
             return updateTime;
         }
 
         public void setUpdateTime(Date updateTime) {
             this.updateTime = updateTime;
         }
     }
 

能理解【List<CategoryVO> childCategory】在递归查询中的,层层嵌套所起的作用;很重要;


二:正式开发;

1.在CategoryController类中,创建前台商品分类目录的方法:listCategoryForCustomer()方法;

 
         /**
          * 前台的,分类目录列表
          * @return
          */
         @ApiOperation("前台分类目录列表")
         @GetMapping("/category/list")
         @ResponseBody
         public ApiRestResponse listCategoryForCustomer() {
             List<CategoryVO> categoryVOS = categoryService.listCategoryForCustomer();
             return ApiRestResponse.success(categoryVOS);
         }

说明:

(1) url,请求方式,要符合接口要求;

(2) 因为,这个接口就是查询所有的商品分类数据;即,这个接口的目标是固定且明确的;所以,这个接口不需要参数;

(3) 因为要符合接口的要求:接口的返回结果的ApiRestResponse的data,就是一个List<CategoryVO>;

(4) 有关Service层的listCategoryForCustomer()方法,在下一部分介绍;

2.在CategoryServiceImpl实现类中,编写分页查询的方法listCategoryForCustomer();然后在CategoryService接口中,反向生成方法的声明;

(1)在CategoryServiceImpl实现类中,编写分页查询的方法listCategoryForCustomer();(递归查询:核心,重点!!!)

 
 /**
     * 递归查询得到,分类目录数据;(针对前台的)
     * @return
     */
    @Override
    public List<CategoryVO> listCategoryForCustomer() {
        //定义一个List,这个List就用来存在最终的查询结果;即,这个List中的直接元素是:所有的parent_id=0,即type=1的,第1级别的目录;
        List<CategoryVO> categoryVOList = new ArrayList<CategoryVO>();
 
        //我们额外创建recursivelyFindCategories()方法,去实现递归查询的逻辑;
        //我们第一次递归查询时,是先查一级目录;(而一级目录的parentId是0)
        //该方法第一个参数是:List<CategoryVO> categoryVOList:用来存放当前级别对应的,所有的下一级目录数据;
        //  PS:对于【最终返回给前端的List<CategoryVO> categoryVOList】来说,其所谓的下一级目录就是:所有的parent_id=0,即type=1的,第1级别的目录;
        //  PS:对于【所有的parent_id=0,即type=1的,第1级别的目录;】来说,其categoryVOList就是【List<CategoryVO> childCategory属性】,其是用来存放该级别对应的所有的parent_id=1,即type=2的,第2级别的目录;
        //  PS:对于【所有的parent_id=1,即type=2的,第2级别的目录;】来说,其categoryVOList就是【List<CategoryVO> childCategory属性】,其是用来存放该级别对应的所有的parent_id=2,即type=3的,第3级别的目录;
        //该方法的第二个参数是:当前级别目录的parent_id,即也就是当前级别的上一级目录的id;
        //即,第一个参数是【上一级别的List<CategoryVO> categoryVOList】;第二参数是【下一级别的parent_id,也就是当前级别的id】;
        recursivelyFindCategories(categoryVOList, 0);
        return categoryVOList;
    }
 
    /**
     * 递归查询分类目录数据的,具体逻辑;;;其实就是,递归获取所有目录分类和子目录分类,并组合称为一个“目录树”;
     * @param categoryVOList :存放所有下级别分类目录的数据;
     * @param parentId :某级分类目录的parentId;
     */
    private void recursivelyFindCategories(List<CategoryVO> categoryVOList, Integer parentId) {
        //首先,根据parent_id,查询出所有该级别的数据;(比如,第一次我们查询的是parent_id=0,即type=1的,第1级别的目录)
        List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId);
        //然后,遍历上面查询的该级别的数据;去尝试查询该级别数据的,下一级别的数据;
        if (!CollectionUtils.isEmpty(categoryList)) {
            //遍历所有查到的当前级别数据,把其放在对应上级目录的【List<CategoryVO> categoryVOList】中;
            for (int i = 0; i < categoryList.size(); i++) {
                //获取到【上面查询的,该级别数据中的,一条数据】,把其存储到上级目录的List<CategoryVO> childCategory属性中;
                //自然,如果该级别是【parent_id=0,即type=1的,第1级别的目录】,就是把其存储在最顶级的、返回给前端的那个List<CategoryVO> categoryVOS中;
                Category category =  categoryList.get(i);
                CategoryVO categoryVo = new CategoryVO();
                BeanUtils.copyProperties(category, categoryVo);
                categoryVOList.add(categoryVo);
 
                //然后,这一步是关键:针对【每一个当前级别的,目录数据】去递归调用recursivelyFindCategories()方法;
                //自然,第一个参数是【当前级别数据的,List<CategoryVO> childCategory属性】:这是存放所有下级别目录数据的;
                //第二个参数是【当前级别数据的id】:这自然是下级别目录数据的parent_id:
                recursivelyFindCategories(categoryVo.getChildCategory(), categoryVo.getId());
            }
        }
    }

说明:

(1)这儿并不是很好理解(但其实还好啦),要仔细看注释;主要是递归查询的思想;以及,每个CategoryVO中的【List <CategoryVO> childCategory】是用来存放,所有对应下级目录数据的;

而这种,对应有关联的级别之间的层层包裹,就是我们这儿递归的核心;

(2) Dao层的selectCategoriesByParentId()方法,是根据parent_id查询对应级别分类数据;具体内容,在下一部分介绍;

(3) 这儿的核心是,熟悉【解决递归查询需求的,策略】;


(2)然后在CategoryService接口中,反向生成方法的声明;

3.在CategoryMapper接口中,定义【根据parent_id查询对应级别分类数据】的方法selectCategoriesByParentId();然后在CategoryMapper.xml中编写方法的SQL;

(1)在CategoryMapper接口中,定义【根据parent_id查询对应级别分类数据】的方法selectCategoriesByParentId();

这儿为了更加“完美”,虽然只有一个参数,也使用了@Param注解;


(2)然后在CategoryMapper.xml中编写方法的SQL;

  <select id="selectCategoriesByParentId" parameterType="int" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"></include>
    from imooc_mall_category
    where parent_id = #{parentId}
  </select>

4.启动项目,测试;