java 菜单列表转菜单树

需求中经常遇到菜单列表转菜单树的情况,如果知道顶级菜单的话,有工具类可以使用,还是很方便的,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));

上面的代码是自己设计,手敲,调试的,肯定有不完善,甚至是错误的地方,欢迎指正。

 

正式测试的时候,脑袋突然灵光一闪,没必要费那么大劲写这个树转化方法啊,循环菜单列表,递归找菜单的父级,直到顶级,然后调用人家提供好的工具类。我了个法克。