分类功能(构建三级分类树)

分类功能实现逻辑

项目使用固定的三级分类

1.从数据库中一次性全查出所有分类信息

2.构建分类信息的父子结构,实现查询返回父子结构的分类信息

3.将查询到的结果保存在Redis中,后续用户可以直接获取

业务分析

查询全部分类的业务重点在构建三级分类树结构

需要将从数据库中查询出的分类对象构成如下结构

```json
[
    {id:1,name:"手机/运行商/数码",parentId:0,depth:1,children:[
        {id:2,name:"手机通讯",parentId:1,depth:2,children:[
            {id:3,name:"智能手机",parentId:2,depth:3,children:null},
            {id:4,name:"非智能手机",parentId:2,depth:3,children:null}
        ]},
    ]},
    {id:5,name:"电脑/办公",parentId:0,depth:1,children:[....]}
]

这就是我们需要获得的对象的结构

在数据库mall_pms中找到我们保存全部分类信息的表pms_category

分析表信息如下

id: 主键

name: 显示在页面上的分类名称

parentId: 父分类的id(如果是一级分类父分类id为0)

depth: 分类深度,当前项目为3级分类,1\2\3 分别代表它的等级

keyword: 搜索关键字

sort: 排序依据 正常查询时,根据此进行排序,数字越小越出现在前面(升序)

icon: 图标地址

enable: 是否可用

isparent: 是否为父分类

isdisplay: 是否显示在导航栏

实施开发

在前台项目csmall-front-webapi中开发

创建service.impl包,在包中编写业务逻辑实现类,实现IFrontCategoryService

```java
@DubboService
@Service
@Slf4j
public class FrontCategoryServiceImpl implements IFrontCategoryService {

    // 装配Dubbo业务逻辑层对象,完成Product模块查询全部分类对象集合的方法
    // front模块不连数据,是消费者
    @DubboReference
    private IForFrontCategoryService dubboCategoryService;
    // 装配操作Redis的对象
    @Autowired
    private RedisTemplate redisTemplate;

    // 开发过程中,使用Redis的规范要求所有代码中使用的Redis的Key,都要定义为常量避免拼写错误
    public static final String CATEGORY_TREE_KEY="category_tree";

    @Override
    public FrontCategoryTreeVO categoryTree() {
        // 方法中先检查Redis中是否保存了三级分类树对象
        if(redisTemplate.hasKey(CATEGORY_TREE_KEY)){
            // redis中如果已经保存了这个key,直接获取
            FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                    (FrontCategoryTreeVO<FrontCategoryEntity>)
                    redisTemplate.boundValueOps(CATEGORY_TREE_KEY).get();
            // 将从redis中获取的treeVO返回
            return treeVO;
        }
        // Redis中没有三级分类树信息,表示本次访问可以是首次访问
        // 就要进行连接数据库查询数据后,构建三级分类树结构,再保存到Redis的业务流程
        // dubbo调用查询所有分类对象的方法
        List<CategoryStandardVO> categoryStandardVOs=
                            dubboCategoryService.getCategoryList();
        // 请记住CategoryStandardVO是没有children属性的,FrontCategoryEntity是有的!
        // 下面就是要编写一个方法,将子分类对象保存到对应的父分类对象的children属性中
        // 所有大概思路就是将CategoryStandardVO转换为FrontCategoryEntity
        // 转换和构建过程比较复杂,我们专门编写一个方法来完成
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                                            initTree(categoryStandardVOs);
        // 上面方法,完成了三级分类树的构建,下面要将treeVO保存到Redis
        redisTemplate.boundValueOps(CATEGORY_TREE_KEY)
                .set(treeVO,1, TimeUnit.MINUTES);
        // 上面时间定义了1分钟,是学习测试比较适合的,实际开发中可能会保存较长时间例如24小时
        // 最后别忘了返回!!!!
        return treeVO;
    }

