以前设计到的都是用户的简单角色,然后角色的对应表的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 -->