智慧社區項目開發(三)——基于 Spring Boot 實現動態路由加載:從數據庫到前端菜單的完整方案

在后臺管理系統中,不同用戶角色往往擁有不同的操作權限,對應的菜單展示也需動態調整。動態路由加載正是解決這一問題的核心方案 —— 根據登錄用戶的權限,從數據庫查詢其可訪問的菜單,封裝成前端所需的路由結構并返回。本文將詳細講解如何基于 Spring Boot + MyBatis-Plus 實現這一功能,包含完整代碼與實現思路。

一、需求與實現思路

動態路由加載的核心目標是:根據登錄用戶的權限,動態生成其可訪問的菜單路由,最終返回給前端用于渲染側邊欄。整體實現思路分為四步:

  1. 獲取當前登錄用戶信息:通過 Session 獲取已登錄用戶的 ID(userId);
  2. 查詢用戶角色名稱:基于 userId,通過user_role表(用戶 - 角色關聯)和role表(角色表)聯查,獲取用戶的角色名稱(如 “超級管理員”);
  3. 查詢用戶權限菜單:基于 userId,通過user_rolerole_menu(角色 - 菜單關聯)、menu(菜單表)三表聯查,獲取用戶可訪問的所有菜單;
  4. 封裝路由結構:將數據庫查詢的菜單列表,轉換為前端所需的路由格式(包含一級菜單、二級菜單、路由元信息等)。

二、核心表結構設計

實現動態路由的前提是合理的表結構設計,需包含 3 張核心表(用戶 - 角色 - 菜單的關聯關系):

  • user:用戶表(存儲用戶 ID、用戶名等);
  • role:角色表(存儲角色 ID、角色名稱,如 “超級管理員”);
  • menu:菜單表(存儲菜單 ID、父級 ID、路徑、組件路徑等路由信息);
  • user_role:用戶 - 角色關聯表(多對多關系);
  • role_menu:角色 - 菜單關聯表(多對多關系)。

其中,menu表的核心字段如下(與代碼對應):

字段名含義說明示例值
menu_id菜單 ID(主鍵)1
parent_id父級菜單 ID(0 表示一級菜單)0
name菜單名稱(用于前端顯示)"系統管理"
path路由路徑"/sys"
component前端組件路徑"Layout"
icon菜單圖標(前端顯示)"system"
hidden是否隱藏("true"/"false")"false"
sort排序號(控制菜單展示順序)1

三、VO 類設計(適配前端路由格式)

前端路由通常需要包含菜單名稱、路徑、組件、圖標等信息,且需區分一級菜單和子菜單。因此,我們設計以下 VO(View Object)類封裝路由數據:

1. MenuRouterVO(一級菜單路由)

@Data
public class MenuRouterVO {private String name;       // 菜單名稱private String path;       // 路由路徑private String component;  // 前端組件路徑private String hidden;     // 是否隱藏("true"/"false")private String redirect = "noRedirect";  // 重定向路徑(默認無)private Boolean alwaysShow = true;       // 是否總是顯示(一級菜單通常為true)private MetaVO meta;       // 路由元信息(包含標題、圖標)private List<ChildMenuRouterVO> children;  // 子菜單列表
}

2. ChildMenuRouterVO(二級菜單路由)

@Data
public class ChildMenuRouterVO {private String name;       // 子菜單名稱private String path;       // 子菜單路徑private String component;  // 子菜單組件路徑private String hidden;     // 是否隱藏private MetaVO meta;       // 子菜單元信息
}

3. MetaVO(路由元信息)

用于存儲前端渲染所需的標題和圖標:

@Data
public class MetaVO {private String title;  // 菜單標題(顯示在側邊欄)private String icon;   // 菜單圖標(如"system")
}

四、核心代碼實現

1. 控制器:處理動態路由請求(Controller)

控制器的作用是接收前端請求,協調獲取用戶信息、角色、菜單,并封裝返回結果。