    private FrontCategoryTreeVO<FrontCategoryEntity> initTree(
                            List<CategoryStandardVO> categoryStandardVOs) {
        // 第一步:
        // 确定所有分类的父分类id
        // 以父分类id为Key,以子分类对象为value保存在一个Map中
        // 一个父分类可以包含多个子分类对象,所以这个Map的value是个List
        Map<Long,List<FrontCategoryEntity>> map=new HashMap<>();
        log.info("当前分类对象总数量:{}",categoryStandardVOs.size());
        // 遍历数据库查询出来的所有分类对象集合
        for(CategoryStandardVO categoryStandardVO : categoryStandardVOs){
            // 因为CategoryStandardVO没有children属性不能保存子分类对象
            // 所以要将CategoryStandardVO对象转换为能够保存children属性的FrontCategoryEntity
            FrontCategoryEntity frontCategoryEntity=new FrontCategoryEntity();
            // 同名属性赋值
            BeanUtils.copyProperties(categoryStandardVO,frontCategoryEntity);
            // 获取当前分类对象的父分类id,用作Map中的Key(如果父分类id为0,表示一级分类)
            // 将父分类id取出,以便后续使用
            Long parentId=frontCategoryEntity.getParentId();
            // 要判断这个父分类id作为Key是否已经在map中出现
            if(map.containsKey(parentId)){
                // 如果当前map已经存在这个key,直接将当前分类对象添加到value的集合中即可
                map.get(parentId).add(frontCategoryEntity);
            }else{
                // 如果当前map没有这个key,那么我们就要创建这个key-value
                // 要先实例化一个List对象,作为value
                List<FrontCategoryEntity> value=new ArrayList<>();
                value.add(frontCategoryEntity);
                // 最后再将这个包含分类对象的list添加到value中
                map.put(parentId,value);
            }
        }
        // 第二步:
        // 将子分类对象关联到对应的父分类对象的children属性中
        // 先获的所有一级分类对象, 也就是父分类id为0的对象
        List<FrontCategoryEntity> firstLevels=map.get(0L);
        // 判断一级分类集合如果为null,直接抛出异常,终止程序
        if(firstLevels==null || firstLevels.isEmpty()){
            throw new CoolSharkServiceException(
                    ResponseCode.INTERNAL_SERVER_ERROR,"缺失一级分类对象!");
        }
        // 遍历一级分类集合
        for(FrontCategoryEntity oneLevel : firstLevels){
            // 获取当前一级分类对象的id
            Long secondLevelParentId=oneLevel.getId(); // getId!!!!!!!!!!
            // 根据上面一级分类的id,获得对应的二级分类集合
            List<FrontCategoryEntity> secondLevels=map.get(secondLevelParentId);
            if(secondLevels==null || secondLevels.isEmpty()){
                // 二级分类缺失不用抛异常,报出警告即可
                log.warn("当前分类没有二级分类内容:{}",secondLevelParentId);
                // 跳过本次循环,继续下次循环
                continue;
            }
            // 确定二级分类对象后,遍历二级分类对象集合
            for(FrontCategoryEntity twoLevel : secondLevels){
                // 获取当前二级分类的id(三级分类的父id)
                Long thirdLevelParentId=twoLevel.getId();  //getId!!!!!!!!
                // 根据二级分类的id获取对应的三级分类对象集合
                List<FrontCategoryEntity> thirdLevels=map.get(thirdLevelParentId);
                // 判断三级分类对象集合是否为null
                if(thirdLevels==null || thirdLevels.isEmpty()){
                    log.warn("当前二级分类对象没有三级分类内容:{}",thirdLevelParentId);
                    continue;
                }
                // 将三级分类对象集合,添加到当前二级分类对象的children属性中
                twoLevel.setChildrens(thirdLevels);
            }
            // 将二级分类对象集合(已经赋好值的对象集合),添加到一级分类对象的children属性中
            oneLevel.setChildrens(secondLevels);
        }
        // 到此为止,所有的分类对象,都应该正确保存到了自己对应的父分类对象的children属性中
        // 但是最后要将一级分类的集合firstLevels,赋值给FrontCategoryTreeVO<FrontCategoryEntity>
        // 所以要先实例化它,再给它赋值,返回
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                new FrontCategoryTreeVO<>();
        treeVO.setCategories(firstLevels);
        // 最后千万别忘了返回!!!!
        return treeVO;
    }
}
```

创建控制层

controller包,CategoryController类

代码: 

```java
@RestController
@RequestMapping("/front/category")
@Api(tags = "前台分类查询")
public class CategoryController {
    @Autowired
    private IFrontCategoryService categoryService;

    @GetMapping("/all")
    @ApiOperation("查询获得三级分类树对象")
    public JsonResult<FrontCategoryTreeVO<FrontCategoryEntity>> getTreeVO(){
        FrontCategoryTreeVO<FrontCategoryEntity> treeVO=
                categoryService.categoryTree();
        return JsonResult.ok(treeVO);
    }


}
```

测试

启动nacos、seata、redis

先启动生产者product\后启动消费者front

访问对应的knife4j文档进行测试即可

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿码德乌斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值