以前設計到的都是用戶的簡單角色,然后角色的對應表的Permisson對應了菜單的ID,最近接了一個項目要求控制到頁面操作權限,就是權限到頁面按鈕,所以不得不去補習下這方面的知識,還好再github上很快發現了一個通用的基礎權限框架.WYRMS.
簡單說下他涉及到的權限設計的相關表吧.
用戶表(Users) 角色表(Roles) 角色用戶關聯表(RoleUsers)
模塊(菜單)表(Modules) 權限表(Permissions) 權限角色表(PermissionRoles)
用戶組表(UserGroups) 用戶組角色表(UserGroupRoles) 用戶用戶組表(UserGroupUsers)
在這里, 我們在動態加載菜單權限樹的時候,我們先根據用戶ID查詢出用戶角色所在的角色結果集,然后再次去查詢用戶所在的用戶組涉及到的角色結果集,合並重復角色,獲得當前用戶涉及到的所有扮演的角色,然后我們去權限表里面查詢出這些角色涉及到的所有權限集合,接下來我們就要根據權限集合推導出用戶涉及到的顯示菜單也就是顯示模塊,因為每種權限都有一個所屬菜單的字段,我們提取所有涉及到的模塊,其實這里面提取出來的都是最底層的菜單,所有我們的難點在於逆推出整個菜單,這也是我對這個Github項目不滿意的地方,他原有的代碼只能支持二級菜單,不能無限遞歸菜單.
1 User user = 2 _UserRepository.GetEntitiesByEager(new List<string> { "Roles", "UserGroups" }) 3 .FirstOrDefault(c => c.Id == id); 4 var roleIdsByUser = user.Roles.Select(r => r.Id).ToList(); 5 var roleIdsByUserGroup = user.UserGroups.SelectMany(g => g.Roles).Select(r => r.Id).ToList(); 6 roleIdsByUser.AddRange(roleIdsByUserGroup); 7 var roleIds = roleIdsByUser.Distinct().ToList(); 8 List<Permission> permissions = 9 _RoleService.Roles.Where(t => roleIds.Contains(t.Id) && t.Enabled == true) 10 .SelectMany(c => c.Permissions) 11 .Distinct() 12 .ToList();
1 List<Module> childModules = 2 _PermissionService.Permissions.Where(p => permissionIds.Contains(p.Id) && p.Enabled == true) 3 .Select(p => p.module) 4 .Distinct() 5 .ToList();
我的想法是,我先根據每個底層菜單不斷向上逆推,只要父節點模塊存在我就加入這個模塊List<Module>,知道表示父節點的字段為null,然后根據這個列表中的最上層節點,依次往下選擇有的子節點。
1 /// <summary> 2 /// 根據傳遞過來的最底層菜單節點,遞歸得出涉及到的所有菜單節點 3 /// </summary> 4 /// <param name="childModules"></param> 5 /// <returns></returns> 6 public List<Module> GetRelatedMenuItem(List<Module> childModules) 7 { 8 List<Module> relatedMenuItem = new List<Module>(); 9 10 //首先遍歷當前底層節點得到涉及到的第一個上層父節點 11 foreach (Module item in childModules.Distinct().ToList()) 12 { 13 relatedMenuItem.AddRange(GetTopParentTree(item)); 14 } 15 return relatedMenuItem.Distinct().ToList(); 16 } 17 18 public List<Module> GetTopParentTree(Module currentModule) 19 { 20 List<Module> relatedNode=new List<Module>(); 21 22 if (currentModule.ParentId != null) 23 { 24 relatedNode.AddRange(GetTopParentTree(currentModule.ParentModule)); 25 } 26 27 relatedNode.Add(currentModule); 28 29 return relatedNode; 30 } 31 32 private List<ModuleVM> GetMenuTree(List<Module> childModules) 33 { 34 List<ModuleVM> resultMenuList=childModules.Where<Module>(x => x.ParentId == null).Select(c => new ModuleVM { Id = c.Id, Name = c.Name, LinkUrl = c.LinkUrl, Code = c.Code }).ToList(); 35 if (resultMenuList!=null&&resultMenuList.Count > 0) 36 { 37 for (int index = 0; index < resultMenuList.Count; index++) 38 { 39 resultMenuList[index].ChildModules = GetChildrenNode(childModules, resultMenuList[index]); 40 } 41 } 42 43 return resultMenuList; 44 } 45 /// <summary> 46 /// 迭代獲取父節點底下所有的相關子節點樹 47 /// </summary> 48 /// <param name="childModules"></param> 49 /// <param name="parentModule"></param> 50 /// <returns></returns> 51 private List<ModuleVM> GetChildrenNode(List<Module> childModules, ModuleVM parentModule) 52 { 53 List<ModuleVM> resultMenuList = null; 54 55 //首先尋找該節點在下面是否有子節點 56 resultMenuList = childModules.Where(x => x.ParentId == (int?)parentModule.Id) 57 .Select(c => new ModuleVM { Id = c.Id, Name = c.Name, LinkUrl = c.LinkUrl, Code = c.Code }).ToList(); 58 59 if (resultMenuList != null && resultMenuList.Count > 0) 60 { 61 for (int index = 0; index < resultMenuList.Count; index++) 62 { 63 resultMenuList[index].ChildModules = GetChildrenNode(childModules, resultMenuList[index]); 64 } 65 } 66 67 return resultMenuList; 68 }
最后在前端顯示的時候,我們只需要在Razor里面寫個迭代就好:
<ul class="sidebar-menu"> <li class="active"><a href="/Common/Home/Index"><i class="fa fa-home"></i> <span>主頁</span></a></li> @helper NodeHelper(ModuleVM node) { <li class="treeview"> @if (node.ChildModules != null&&node.ChildModules.Count>0) { <a href="#"> <i class="fa fa-windows"></i> <span>@node.Name</span> <i class="fa fa-angle-left pull-right"></i> </a> <ul class="treeview-menu"> @foreach (ModuleVM child in node.ChildModules) { @NodeHelper(child) } </ul> } else { <a href="@Url.Content(@node.LinkUrl)">@node.Name</a> } </li> } @foreach (var item in Model) { @NodeHelper(item) } </ul><!-- /.sidebar-menu -->