Java中實(shí)現(xiàn)樹形菜單的兩種方式
一、什么是目錄結(jié)構(gòu)?
就是在實(shí)際開發(fā)過程中,總會(huì)遇到菜單,或則是權(quán)限,這個(gè)時(shí)候就涉及到后端返回?cái)?shù)據(jù)給前端的時(shí)候,不能一個(gè)集合把數(shù)據(jù)一股腦的全部扔給前端,總要把數(shù)據(jù)整理好,做成像書目錄一樣的結(jié)構(gòu)返回給前端。就像以下圖示一樣

二、目錄樹結(jié)構(gòu)實(shí)現(xiàn)寫法
1、準(zhǔn)備階段
①創(chuàng)建數(shù)據(jù)表
PS:如果是練習(xí)可以不用創(chuàng)建數(shù)據(jù)庫,數(shù)據(jù)全部通過java代碼來創(chuàng)建也可以
CREATE TABLE permission_directory ( id int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', parent_id int(11) NOT NULL DEFAULT '0' COMMENT '父目錄ID', menu_name varchar(255) NOT NULL COMMENT '菜單名稱', menu_level int(11) NOT NULL COMMENT '菜單等級', route varchar(255) NOT NULL COMMENT '路由', PRIMARY KEY (id) COMMENT '主鍵', UNIQUE KEY parent_id (parent_id,menu_name,menu_level,route) COMMENT '唯一索引,包含父目錄ID、菜單名稱、菜單等級和路由' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '存儲(chǔ)引擎為InnoDB,字符集為utf8';
②向表中插入數(shù)據(jù)
INSERT INTO permission_directory (parent_id, menu_name, menu_level, route) VALUES (1, '首頁', 0, '/index'), (2, '系統(tǒng)設(shè)置', 0, '/user/manage'), (3, '操作手冊', 0, '/role/manage'), (4, '菜單管理', 2, '/menu/manage'), (5, '用戶管理', 2, '/system/setting'), (6, '日志管理', 3, '/log/manage'), (7, '定時(shí)任務(wù)', 3, '/task/schedule'), (8, 'API接口文檔', 3, '/api/documentation'), (9, '操作手冊', 8, '/operation/manual');
③創(chuàng)建菜單對象PermissionDirectory類
PS:這里我用了@Data注解,就不用封裝屬性了,如果沒寫@Data注解就把每個(gè)屬性封裝以下,也就是get()和set()方法
@Data
public class PermissionDirectory {
@MyAnnotation("主鍵id")
private int id;
@MyAnnotation("父目錄id")
private int parentId;
@MyAnnotation("菜單名稱")
private String menuName;
@MyAnnotation("菜單等級")
private int menuLevel;
@MyAnnotation("路由")
private String route;
}④創(chuàng)建存儲(chǔ)菜單對象PermissionDirectoryResVO類
@Data
public class PermissionDirectoryResVO {
@MyAnnotation("主鍵id")
private Integer id;
@MyAnnotation("父目錄id")
private Integer parentId;
@MyAnnotation("菜單名稱")
private String menuName;
@MyAnnotation("菜單等級")
private Integer menuLevel;
@MyAnnotation("路由")
private String route;
@MyAnnotation("用于存儲(chǔ)當(dāng)前目錄下面的全部子集")
private List<PermissionDirectoryResVO> authMenuList;
}2、邏輯代碼實(shí)現(xiàn)
這里關(guān)于如何去連接數(shù)據(jù)庫啊等等一系列都省略了,關(guān)鍵就是目錄樹的邏輯講解
①第一種寫法
public List<PermissionDirectoryResVO> searchMenu() {
List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
if (CollectionUtil.isNotEmpty(menuList)){
List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> {
PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO);
return permissionDirectoryResVO;
}).collect(Collectors.toList());
pdr.forEach(e ->{
List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
e.setAuthMenuList(pdrList != null ? pdrList : null);
});
List<PermissionDirectoryResVO> parentNodes = pdr.stream().
filter(e -> e.getParentId().equals(0)).collect(Collectors.toList());
directoryTree.addAll(parentNodes);
}
return directoryTree;
}
/**
* 獲取全部子集
* @param id
* @param list
* @return
*/
public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList());
}
}第一種寫法代碼詳細(xì)解
第一步:創(chuàng)建存儲(chǔ)最終結(jié)果數(shù)據(jù)的集合容器
List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
第二步:獲取需要整理成樹狀結(jié)構(gòu)的所有數(shù)據(jù)
List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
PS:這里我是通過查詢數(shù)據(jù)獲取的數(shù)據(jù),練習(xí)的話,可以new一些數(shù)據(jù)出來存入集合中就行了
第三步:判斷獲取的數(shù)據(jù)是否為空,如果為空的話就沒有去整理成樹結(jié)構(gòu)的必要了,數(shù)據(jù)都沒有
if (CollectionUtil.isNotEmpty(menuList)){ .... }
PS:這里我用的是糊涂類提供的方法進(jìn)行判斷,如果小白在寫的過程中發(fā)現(xiàn)報(bào)錯(cuò),找不到這個(gè)方法或則這個(gè)類就換一種寫法
第四步:將獲取的PermissionDirectory數(shù)據(jù)全部賦值給PermissionDirectoryResVO
List<PermissionDirectoryResVO> pdr = menuList.stream().map(PermissionDirectory -> {
PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO);
return permissionDirectoryResVO;
}).collect(Collectors.toList());
具體解釋如下:
menuList.stream():將menuList集合轉(zhuǎn)換為一個(gè)流(Stream)
map(PermissionDirectory -> {...}):這個(gè)簡單理解就是循環(huán)menuList集合,然后遍歷集合中的每一個(gè)PermissionDirectory元素
BeanUtils.copyProperties(PermissionDirectory,permissionDirectoryResVO):將PermissionDirectory對象的屬性值復(fù)制到permissionDirectoryResVO對象中。這樣,authMenuResVO對象就具有了與AuthMenu對象相同的屬性值。
return permissionDirectoryResVO:將轉(zhuǎn)換后的permissionDirectoryResVO對象作為結(jié)果返回給調(diào)用者。
collect(Collectors.toList()):將處理后的流中的元素收集到一個(gè)新的列表中,并返回該列表
因此,這段代碼的作用是將原始列表menuList中的每個(gè)元素轉(zhuǎn)換為AuthMenuResVO類型的對象,并將轉(zhuǎn)換后的對象存儲(chǔ)在一個(gè)新的列表permissionDirectoryResVO中。
第五步:寫一個(gè)獲取子集的方法體
public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
return list.stream().filter(t-> t.getParentId().equals(id)).collect(Collectors.toList());
}
具體解釋如下:
forEach(e -> {...}):是list對象的一個(gè)方法,用于遍歷該列表(或集合)中的每個(gè)元素,并對每個(gè)元素執(zhí)行一段操作。
e -> {...}是一個(gè)Lambda表達(dá)式,表示對每個(gè)元素執(zhí)行的操作,相當(dāng)于e就是PermissionDirectoryResVO元素對象
因此,這段代碼就是通過傳遞一個(gè)主鍵id和一個(gè)PermissionDirectoryResVO集合對象參數(shù),然后遍歷循環(huán)PermissionDirectoryResVO對象集合,把每一個(gè)對象的父目錄id和傳遞過來的參數(shù)id進(jìn)行對比,如果父目錄id等于參數(shù)id就把這個(gè)對象收集到新的集合中,最后作為參數(shù)返回。
第六步:遍歷全部數(shù)據(jù),利用遞歸思想,獲取全部的子集
pdr.forEach(e ->{
List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
e.setAuthMenuList(pdrList != null ? pdrList : null);
});
具體解釋如下:
List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);這一步通過調(diào)用第五步寫好的方法已經(jīng)獲取到了全部子集,就是說,如果所有數(shù)據(jù)一集目錄有三個(gè),分別是1、2、3,那么當(dāng)循環(huán)完的時(shí)候會(huì)有3個(gè)pdrList集合,每個(gè)集合中分別裝有1目錄下的數(shù)據(jù)、2目錄下的數(shù)據(jù)、3目錄下的數(shù)據(jù)。
當(dāng)每一次循環(huán)的時(shí)候,都會(huì)對pdr集合中的元素進(jìn)行一次判斷,e.setAuthMenuList(pdrList != null ? pdrList : null);使用三目運(yùn)算符,如果pdrList集合不為空就表示當(dāng)前元素有子集,然把pdrList集合賦值給元素的authMenuList屬性,如果為空就表示沒有子集,賦值空就可以。
當(dāng)集合遍歷完畢,數(shù)據(jù)情況看圖①實(shí)例
第七步:獲取所有頂點(diǎn)數(shù)據(jù)
List<PermissionDirectoryResVO> parentNodes = pdr.stream().
filter(e -> e.getParentId().equals(0)).collect(Collectors.toList());
directoryTree.addAll(parentNodes);
具體解釋如下:
判斷pdr集合中父目錄id為0的數(shù)據(jù),然后賦值給新的parentNodes,最后把這個(gè)集合存進(jìn)directoryTree集合容器中
圖①
②第二種寫法
public List<PermissionDirectoryResVO> searchMenu() {
List<PermissionDirectoryResVO> directoryTree = new ArrayList<>();
// 獲取全部數(shù)據(jù)
List<PermissionDirectory> menuList = permissionDirectoryMapper.getMenuList();
// 創(chuàng)建存儲(chǔ)PermissionDirectoryResVO對象的集合容器
List<PermissionDirectoryResVO> pdr = new ArrayList<>();
// 判斷集合中數(shù)據(jù)是否為空,不為空進(jìn)行樹結(jié)構(gòu)排列
if (CollectionUtil.isNotEmpty(menuList)){
// 遍歷循環(huán)集合menuList元素賦值給pdr集合中元素對象,這里就是第一種寫法的第四步
for (PermissionDirectory permissionDirectory : menuList){
PermissionDirectoryResVO permissionDirectoryResVO = new PermissionDirectoryResVO();
permissionDirectoryResVO.setId(permissionDirectory.getId());
permissionDirectoryResVO.setParentId(permissionDirectory.getParentId());
permissionDirectoryResVO.setMenuName(permissionDirectory.getMenuName());
permissionDirectoryResVO.setMenuLevel(permissionDirectory.getMenuLevel());
permissionDirectoryResVO.setRoute(permissionDirectory.getRoute());
pdr.add(permissionDirectoryResVO);
}
}
// 遍歷全部數(shù)據(jù),利用遞歸思想,獲取全部的子集,第一種寫法的第六步
for (PermissionDirectoryResVO e : pdr){
List<PermissionDirectoryResVO> pdrList = getChildrenList(e.getId(),pdr);
e.setAuthMenuList(pdrList != null ? pdrList : null);
}
// 獲取所有頂點(diǎn)數(shù)據(jù)
for (PermissionDirectoryResVO e : pdr){
if (e.getParentId().equals(0)){
directoryTree.add(e);
}
}
return directoryTree;
}
/**
* 獲取全部子集
* @param id
* @param list
* @return
*/
public static List<PermissionDirectoryResVO> getChildrenList(Integer id, List<PermissionDirectoryResVO> list){
List<PermissionDirectoryResVO> pdr = new ArrayList<>();
// 這里就是第一種寫法的第五步
for (PermissionDirectoryResVO per : list){
if (per.getParentId().equals(id)){
pdr.add(per);
}
}
return pdr;
}
}最終結(jié)果
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 3,
"parentId": 0,
"menuName": "操作手冊",
"menuLevel": 1,
"route": "/role/manage",
"authMenuList": [
{
"id": 8,
"parentId": 3,
"menuName": "API接口文檔",
"menuLevel": 2,
"route": "/api/documentation",
"authMenuList": [
{
"id": 9,
"parentId": 8,
"menuName": "操作手冊",
"menuLevel": 3,
"route": "/operation/manual",
"authMenuList": []
}
]
},
{
"id": 7,
"parentId": 3,
"menuName": "定時(shí)任務(wù)",
"menuLevel": 2,
"route": "/task/schedule",
"authMenuList": []
},
{
"id": 6,
"parentId": 3,
"menuName": "日志管理",
"menuLevel": 2,
"route": "/log/manage",
"authMenuList": []
}
]
},
{
"id": 2,
"parentId": 0,
"menuName": "系統(tǒng)設(shè)置",
"menuLevel": 1,
"route": "/user/manage",
"authMenuList": [
{
"id": 5,
"parentId": 2,
"menuName": "用戶管理",
"menuLevel": 2,
"route": "/system/setting",
"authMenuList": []
},
{
"id": 4,
"parentId": 2,
"menuName": "菜單管理",
"menuLevel": 2,
"route": "/menu/manage",
"authMenuList": []
}
]
},
{
"id": 1,
"parentId": 0,
"menuName": "首頁",
"menuLevel": 1,
"route": "/index",
"authMenuList": []
}
]
}以上就是Java中實(shí)現(xiàn)樹形菜單的兩種方式的詳細(xì)內(nèi)容,更多關(guān)于Java實(shí)現(xiàn)樹形菜單的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot項(xiàng)目登錄校驗(yàn)功能實(shí)現(xiàn)
本文介紹了Web登錄校驗(yàn)的重要性,對比了Cookie、Session和JWT三種會(huì)話技術(shù),分析其優(yōu)缺點(diǎn),并講解了過濾器與攔截器的統(tǒng)一攔截方案,推薦使用JWT結(jié)合攔截器實(shí)現(xiàn)安全、高效的登錄校驗(yàn),下面通過實(shí)例代碼講解Springboot項(xiàng)目的登錄校驗(yàn)實(shí)現(xiàn),感興趣的朋友一起看看吧2025-08-08
SpringBoot中MybatisX插件的簡單使用教程(圖文)
MybatisX 是一款基于 IDEA 的快速開發(fā)插件,方便在使用mybatis以及mybatis-plus開始時(shí)簡化繁瑣的重復(fù)操作,本文主要介紹了SpringBoot中MybatisX插件的簡單使用教程,感興趣的可以了解一下2023-06-06
SpringBoot獲取ApplicationContext的3種方式
這篇文章主要為大家詳細(xì)介紹了SpringBoot獲取ApplicationContext的3種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
java token生成和校驗(yàn)的實(shí)例代碼
這篇文章主要介紹了java token生成和校驗(yàn)的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
SpringBoot?支付渠道實(shí)現(xiàn)統(tǒng)一的支付服務(wù)示例
本文提出了一種基于工廠模式和策略模式的多渠道支付封裝方案,該設(shè)計(jì)采用策略模式實(shí)現(xiàn)各支付渠道(微信、支付寶、銀聯(lián))的統(tǒng)一接口,通過工廠模式動(dòng)態(tài)創(chuàng)建支付策略,感興趣的可以了解一下2026-03-03
Java快速實(shí)現(xiàn)Word轉(zhuǎn)圖片功能的多種方法與實(shí)踐
在軟件開發(fā)中,轉(zhuǎn)換文件格式是一個(gè)常見需求,尤其是將Word文件轉(zhuǎn)換為圖片格式,這在報(bào)表生成、文檔預(yù)覽、自動(dòng)化處理等場景中非常有用,在 Java 中實(shí)現(xiàn)Word轉(zhuǎn)圖片的功能有多種思路,以下將介紹幾種常見的實(shí)現(xiàn)方式,并探討它們的優(yōu)缺點(diǎn),需要的朋友可以參考下2025-08-08

