菜单权限的计划与实现

打印 上一主题 下一主题

主题 831|帖子 831|积分 2493

说明

    该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。
    该系统文章,我会只管说的非常详细,做到不管新手、老手都能看懂。
    说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。
友情提醒:本篇文章是属于系列文章,看该文章前,发起先看之前文章,可以更好理解项目布局。
qq群:801913255,进群有什么不懂的尽管问,群主都会耐心解答。
有兴趣的朋侪,请关注我吧(*^▽^*)。

关注我,学不会你来打我
1、媒介
经过上一篇《大话《权限计划》全篇,领略不同计划模式的魅力》对权限的先容,相信大家看了之后,对权限的计划有了较为清晰的理解。而我们的OverallAuth2.0 也正好到了需要开发权限的阶段,借助这个机会,我们一起来看下菜单权限是怎么计划与实现的。
 2、菜单权限实现流程

通过以上流程图可以看出。菜单权限实现的关键点就是用户、脚色、菜单三者之间的关系。也正是上一篇文章中,我们说的到RABC基于脚色的权限计划。当然这里的脚色我们是分了品级的,也就是说上级脚色会继承下级脚色的权限。
3、数据表计划

上图是菜单权限实现的主要表。从图中可以看出,不同于一样平常的菜单权限计划,这次计划中我加入了公司表和系统表,它们的作用是为了实现不同公司、不同系统都可以在【OverallAuth2.0】系统中实现菜单权限的分配。可以把【OverallAuth2.0】系统看作一个【同一权限分发中心】来同一分发权限。
4、建立脚色与菜单的关系
要通过RABC实现【菜单权限】,我们需要建立菜单与脚色的关系,也就是要往【Sys_MenuRoleRelation】表中写入数据。
那么接下来,我们需要实现以下2张图中的功能。


要保存菜单与脚色的关系,主要有3个接口
第一个接口:根据公司key和系统key,获取对应的菜单
  1.    /// <summary>
  2.    /// 获取公司下系统的菜单
  3.    /// </summary>
  4.    /// <param name="corporationKey">公司key</param>
  5.    /// <param name="systemKey">系统key</param>
  6.    /// <returns></returns>
  7.    public ReceiveStatus<TreeOutPut> GetCorporationSystemMenuList(string corporationKey, string systemKey)
  8.    {
  9.        ReceiveStatus<TreeOutPut> receiveStatus = new();
  10.        if (string.IsNullOrEmpty(corporationKey))
  11.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不能为空!");
  12.        if (string.IsNullOrEmpty(systemKey))
  13.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不能为空!");
  14.        //公司信息
  15.        var corporationModel = _sysCorporationRepository.GetByKey(corporationKey, BaseSqlRepository.sysCorporation_selectByKeySql);
  16.        if (corporationModel == null)
  17.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不存在!");
  18.        if (corporationModel.IsOpen == false)
  19.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司已停用,不能设置!");
  20.        //系统信息
  21.        var sysModel = _sysSystemRepository.GetByKey(systemKey, BaseSqlRepository.sysSystem_selectByKeySql);
  22.        if (sysModel == null)
  23.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不存在!");
  24.        if (sysModel.IsOpen == false)
  25.            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统已停用,不能设置!");
  26.        var corporationSystemMenuList = _menuRepository.GetCorporationSystemMenuList(corporationKey, systemKey);
  27.        foreach (var item in corporationSystemMenuList)
  28.        {
  29.            item.disabled = Const.OverallAuth_MenuKey.Any(f => f == item.key) ? true : false;
  30.        }
  31.       
  32.        //把菜单递归转换成树形结构
  33.        var tree = corporationSystemMenuList.ConvertTreeData();
  34.        receiveStatus.data = tree;
  35.        return receiveStatus;
  36.    }
