尚品甄選電商SpringBoot-Web開發3權限管理之選單管理
目標
- 選單管理
- 選單需求和表結構
- 選單管理
CRUD
操作介面 - 選單管理
CRUD
前端
- 為角色分配選單
- 需求分析
- 介面
- 搜尋所有選單和角色分配選單
id
列表 - 儲存角色分配的選單數據
- 搜尋所有選單和角色分配選單
- 前端
- 動態選單
- 需求分析
- 介面
- 搜尋當前登入用戶可以操作的選單
- 前端
選單管理
選單需求和表結構
選單中的數據會有層級關係例如:
- 權限管理
- 用戶管理
- 角色管理
- 選單管理
- 訂單管理
- 訂單列表
那在資料庫中應該如何儲存這樣的層級關係呢?
可以使用id
和parentId
來表示,例如
如此一來就可以連結
新增相關檔案,完成準備工作
列出選單
一樣是controller -> service -> mapper
,不過service
的邏輯稍微複雜了一點
為了列出所有的選單,以及子選單,新增以下介面以供使用
// SysMenuController.java
// 選單列表
@GetMapping("/findNodes")
public Result findNodes() {
List<SysMenu> list = sysMenuService.findNodes();
return Result.build(list, ResultCodeEnum.SUCCESS);
}
service
的部分則是分為兩個主要操作
- 調用
mapper
尋找所有存在於數據庫中的選單 - 依照層級關係封裝所有的選單,使前端的
element-plus
可以直接使用數據
// SysMenuServiceImpl.java
// 選單列表
@Override
public List<SysMenu> findNodes() {
// 尋找所有選單,回傳list
List<SysMenu> sysMenuList = sysMenuMapper.findAll();
if (CollectionUtils.isEmpty(sysMenuList)) return null;
// 使用工具,把回傳的list封裝成前端element-plus要求的格式
return MenuHelper.buildTree(sysMenuList);
}
核心的部分是工具,利用遞迴一層一層封裝數據
▶
MenuHelper工具
// 封裝樹狀選單
public class MenuHelper {
public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
// 封裝數據
List<SysMenu> trees = new ArrayList<>();
// 疊代sysMenu列表
for (SysMenu sysMenu : sysMenuList) {
if (sysMenu.getParentId() == 0) {
trees.add(findChildren(sysMenu, sysMenuList));
}
}
return trees;
}
// 搜尋menu的子menu
private static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> sysMenuList) {
sysMenu.setChildren(new ArrayList<>());
for (SysMenu ele : sysMenuList) {
// 找到對應的子menu
if (sysMenu.getId().longValue() == ele.getParentId().longValue()) {
// 加入parent的小孩們,但可能有更下層的小孩,所以繼續呼叫findChildren
sysMenu.getChildren().add(findChildren(ele, sysMenuList));
}
}
return sysMenu;
}
}
新增與修改選單
- 這部分就是簡單的
controller -> service -> mapper
搞定,沒有額外的邏輯了
刪除選單
刪除選單時會有個問題,如果刪除高層選單,會需要考慮子選單是否一併刪除。這裡採取的方案是,如果刪除的選單包含子選單,那就不能刪除,所以步驟如下
- 判斷當前要刪除的選單是否包含子選單
- 包含:不能刪除;不包含:可以刪除
主要邏輯如下
SysMenuServiceImpl.java// 選單刪除
@Override
public void removeById(Long id) {
// 取得當前選單的子選單個數
int count = sysMenuMapper.selectCountById(id);
// 是否包含子選單,如包含,不可刪
if (count > 0) throw new SimonException(ResultCodeEnum.NODE_ERROR);
// 不包含子選單,刪除
sysMenuMapper.delete(id);
}
前端整合
前端的code
直接貼上,重點是後端的程式
為角色分配選單
需求分析
需要兩個介面:
- 查詢所有選單和角色分配過選單
id
的列表用以在前端顯示 - 儲存角色和選單之間的關係
查詢所有選單和角色分配過選單id
的列表
也是簡單的controller -> service -> mapper
// 搜尋所有選單以及搜尋角色分配過的選單id列表
@GetMapping("/findSysRoleMenuByRoleId/{roleId}")
public Result findSysRoleMenuByRoleId(@PathVariable("roleId") Long roleId) {
Map<String, Object> map = sysRoleMenuService.findSysRoleMenuByRoleId(roleId);
return Result.build(map, ResultCodeEnum.SUCCESS);
}
// 搜尋所有選單以及搜尋角色分配過的選單id列表
@Override
public Map<String, Object> findSysRoleMenuByRoleId(Long roleId) {
// 搜尋所有選單
List<SysMenu> sysMenuList = sysMenuService.findNodes();
// 根據id搜尋角色分配過的選單id列表
List<Long> roleMenuIds = sysRoleMenuMapper.findSysRoleMenuByRoleId(roleId);
Map<String, Object> map = new HashMap<>();
map.put("sysMenuList", sysMenuList);
map.put("roleMenuIds", roleMenuIds);
return map;
}
<select id="findSysRoleMenuByRoleId" resultType="long">
select menu_id
from sys_role_menu
where role_id = #{roleId} and is_deleted = 0
</select>
其實可以在搜尋的時候去掉is_deleted = 0
這個條件,因為後面實作重新分配角色選單的時候是直接把role<->menu
關係刪除,只是tutorial
中還是包含了這個條件所以我就保留了
儲存角色分配選單之數據
和上一篇分配角色時的邏輯大致相同
SysRoleMenuServiceImpl.java// 儲存角色分配選單數據
@Override
public void doAssign(AssignMenuDto assignMenuDto) {
// 刪除原本角色分配的選單數據
sysRoleMenuMapper.deleteByRoleId(assignMenuDto.getRoleId());
// 為角色重新分配選單數據
List<Map<String, Number>> menuInfo = assignMenuDto.getMenuIdList();
if (menuInfo != null && !menuInfo.isEmpty()) {
sysRoleMenuMapper.doAssign(assignMenuDto);
}
}
<!--刪除原本角色分配的選單數據-->
<delete id="deleteByRoleId">
delete from sys_role_menu where role_id = #{roleId}
</delete>
<!--為角色分配選單數據-->
<insert id="doAssign">
insert into sys_role_menu (role_id,
menu_id,
create_time,
update_time,
is_deleted,
is_half)
values
<foreach collection="menuIdList" item="menuInfo" separator=",">
(#{roleId}, #{menuInfo.id}, now(), now(), 0, #{menuInfo.isHalf})
</foreach>
</insert>
前端整合
這邊前端也是大同小異,直接照教程貼上理解即可
動態選單
後端的部分主要的邏輯在service
▶
SysMenuServiceImpl.java
@Override
public List<SysMenuVo> findMenusByUserId() {
// 取得當前登入用戶之用戶id
Long userId = AuthContextUtil.get().getId();
// 根據id搜尋其可使用之選單
List<SysMenu> menuList = sysMenuMapper.findMenusByUserId(userId);
// 封裝成選單樹
List<SysMenu> sysMenuList = MenuHelper.buildTree(menuList);
// 轉換選單為回傳格式並回傳
return buildMenus(sysMenuList);
}
private List<SysMenuVo> buildMenus(List<SysMenu> menuList) {
List<SysMenuVo> sysMenuVoList = new LinkedList<>();
for (SysMenu sysMenu : menuList) {
// Setup current menu
SysMenuVo sysMenuVo = new SysMenuVo();
sysMenuVo.setTitle(sysMenu.getTitle());
sysMenuVo.setName(sysMenu.getComponent());
// Get current menu's children
List<SysMenu> children = sysMenu.getChildren();
// Build its children if necessary
if (!CollectionUtils.isEmpty(children)) {
sysMenuVo.setChildren(buildMenus(children));
}
// Add to the list
sysMenuVoList.add(sysMenuVo);
}
return sysMenuVoList;
}
以及最查詢資料庫,需要關聯三張表
<!--根據用戶id搜尋可以操作的選單們-->
<select id="findMenusByUserId" resultMap="sysMenuMap">
select distinct sm.*
from sys_menu sm
inner join sys_role_menu srm on sm.id = srm.menu_id
inner join sys_user_role sur on srm.role_id = sur.role_id
where sur.user_id = #{userId}
and sm.is_deleted = 0
</select>
前端的部分把相關的code
改成動態選單就好了
bug問題
- 角色分配了某個選單以及其全部子選單,這時再添加一個新的子選單,該角色會自動被分配新的角色。
- 例如:
測試人員
被分配系統管理
選單下的所有子選單,這時再為系統管理
新增一個子選單(例如地區管理
)那測試人員
就會被分配這個新的選單
如何解決?
- 新增選單時,把其父選單
isHalf
重新賦值為1
尚品甄選電商SpringBoot-Web開發3權限管理之選單管理
https://f88083.github.io/2024/05/01/尚品甄選電商SpringBoot-Web開發3權限管理之選單管理/