@RestController
@RequestMapping("/sys/user")
public class UserController {@Autowiredprivate RoleMapper roleMapper;@Autowiredprivate MenuService menuService;/*** 加載動態路由:返回用戶信息、角色、可訪問菜單路由*/@GetMapping("/getRouters")public Result getRouters(HttpSession session) {// 1. 從Session獲取當前登錄用戶(登錄時已存入Session)User user = (User) session.getAttribute("user");if (user == null) {return Result.error("用戶未登錄");}// 2. 根據userId查詢角色名稱(如"超級管理員")String roleName = roleMapper.getRoleNameByUserId(user.getUserId());// 3. 根據userId查詢并封裝用戶可訪問的菜單路由List<MenuRouterVO> routers = menuService.getMenuRouterByUserId(user.getUserId());// 4. 封裝結果返回(用戶信息、角色、路由)return Result.ok().put("data", user)       // 用戶基本信息.put("roles", roleName)  // 角色名稱.put("routers", routers); // 動態路由列表}
}

2. 角色查詢:獲取用戶角色名稱(RoleMapper)

通過user_role表關聯role表,根據 userId 查詢角色名稱:

@Repository
public interface RoleMapper extends BaseMapper<Role> {/*** 根據userId查詢角色名稱* 聯表邏輯:user_role(用戶-角色關聯) → role(角色表)*/@Select("SELECT role_name FROM role, user_role " +"WHERE user_role.role_id = role.role_id " +"AND user_role.user_id = #{userId}")String getRoleNameByUserId(Integer userId);
}

說明:若用戶擁有多個角色,可修改 SQL 為GROUP_CONCAT(role_name)并返回字符串(如 “管理員,編輯”)。

3. 菜單查詢:獲取用戶權限菜單(MenuMapper)

通過user_rolerole_menumenu三表聯查,獲取用戶可訪問的所有菜單:

@Repository
public interface MenuMapper extends BaseMapper<Menu> {/*** 根據userId查詢可訪問的菜單列表* 聯表邏輯:user_role → role_menu → menu*/@Select({"SELECT m.menu_id, m.parent_id, m.name, m.path, m.component, " +"m.icon, m.hidden, m.sort " +"FROM user_role ur, role_menu rm, menu m " +"WHERE ur.role_id = rm.role_id " +"AND rm.menu_id = m.menu_id " +"AND ur.user_id = #{userId} " +"ORDER BY m.sort"  // 按sort排序,保證菜單展示順序})List<Menu> getMenusByUserId(Integer userId);
}

說明:查詢結果包含菜單的 ID、父級 ID、路徑等核心信息,后續將轉換為路由 VO。

4. 菜單服務:封裝路由結構(MenuService)

Service 層的核心是將數據庫查詢的Menu列表轉換為前端所需的MenuRouterVO列表,實現步驟:

  1. 從數據庫查詢用戶可訪問的所有菜單(menuList);
  2. 篩選一級菜單(parent_id = 0);
  3. 為每個一級菜單封裝MenuRouterVO屬性(名稱、路徑、組件等);
  4. 為每個一級菜單匹配子菜單(parent_id = 一級菜單ID),封裝為ChildMenuRouterVO
  5. 組合一級菜單與子菜單,返回最終路由列表。
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {@Autowiredprivate MenuMapper menuMapper;@Overridepublic List<MenuRouterVO> getMenuRouterByUserId(Integer userId) {// 1. 查詢用戶可訪問的所有菜單List<Menu> menuList = menuMapper.getMenusByUserId(userId);// 2. 存儲最終的路由列表(一級菜單)List<MenuRouterVO> routerList = new ArrayList<>();// 3. 遍歷菜單列表,篩選一級菜單并封裝for (Menu menu : menuList) {// 一級菜單:parent_id = 0if (menu.getParentId() == 0) {MenuRouterVO parentRouter = new MenuRouterVO();// 封裝一級菜單基本屬性parentRouter.setName(menu.getName());parentRouter.setPath(menu.getPath());parentRouter.setComponent(menu.getComponent());parentRouter.setHidden(menu.getHidden());parentRouter.setRedirect("noRedirect"); // 固定值(前端要求)parentRouter.setAlwaysShow(true);       // 總是顯示一級菜單// 封裝元信息(標題、圖標,用于前端渲染)MetaVO parentMeta = new MetaVO();parentMeta.setTitle(menu.getName());parentMeta.setIcon(menu.getIcon());parentRouter.setMeta(parentMeta);// 4. 為當前一級菜單匹配子菜單List<ChildMenuRouterVO> children = new ArrayList<>();for (Menu childMenu : menuList) {// 子菜單:parent_id = 一級菜單IDif (childMenu.getParentId().equals(menu.getMenuId())) {ChildMenuRouterVO childRouter = new ChildMenuRouterVO();// 封裝子菜單屬性childRouter.setName(childMenu.getName());childRouter.setPath(childMenu.getPath());childRouter.setComponent(childMenu.getComponent());childRouter.setHidden(childMenu.getHidden());// 子菜單元信息MetaVO childMeta = new MetaVO();childMeta.setTitle(childMenu.getName());childMeta.setIcon(childMenu.getIcon());childRouter.setMeta(childMeta);children.add(childRouter);}}// 5. 綁定子菜單到一級菜單parentRouter.setChildren(children);routerList.add(parentRouter);}}return routerList;}
}

五、關鍵邏輯解析

1. 表關聯查詢的意義

動態路由的核心是 “權限控制”,而權限控制的基礎是用戶 - 角色 - 菜單的關聯關系:

  • 用戶(user)通過user_role關聯角色(role);
  • 角色(role)通過role_menu關聯菜單(menu);
  • 最終實現 “用戶→角色→菜單” 的權限傳遞,確保用戶只能訪問其角色允許的菜單。

2. 路由封裝的核心思路

數據庫查詢的menuList是扁平的菜單列表(包含一級和二級菜單),需要轉換為樹形結構(一級菜單包含子菜單列表):

  • 先篩選parent_id = 0的一級菜單;
  • 再遍歷所有菜單,為每個一級菜單匹配parent_id等于其menu_id的子菜單;
  • 通過MetaVO封裝前端渲染所需的標題和圖標,確保與前端路由組件屬性對應。

3. 擴展性考慮

若系統需要支持三級及以上菜單,只需修改 Service 層的封裝邏輯,將子菜單的篩選改為遞歸處理:

// 遞歸獲取子菜單(示例偽代碼)
private List<ChildMenuRouterVO> getChildRouters(Integer parentId, List<Menu> menuList) {List<ChildMenuRouterVO> children = new ArrayList<>();for (Menu menu : menuList) {if (menu.getParentId().equals(parentId)) {ChildMenuRouterVO child = new ChildMenuRouterVO();// 封裝子菜單屬性...// 遞歸查詢當前子菜單的子菜單(三級菜單)child.setChildren(getChildRouters(menu.getMenuId(), menuList)); children.add(child);}}return children;
}

六、最終返回結果示例

前端接收的 JSON 格式如下(與 VO 類結構對應),可直接用于渲染動態路由:

{"code": 200,"msg": "操作成功","data": {"userId": 1,"username": "admin","realName": "管理員"// ...其他用戶信息},"roles": "超級管理員","routers": [{"name": "系統管理","path": "/sys","component": "Layout","hidden": "false","redirect": "noRedirect","alwaysShow": true,"meta": {"title": "系統管理","icon": "system"},"children": [{"name": "管理員管理","path": "/user","component": "sys/user/index","hidden": "false","meta": {"title": "管理員管理","icon": "user"}}]}]
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/916980.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/916980.shtml
英文地址,請注明出處:http://en.pswp.cn/news/916980.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python在自動化與運維領域的核心角色:工具化、平臺化與智能化

&#x1f4dd;個人主頁&#x1f339;&#xff1a;慌ZHANG-CSDN博客 &#x1f339;&#x1f339;期待您的關注 &#x1f339;&#x1f339; 引言 在 IT 系統日益復雜、運維任務持續增長的今天&#xff0c;自動化已成為企業基礎設施管理的關鍵方向。Python 以其簡潔的語法、強大…

RAG實戰指南 Day 28:RAG系統緩存與性能優化

【RAG實戰指南 Day 28】RAG系統緩存與性能優化 開篇 歡迎來到"RAG實戰指南"系列的第28天&#xff01;今天我們將深入探討RAG系統的緩存機制與性能優化策略。在實際生產環境中&#xff0c;RAG系統往往面臨高并發、低延遲的需求&#xff0c;而合理的緩存設計和性能優…

swanlab實驗優雅起名

init中的參數的作用project&#xff1a;整個實驗的名字&#xff1b;experiment_name&#xff1a;在這個實驗中&#xff0c;你的名字是什么&#xff1b; 比如說現在我們要進行對比實驗&#xff0c;PEAN和Triflownet分別是對比方法的名字&#xff0c;這樣的好處是&#xff0c;她們…

Nestjs框架: NestJS 核心機制解析 —— DI(依賴注入)容器與模塊化工作原理

理解 NestJS 的 DI 管理機制 我們想要了解依賴注入&#xff08;Dependency Injection, DI&#xff09;最核心的工作邏輯NestJS 擁有自己的一套 DI 管理系統&#xff0c;它通過一個稱為 DI 容器 的機制&#xff0c;來統一管理應用中所有類&#xff08;class&#xff09;的依賴關…

日語學習-日語知識點小記-構建基礎-JLPT-N3階段(12):文法+單詞

日語學習-日語知識點小記-構建基礎-JLPT-N3階段&#xff08;12&#xff09;&#xff1a;文法單詞 1、前言&#xff08;1&#xff09;情況說明&#xff08;2&#xff09;工程師的信仰2、知識點&#xff11;ーたぶん 多分&#xff12;ーV&#xff08;て&#xff09;いく ? V&…

【趙渝強老師】OceanBase租戶的資源管理

OceanBase數據庫是多租戶的數據庫系統&#xff0c;一個集群內可包含多個相互獨立的租戶&#xff0c;每個租戶提供獨立的數據庫服務。在OceanBase數據庫中&#xff0c;使用資源配置&#xff08;Unit Config&#xff09;、資源單元&#xff08;Unit&#xff09;和資源池&#xff…

8K、AI、低空智聯,H.266能否撐起下一代視頻通路?

一、&#x1f4c8; 爆發式增長的 AI 與視頻數據&#xff1a;智能時代的“數據燃料革命” 隨著生成式 AI、大模型推理、多模態理解等技術的迅猛發展&#xff0c;視頻數據從“記錄工具”轉變為“感知基礎設施”&#xff0c;其在現代智能系統中的戰略地位日益凸顯。 1?? 視頻數…

保姆級別IDEA關聯數據庫方式、在IDEA中進行數據庫的可視化操作(包含圖解過程)

本文以mysql為例&#xff0c;學會了Mysql&#xff0c;其它的數據庫也是類似的模版~如果您覺得這邊文章對你有幫助&#xff0c;可以收藏防止找不到~如果您覺得這篇文章不錯&#xff0c;也感謝您的點贊對我創作的支持1.1 打開側邊欄的Database2.2 選擇要連接的數據庫&#xff08;…

33.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--單體轉微服務--財務服務--記賬

這篇文章我們一起把記賬模塊從單體應用遷移到微服務架構中。記賬模塊的功能想必大家都已經了解了&#xff0c;主要是記錄用戶的收入和支出&#xff0c;以及對這些記錄的刪除修改和查詢等操作。具體的功能可以參考單體應用專欄&#xff0c;在這里就不多講了。我們現在一起開始遷…

Cursor結合Playwright MCP Server支持自動化

Cursor結合Playwright MCP Server支持自動化 今天分享一下 playwright MCP Server&#xff0c;其提供了瀏覽器自動化能力&#xff0c;使大型語言模型能夠在真實的瀏覽器環境中與網頁交互&#xff0c; 也可以執行任務&#xff0c;例如運行JavaScript、截屏和導航網頁元素&…

Python 求梯形面積的程序(Program to find area of a Trapezoid)

梯形的定義&#xff1a; 梯形是凸四邊形&#xff0c;至少有一對邊平行。平行邊稱為梯形的底邊&#xff0c;另外兩條不平行的邊稱為梯形的腿。梯形也可以有兩對底邊。在上圖中&#xff0c;CD || AB&#xff0c;它們構成底邊&#xff0c;而另外兩條邊&#xff0c;即AD和BC&#…

C語言 —— 指針(4)

動態內存分配動態內存需要手動申請&#xff0c;手動歸還&#xff0c;其內存是開辟在堆區。申請的函數為&#xff1a;void *malloc(size_t size) &#xff08;需包含頭文件#include<stdlib.h>&#xff09;size&#xff1a;要分配的內存大小&#xff0c;以字節為單位。申請…

常用算法思想及模板

今天繼續整理一些關于算法競賽中C適用的一些模板以及思想。 保留x位小數 保留x位小數在C語言中可以使用printf中的"%.xf"來實現&#xff0c;但是很多C選手由于關閉了同步流&#xff0c;害怕cin、cout與scanf、printf混用容易出錯&#xff0c;所以就給大家介紹一個強…

GitLab 倉庫 — 常用的 git 命令

在公司的 gitlab 公共倉庫中寫代碼做項目時&#xff0c;主要涉及以下常用 git 命令&#xff1a;一、單個命令講解1. 拉取代碼&#xff08;1&#xff09;git clone [倉庫 URL]?克隆遠程倉庫到本地&#xff08;需確保 URL 正確&#xff09; ?&#xff08;?2&#xff09;git pu…

【28】C# WinForm入門到精通 ——多文檔窗體MDI【屬性、方法、實例、源碼】【多窗口重疊、水平平鋪、垂直平鋪、窗體傳值】

文章目錄1多文檔窗體MDI2 基本設置3 實例&#xff1a;多窗口重疊、水平平鋪、垂直平鋪3.1 主窗口屬性設置3.2 主窗口3.3 主窗口窗口添加MenuStrip菜單3.4 添加處理函數3.5 測試效果4 利用窗體參數定義進行傳值4.1 在Form2、Form3添加相關控件4.2 Form3 定義函數public Form3(st…

【計算機科學與應用】基于Session欺騙攻擊的Web應用程序防護

導讀&#xff1a; 本文對Web應用程序開發中的Session欺騙攻擊進行了闡述&#xff0c;詳細講解了防范Session欺騙攻擊的三種傳統方法&#xff0c;并給出了防范代碼&#xff0c;分析了三種傳統防范方法的不足&#xff0c;新設計了一種通過Referer信息驗證來加強對Session欺騙的防…

yolo8+阿里千問圖片理解(華為簡易版小藝看世界)

? 實現目標 按下空格鍵 → 獲取攝像頭當前畫面&#xff1b; 將圖片上傳給 大模型 接口&#xff0c;讓其“看圖說話”&#xff1b; 獲取返回描述后&#xff0c;以字幕形式展示在圖像畫面上&#xff1b; 持續顯示識別結果&#xff0c;直到下次按空格。 &#x1f9e0; 需要準…

【ee類保研面試】數學類---線性代數

25保研er&#xff0c;希望將自己的面試復習分享出來&#xff0c;供大家參考 part0—英語類 part1—通信類 part2—信號類 part3—高數類 part100—self項目準備 文章目錄線性代數知識點大全**1. 余子式與代數余子式****2. 行列式的含義****3. 矩陣的秩&#xff08;Rank&#xf…

在 Scintilla 中為 Squirrel 語言設置語法解析器的方法

Scintilla 作為一個強大的開源文本編輯控件&#xff0c;通過配置語法解析器&#xff0c;能夠對多種編程語言實現語法高亮、代碼折疊等實用功能。若要為新語言 Squirrel 設置語法解析器&#xff0c;可參考以下步驟&#xff1a;?創建 Lexer 源文件&#xff1a;Scintilla 通過 Le…

Go語言核心知識點補充

Go語言核心知識點補充 make函數、for循環與輸入處理詳解 在前幾章的內容中&#xff0c;我們介紹了Go語言的基礎語法、變量聲明、切片、循環等核心概念。但在實際開發中&#xff0c;一些細節性的知識點往往決定了代碼的健壯性與效率。 本文將針對前幾章涉及到的變量聲明與初始化…