携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
今天学习了尚硅谷的谷粒商城项目,其中有一个实现三级菜单的功能,我将实现思路记录下来,请大家指教。我采用的持久层框架是mybatis-plus。
1.什么是三级菜单
如上图所示就是我说的三级菜单。我们获取上图里面的数据呢?
2.三级菜单的数据库设计
我们三级菜单里面的数据都是从数据库里面获取的,所以我们需要设计一张合理的数据库表来存放这些内容。
DROP TABLE IF EXISTS `pms_category`;
CREATE TABLE `pms_category` (
`cat_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id',
`name` char(50) DEFAULT NULL COMMENT '分类名称',
`parent_cid` bigint(20) DEFAULT NULL COMMENT '父分类id',
`cat_level` int(11) DEFAULT NULL COMMENT '层级',
`show_status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`icon` char(255) DEFAULT NULL COMMENT '图标地址',
`product_unit` char(50) DEFAULT NULL COMMENT '计量单位',
`product_count` int(11) DEFAULT NULL COMMENT '商品数量',
PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';
简单介绍下这张表格。 无论数据是几级分类的数据,都是符合这张表的。我们这张表里面有个parent_cid,如果数据的parent_cid是0,代表这条数据是一级菜单里面的数据。cat_id除了是主键外,它还有另一个作用,如果另一条数据的parent_cid等于本条数据的cat_id,那么说明另一条数据是本条数据的子菜单。sort的作用就是排序。
3.实体类设计
下面的代码是分类数据库表对应的实体类。下面的实体类新添加了一个字段private List children,原因是后面我们会把查询到的数据组成一个树状结果,即一级菜单的数据包含二级菜单的数据,二级菜单的数据包含三级菜单的数据,因为无论你是几级菜单的数据,都可以用到这个实体类,所以就这样写。@TableField(exist = false) 注解加载bean属性上,表示当前属性不是数据库的字段。
/**
* 商品三级分类
*
* @author wzj
* @email wzj@gmail.com
* @date 2022-06-26 11:03:23
*/
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分类id
*/
@TableId
private Long catId;
/**
* 分类名称
*/
private String name;
/**
* 父分类id
*/
private Long parentCid;
/**
* 层级
*/
private Integer catLevel;
/**
* 是否显示[0-不显示,1显示]
*/
private Integer showStatus;
/**
* 排序
*/
private Integer sort;
/**
* 图标地址
*/
private String icon;
/**
* 计量单位
*/
private String productUnit;
/**
* 商品数量
*/
private Integer productCount;
@TableField(exist = false)
private List<CategoryEntity> children;
}
4.业务代码实现
4.1 controller层
这里的代码较为简单,其实就是说明下你要去那里查询数据
@Autowired
private CategoryService categoryService;
/**
* 查出所有分类及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
小tips:你可以直接用注入进来的categoryService去调用listWithTree(),这个方法是将数据组成树状结果的方法。问题来了,你这方法不是还没有吗?q(≧▽≦q)我们可以先这样写,接着让idea帮我们创建。
4.2 service层
经过上面的步骤,我们service层接口的代码如下:
/**
* 商品三级分类
*
* @author wzj
* @email wzj@gmail.com
* @date 2022-06-26 11:03:23
*/
public interface CategoryService extends IService<CategoryEntity> {
PageUtils queryPage(Map<String, Object> params);
List<CategoryEntity> listWithTree();
}
service层接口的实现类,才是实现我们数据组装的地方
@Override
public List<CategoryEntity> listWithTree() {
//1.查出所有分类
List<CategoryEntity> entities = baseMapper.selectList(null);
//2.组装成父子的树形结构
//2.1 找到所有1级分类
List<CategoryEntity> level1Menus = entities.stream()
.filter(categoryEntity ->
categoryEntity.getParentCid() == 0)
.map((menu) ->{
menu.setChildren(getChildren(menu,entities));
return menu;
})
.sorted((menu1,menu2) ->{
return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
})
.collect(Collectors.toList());
return level1Menus;
}
//递归查找所有菜单的子菜单
private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){
List<CategoryEntity> children = all.stream()
.filter(categoryEntity -> {
return categoryEntity.getParentCid() == root.getCatId();
})
.map(categoryEntity ->{
//1.找到子菜单
categoryEntity.setChildren(getChildren(categoryEntity,all));
return categoryEntity;
})
.sorted((menu1,menu2) ->{
//2.菜单的排序
return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
})
.collect(Collectors.toList());
return children;
}
这里我记录下service层实现类代码的实现思路。
1.在listWithTree方法里我们先将所有数据查询出来
2.将查询出来的数据通过stream里的filter方法过滤下,过滤条件是parentCid == 0即一级菜单的数据
3.将过滤出来的数据通过stream里的map方法重新添加到流里
3.1我们在map方法里要将查询到的子菜单封装到当前菜单里面,在返回到流里
3.2所以我们在map里面采用递归的方式,不断查询当前菜单是否有子菜单。为此我们又写了一个方法getChildren来递归查询子菜单。
4.将处理后的数据通过stream里的collect方法重新生成集合并返回
有些朋友看到这里,可能会有如下问题
1.map里的setChildren方法哪里来的?还记得实体类设置的那个字段吗?在文章开头有提到 2.递归的结束条件是什么?确实,写成了stream可读性下降了不少,你可以看下filter,这个就是过滤的条件,如果没有符合过滤条件的子菜单了,那么递归到了这里就结束了。3.为什么排序要这样写menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort()?原因是sort可能是空的。