需求中经常遇到菜单列表转菜单树的情况,如果知道顶级菜单的话,有工具类可以使用,还是很方便的,hutool有一个TreeUtil类可以帮我们实现此功能,代码如下:
1.menu对象
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "Menu对象", description = "菜单表")
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "上级id")
private String parentId;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "路径")
private String url;
@ApiModelProperty(value = "类型")
private Integer type;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "是否删除")
@TableField("is_deleted")
@TableLogic
private Boolean deleted;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
2.接口需要返回的菜单节点
@ApiModelProperty("菜单")
private List<Tree<String>> menus;
3.调用方法
vo.setMenus(
TreeUtil.build(
menus, // 菜单列表
"", // 顶级菜单的id
(menu, treeMenu) ->{
treeMenu.setId(menu.getId()).setParentId(menu.getParentId()).setName(menu.getName());
treeMenu.putExtra("url", menu.getUrl());
treeMenu.putExtra("type", menu.getType());
}));
如果只有菜单列表,不知道顶级菜单,那要怎么转成菜单树呢?这是本文的重点了。但是很少用到是真的。上代码:
1.首先定义菜单树
@Data
@ApiModel("菜单树DTO")
public class MenuTreeDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "编码")
private String id;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "父级编码")
private String parentId;
@ApiModelProperty(value = "路由")
private String url;
@ApiModelProperty(value = "类型")
private String type;
@ApiModelProperty(value = "子集")
private List<MenuTreeDTO> children;
}
2.转换方法
/**
* 菜单转菜单树
* @param menus
* @return
*/
private static List<MenuTreeDTO> convertMenuTree(List<Menu> menus) {
// 菜单map
Map<String, MenuTreeDTO> menuMap = menus.stream().map(menu -> {
MenuTreeDTO menuTree = BeanUtil.copyProperties(menu, MenuTreeDTO.class);
return menuTree;
}).collect(Collectors.toMap(MenuTreeDTO::getId, Function.identity()));
// 菜单子集map
HashMap<String, List<MenuTreeDTO>> menuChildMap = new HashMap<>(2);
menus.forEach(menu -> {
MenuTreeDTO menuTree = BeanUtil.copyProperties(menu, MenuTreeDTO.class);
// 找到父级节点,若子级已经在父节点了,跳过,否则加入父节点
MenuTreeDTO parentMenu = menuMap.get(menu.getParentId());
if (null != parentMenu) {
if (CollUtil.isNotEmpty(parentMenu.getChildren())) {
if (!getHasExist(parentMenu.getChildren(), menuTree.getId())) {
parentMenu.getChildren().add(menuTree);
}
} else {
parentMenu.setChildren(new ArrayList<MenuTreeDTO>() {{ add(menuTree); }});
}
// 如果父节点是别人的子节点,别人的子节点需要重新设置,这也是上方为什么需要判断子节点是否已经存在
MenuTreeDTO grandMenu = menuMap.get(parentMenu.getParentId());
if (null != grandMenu) {
if (CollUtil.isNotEmpty(grandMenu.getChildren())) {
if (getHasExist(grandMenu.getChildren(), parentMenu.getId())) {
MenuTreeDTO menuTreeDTO = getMenuTree(grandMenu.getChildren(), parentMenu.getId());
menuTreeDTO.setChildren(parentMenu.getChildren());
} else {
grandMenu.getChildren().add(parentMenu);
}
} else {
grandMenu.setChildren(new ArrayList<MenuTreeDTO>() {{ add(parentMenu); }});
}
}
}
List<MenuTreeDTO> menuTrees = menuChildMap.get(menu.getParentId());
if (CollUtil.isNotEmpty(menuTrees)) {
menuTrees.add(menuTree);
} else {
menuChildMap.put(menu.getParentId(), new ArrayList<MenuTreeDTO>() {{ add(menuTree); }});
}
});
// 经过以上处理,有子级的被当成了‘树干’,有的可能是别人的子级,需要从‘树干’上移除掉
Set<Entry<String, List<MenuTreeDTO>>> entrySet = menuChildMap.entrySet();
Iterator<Entry<String, List<MenuTreeDTO>>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry<String, List<MenuTreeDTO>> entry = iterator.next();
MenuTreeDTO menuTree = menuMap.get(entry.getKey());
if (null != menuTree && null != menuMap.get(menuTree.getParentId())) {
iterator.remove();
}
}
// 修剪掉树叶后,循环输出树【特别注意:如果菜单链断裂,比如应该是A->B->C,但是菜单列表只有A、C,A、C会被当成不相关的元素输出】
List<MenuTreeDTO> menuTrees = new ArrayList<>();
menuChildMap.forEach((parendMenuId, menuTreeList) -> {
MenuTreeDTO menuTree = menuMap.get(parendMenuId);
if (null != menuTree) {
menuTrees.add(menuTree);
}
});
return menuTrees;
}
/**
* 获取菜单
* @param menuTrees
* @param menuId
* @return
*/
private static MenuTreeDTO getMenuTree(List<MenuTreeDTO> menuTrees, String menuId) {
MenuTreeDTO result = null;
for(MenuTreeDTO menuTree : menuTrees) {
if (menuId.equals(menuTree.getId())) {
result = menuTree;
break;
}
}
return result;
}
/**
* 判断是否已存在
* @param menuTrees
* @param menuId
* @return
*/
private static boolean getHasExist(List<MenuTreeDTO> menuTrees, String menuId) {
List<String> menuIds = menuTrees.stream().map(MenuTreeDTO::getId).collect(Collectors.toList());
return menuIds.contains(menuId);
}
3.调用转换方法
①.接口出参类型需要修改:
@ApiModelProperty("菜单")
private List<MenuTreeDTO> menus;
②.调用转换方法
vo.setMenus(convertMenuTree(menus));
上面的代码是自己设计,手敲,调试的,肯定有不完善,甚至是错误的地方,欢迎指正。
正式测试的时候,脑袋突然灵光一闪,没必要费那么大劲写这个树转化方法啊,循环菜单列表,递归找菜单的父级,直到顶级,然后调用人家提供好的工具类。我了个法克。