复制代码
该接口获取系统对应的菜单,用于呈现到界面上,有人大概会问,为什么要传公司key和系统key。文章开头也有提到,由于overallAuth2.0计划的初衷是,同一权限的分发中心,可以支持多公司、多系统的权限分发,系统中存在不同公司、不同系统的菜单。以是我们在接口处需要通报公司key和系统key获取菜单。
第二个接口:根据脚色获取菜单
  1. /// <summary>
  2. /// 根据角色id获取权限菜单
  3. /// </summary>
  4. /// <param name="roleId">角色id</param>
  5. /// <param name="corporationKey">公司key</param>
  6. /// <param name="isContainSubordinate">是否包含下级角色</param>
  7. /// <returns>返回角色所属菜单key</returns>
  8. public ReceiveStatus<menuOrRoleOutPut> GetMenuKeyByRoleId(int roleId, bool isContainSubordinate, string corporationKey)
  9. {
  10.     ReceiveStatus<menuOrRoleOutPut> receiveStatus = new ReceiveStatus<menuOrRoleOutPut>();
  11.     List<menuOrRoleOutPut> selectedItem = new();
  12.     if (string.IsNullOrWhiteSpace(corporationKey))
  13.         return ExceptionHelper<menuOrRoleOutPut>.CustomExceptionData("请先登录");
  14.     if (roleId > 0)
  15.     {
  16.         //获取角色
  17.         var roleModel = _sysRoleRepository.GetByKey(roleId.ToString(), BaseSqlRepository.sysRole_selectByKeySql);
  18.         if (roleModel == null)
  19.             return ExceptionHelper<menuOrRoleOutPut>.CustomExceptionData(string.Format("角色id为【{0}】的角色不存在", roleId));
  20.         //当前角色菜单
  21.         var currentRoleMenuList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(new List<int> { roleId });
  22.         foreach (var item in currentRoleMenuList)
  23.         {
  24.             menuOrRoleOutPut menuOrRoleOutPut = new()
  25.             {
  26.                 subordinateRoleMenu = false,
  27.                 roleKey = roleId,
  28.                 menuKey = item.MenuId ?? string.Empty
  29.             };
  30.             selectedItem.Add(menuOrRoleOutPut);
  31.         }
  32.         if (isContainSubordinate)
  33.         {
  34.             //递归获取下级角色菜单
  35.             var roleIdList = RoleCore.GetChildrenRoleById(roleId, corporationKey, Const.OverallAuth_SystemKey);
  36.             var menuRoleList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(roleIdList);
  37.             foreach (var item in menuRoleList)
  38.             {
  39.                 menuOrRoleOutPut menuOrRoleOutPut = new()
  40.                 {
  41.                     subordinateRoleMenu = true,
  42.                     roleKey = roleId,
  43.                     menuKey = item.MenuId ?? string.Empty
  44.                 };
  45.                 selectedItem.Add(menuOrRoleOutPut);
  46.             }
  47.         }
  48.     }
  49.     receiveStatus.data = selectedItem;
  50.     receiveStatus.msg = "获取成功";
  51.     return receiveStatus;
  52. }
复制代码
该接口主要是获取脚色已经分配好的菜单,用于默认显示在界面上。
第三个接口:保存脚色与菜单关系
  1. /// <summary>
  2. /// 保存角色权限
  3. /// </summary>
  4. /// <param name="roleMenuExend">角色和菜单模型</param>
  5. /// <param name="userId">操作人员id</param>
  6. public ReceiveStatus SaveRoleAuthority(RoleMenuInput roleMenuExend, string userId)
  7. {
  8.     ReceiveStatus receiveStatus = new();
  9.     //获取角色
  10.     var roleModel = _sysRoleRepository.GetByKey(roleMenuExend.roleId.ToString(), BaseSqlRepository.sysRole_selectByKeySql);
  11.     if (roleModel == null)
  12.         return ExceptionHelper.CustomException(string.Format("角色id为【{0}】的角色不存在", roleMenuExend.roleId));
  13.     var dateTime = DateTime.Now;
  14.     List<SysMenuRoleRelation> list = new();
  15.     //全选菜单
  16.     foreach (var item in roleMenuExend.menuIds)
  17.     {
  18.         SysMenuRoleRelation sysMenuRoleRelation = new SysMenuRoleRelation
  19.         {
  20.             CreateTime = dateTime,
  21.             RoleId = roleMenuExend.roleId,
  22.             CreateUser = userId,
  23.             IsHalfSelected = false,
  24.             MenuId = item,
  25.         };
  26.         list.Add(sysMenuRoleRelation);
  27.     }
  28.     //半选菜单
  29.     foreach (var item in roleMenuExend.isHalfMenuIds)
  30.     {
  31.         SysMenuRoleRelation sysMenuRoleRelation = new SysMenuRoleRelation
  32.         {
  33.             CreateTime = dateTime,
  34.             RoleId = roleMenuExend.roleId,
  35.             CreateUser = userId,
  36.             IsHalfSelected = true,
  37.             MenuId = item,
  38.         };
  39.         list.Add(sysMenuRoleRelation);
  40.     }
  41.     TransactionHelper.ExecuteTransaction(() =>
  42.     {
  43.         _sysMenuRoleRelationRepository.DeleteByRoleId(roleMenuExend.roleId);
  44.         _sysMenuRoleRelationRepository.BatchInsert(list);
  45.     });
  46.     return receiveStatus;
  47. }
复制代码
通过以上3个接口,我们就能自由的分配脚色与菜单的关系。
5、建立用户与脚色的关系
有了脚色与菜单关系后,我们还需要建立用户与脚色的关系,也就是要往【Sys_UserRoleRelation】中插入数据。
同理我们要实现以下2张图的业务逻辑


同样,要建立用户与脚色的关系,我们同样要实现3个接口
第一个接口:获取系统脚色数据
  1. /// <summary>
  2. /// 获取树形结构角色数据
  3. /// </summary>
  4. /// <param name="corporationKey">公司key</param>
  5. /// <param name="systemKey">系统key</param>
  6. /// <returns></returns>
  7. public ReceiveStatus<TreeOutPut> GetRoleTree(string corporationKey, string? systemKey)
  8. {
  9.     ReceiveStatus<TreeOutPut> receiveStatus = new ReceiveStatus<TreeOutPut>();
  10.     if (string.IsNullOrWhiteSpace(corporationKey))
  11.         return ExceptionHelper<TreeOutPut>.CustomExceptionData("请选择公司!");
  12.     if (!string.IsNullOrWhiteSpace(systemKey))
  13.     {
  14.         var systemModel = _sysSystemRepository.GetByKey(systemKey, BaseSqlRepository.sysSystem_selectByKeySql);
  15.         if (systemModel == null)
  16.             return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不存在或已删除!");
  17.     }
  18.     var corporationModel = _sysCorporationRepository.GetByKey(corporationKey, BaseSqlRepository.sysCorporation_selectByKeySql);
  19.     if (corporationModel == null)
  20.         return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不存在或已删除!");
  21.     var corporationSystemList = _sysRoleRepository.GetRoleList(corporationKey, systemKey);
  22.     List<TreeOutPut> list =
  23.     [
  24.         new TreeOutPut
  25.         {
  26.             key = corporationModel.CorporationKey,
  27.             pKey = "",
  28.             label = corporationModel.CorporationName,
  29.         },
  30.     ];
  31.     list.AddRange(corporationSystemList);
  32.     //递归获取角色树
  33.     var tree = list.ConvertTreeData();
  34.     receiveStatus.data = tree;
  35.     return receiveStatus;
  36. }
复制代码
该接口就是,获取系统中所有的脚色
第二个接口:获取已分配用户的脚色数据
  1. /// <summary>
  2. /// 根据用户id获取对应角色
  3. /// </summary>
  4. /// <param name="userId">用户ID</param>
  5. /// <returns>返回用户所属角色key</returns>
  6. public ReceiveStatus<int> GetUserRole(string userId)
  7. {
  8.      ReceiveStatus<int> receiveStatus = new();
  9.      //获取用户角色,并验证
  10.      var model = _sysUserRepository.GetByKey(userId, BaseSqlRepository.sysUser_selectByKeySql);
  11.      if (model == null)
  12.          return ExceptionHelper<int>.CustomExceptionData("用户不存在或已停用");
  13.      var userRoleList = _sysUserRoleRelationRepository.GetSysUserRoleRelationsByUserId(userId);
  14.      var roleIdList = userRoleList.Select(f => f.RoleId).ToList();
  15.      receiveStatus.data = roleIdList;
  16.      return receiveStatus;
  17. }
复制代码
第三个接口:设置(保存)用户脚色
  1. /// <summary>
  2. /// 设置用户角色
  3. /// </summary>
  4. /// <param name="userOrRoleInput">传入模型</param>
  5. /// <param name="userId">修改用户id</param>
  6. public ReceiveStatus SetUserRole(UserRoleInput userOrRoleInput, string userId)
  7. {
  8.     ReceiveStatus receiveStatus = new ReceiveStatus();
  9.     if (userOrRoleInput.RoleId.Count == 0)
  10.         return ExceptionHelper.CustomException("没有选择任何角色!");
  11.     var userModel = _sysUserRepository.GetByKey(userOrRoleInput.UserId, BaseSqlRepository.sysUser_selectByKeySql);
  12.     if (userModel == null)
  13.         return ExceptionHelper.CustomException("该用户不存在");
  14.     //写入数据库,加入事务
  15.     TransactionHelper.ExecuteTransaction(() =>
  16.     {
  17.         _sysUserRoleRelationRepository.DeleteByUserId(userOrRoleInput.UserId);
  18.         var dateTime = DateTime.Now;
  19.         foreach (var item in userOrRoleInput.RoleId)
  20.         {
  21.             SysUserRoleRelation sysUserRoleRelation = new SysUserRoleRelation
  22.             {
  23.                 RoleId = item,
  24.                 UserId = userOrRoleInput.UserId,
  25.                 CreateUser = userId.ToString(),
  26.                 CreateTime = dateTime
  27.             };
  28.             _sysUserRoleRelationRepository.Insert(sysUserRoleRelation, BaseSqlRepository.sysUserRoleRelation_insertSql);
  29.         }
  30.     });
  31.     return receiveStatus;
  32. }
复制代码
该接口,就是保存脚色与用户的关系。
6、登录验证
有了以上的关系,我们就需要再登录是做好验证。
大抵的验证有几点
1、用户是否存在
2、用户是否启用
3、用户是否分配脚色
4、脚色是否启用
5、脚色是否分配菜单
  1. /// <summary>
  2. /// 获取用户所属菜单
  3. /// </summary>
  4. /// <param name="userId">用户id</param>
  5. /// <param name="corporationKey">公司key</param>
  6. /// <returns>返回用户所属菜单</returns>
  7. public ReceiveStatus<SysMenuOutPut> GetMenuByUserId(string userId, string corporationKey)
  8. {
  9.     ReceiveStatus<SysMenuOutPut> receiveStatus = new ReceiveStatus<SysMenuOutPut>();
  10.         
  11.     //获取用户,并验证
  12.     var userModel = _sysUserRepository.GetByKey(userId, BaseSqlRepository.sysUser_selectByKeySql);
  13.     if (userModel == null)
  14.         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户Id【{0}】不存在", userId));
  15.     if (!userModel.IsOpen)
  16.         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未启用", userModel.UserName));
  17.     //获取用户角色,并验证
  18.     var userRoleList = _sysUserRoleRelationRepository.GetSysUserRoleRelationsByUserId(userModel.UserId ?? string.Empty).Where(f => f.IsOpen).ToList();
  19.     if (userRoleList == null || userRoleList.Count == 0)
  20.         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未分配角色,或绑定角色未开启", userModel.UserName));
  21.     var roleIdList = userRoleList.Select(f => f.RoleId).ToList();
  22.     //递归获取角色下,说有角色,并去重  ps:因为我们的角色有继承关系,上级角色会继承下级角色的所有权限
  23.     List<int> roleKeyList = new List<int>();
  24.     foreach (var role in roleIdList)
  25.     {
  26.         var key = RoleCore.GetChildrenRoleById(role, corporationKey, Const.OverallAuth_SystemKey);
  27.         roleKeyList.AddRange(key);
  28.         roleKeyList.Add(role);
  29.     }
  30.     roleKeyList = roleKeyList.Distinct().ToList();
  31.     //获取角色菜单
  32.     var menuRoleList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(roleKeyList);
  33.     if (menuRoleList == null || menuRoleList.Count == 0)
  34.         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未分配菜单", userModel.UserName));
  35.     //递归菜单并返回
  36.     var menuIdList = menuRoleList.GroupBy(f => f.MenuId).Select(f => f.Key).ToList();
  37.     var menuList = _menuRepository.GetMenusByMenuIdList(menuIdList);
  38.     List<SysMenuOutPut> list = new();
  39.     //模型的转换
  40.     foreach (var item in menuList)
  41.     {
  42.         SysMenuOutPut model = new()
  43.         {
  44.             Id = item.Id,
  45.             Pid = item.Pid,
  46.             CorporationKey = item.CorporationKey,
  47.             SystemKey = item.SystemKey,
  48.             Path = item.Path,
  49.             Name = item.Name,
  50.             MenuIcon = item.MenuIcon,
  51.             Component = item.Component,
  52.             IsOpen = item.IsOpen,
  53.             Sort = item.Sort,
  54.             RequireAuth = item.RequireAuth,
  55.             Redirect = item.Redirect,
  56.             CreateTime = item.CreateTime,
  57.             CreateUser = item.CreateUser,
  58.         };
  59.         list.Add(model);
  60.     }
  61.     //把菜单递归转换成前端能识别的树形接口
  62.     var menuDaoList = MenuCore.GetMenuTreeList(list);
  63.     receiveStatus.data = menuDaoList;
  64.     receiveStatus.msg = "获取成功";
  65.     return receiveStatus;
  66. }
复制代码
该接口就是用户在登录之后,获取属于自身的菜单,然后用于绑定到路由菜单中。
 
提示:假如你是跟着本人的系列文章搭建的系统。那么需要做以下修改
找到路径:store->user.ts 把loadMenus()方法中的getMenuTreeData()方法替换成getMenuByUserId()方法即可实现菜单权限。
需要源码的,关注公众号,发送【权限】获取源码
以上就是本篇文章的全部内容,感谢耐心观看
后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html
前端vue 预览地址:http://139.155.137.144:8881
关注公众号:发送【权限】,获取源码
有兴趣的朋侪,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技能文章,不定时分享开源项目。关注我,带你熟悉不一样的程序天下

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表