菜单权限的设计与实现

说明

    该文章是属于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,获取对应的菜单

   /// <summary>    /// 获取公司下系统的菜单    /// </summary>    /// <param name="corporationKey">公司key</param>    /// <param name="systemKey">系统key</param>    /// <returns></returns>    public ReceiveStatus<TreeOutPut> GetCorporationSystemMenuList(string corporationKey, string systemKey)    {        ReceiveStatus<TreeOutPut> receiveStatus = new();        if (string.IsNullOrEmpty(corporationKey))            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不能为空!");        if (string.IsNullOrEmpty(systemKey))            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不能为空!");         //公司信息        var corporationModel = _sysCorporationRepository.GetByKey(corporationKey, BaseSqlRepository.sysCorporation_selectByKeySql);        if (corporationModel == null)            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不存在!");        if (corporationModel.IsOpen == false)            return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司已停用,不能设置!");         //系统信息        var sysModel = _sysSystemRepository.GetByKey(systemKey, BaseSqlRepository.sysSystem_selectByKeySql);        if (sysModel == null)            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不存在!");        if (sysModel.IsOpen == false)            return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统已停用,不能设置!");         var corporationSystemMenuList = _menuRepository.GetCorporationSystemMenuList(corporationKey, systemKey);        foreach (var item in corporationSystemMenuList)        {            item.disabled = Const.OverallAuth_MenuKey.Any(f => f == item.key) ? true : false;        }                //把菜单递归转换成树形结构        var tree = corporationSystemMenuList.ConvertTreeData();        receiveStatus.data = tree;        return receiveStatus;    }

该接口获取系统对应的菜单,用于呈现到界面上,有人可能会问,为什么要传公司key和系统key。文章开头也有提到,由于overallAuth2.0设计的初衷是,统一权限的分发中心,可以支持多公司、多系统的权限分发,系统中存在不同公司、不同系统的菜单。所以我们在接口处需要传递公司key和系统key获取菜单。

第二个接口:根据角色获取菜单

/// <summary> /// 根据角色id获取权限菜单 /// </summary> /// <param name="roleId">角色id</param> /// <param name="corporationKey">公司key</param> /// <param name="isContainSubordinate">是否包含下级角色</param> /// <returns>返回角色所属菜单key</returns> public ReceiveStatus<menuOrRoleOutPut> GetMenuKeyByRoleId(int roleId, bool isContainSubordinate, string corporationKey) {     ReceiveStatus<menuOrRoleOutPut> receiveStatus = new ReceiveStatus<menuOrRoleOutPut>();     List<menuOrRoleOutPut> selectedItem = new();      if (string.IsNullOrWhiteSpace(corporationKey))         return ExceptionHelper<menuOrRoleOutPut>.CustomExceptionData("请先登录");     if (roleId > 0)     {         //获取角色         var roleModel = _sysRoleRepository.GetByKey(roleId.ToString(), BaseSqlRepository.sysRole_selectByKeySql);         if (roleModel == null)             return ExceptionHelper<menuOrRoleOutPut>.CustomExceptionData(string.Format("角色id为【{0}】的角色不存在", roleId));          //当前角色菜单         var currentRoleMenuList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(new List<int> { roleId });         foreach (var item in currentRoleMenuList)         {             menuOrRoleOutPut menuOrRoleOutPut = new()             {                 subordinateRoleMenu = false,                 roleKey = roleId,                 menuKey = item.MenuId ?? string.Empty             };             selectedItem.Add(menuOrRoleOutPut);         }         if (isContainSubordinate)         {             //递归获取下级角色菜单             var roleIdList = RoleCore.GetChildrenRoleById(roleId, corporationKey, Const.OverallAuth_SystemKey);             var menuRoleList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(roleIdList);             foreach (var item in menuRoleList)             {                 menuOrRoleOutPut menuOrRoleOutPut = new()                 {                     subordinateRoleMenu = true,                     roleKey = roleId,                     menuKey = item.MenuId ?? string.Empty                 };                 selectedItem.Add(menuOrRoleOutPut);             }         }     }     receiveStatus.data = selectedItem;     receiveStatus.msg = "获取成功";     return receiveStatus; }

该接口主要是获取角色已经分配好的菜单,用于默认显示在界面上。

第三个接口:保存角色与菜单关系

/// <summary> /// 保存角色权限 /// </summary> /// <param name="roleMenuExend">角色和菜单模型</param> /// <param name="userId">操作人员id</param> public ReceiveStatus SaveRoleAuthority(RoleMenuInput roleMenuExend, string userId) {     ReceiveStatus receiveStatus = new();     //获取角色     var roleModel = _sysRoleRepository.GetByKey(roleMenuExend.roleId.ToString(), BaseSqlRepository.sysRole_selectByKeySql);     if (roleModel == null)         return ExceptionHelper.CustomException(string.Format("角色id为【{0}】的角色不存在", roleMenuExend.roleId));     var dateTime = DateTime.Now;      List<SysMenuRoleRelation> list = new();     //全选菜单     foreach (var item in roleMenuExend.menuIds)     {         SysMenuRoleRelation sysMenuRoleRelation = new SysMenuRoleRelation         {             CreateTime = dateTime,             RoleId = roleMenuExend.roleId,             CreateUser = userId,             IsHalfSelected = false,             MenuId = item,         };         list.Add(sysMenuRoleRelation);     }      //半选菜单     foreach (var item in roleMenuExend.isHalfMenuIds)     {         SysMenuRoleRelation sysMenuRoleRelation = new SysMenuRoleRelation         {             CreateTime = dateTime,             RoleId = roleMenuExend.roleId,             CreateUser = userId,             IsHalfSelected = true,             MenuId = item,         };         list.Add(sysMenuRoleRelation);     }      TransactionHelper.ExecuteTransaction(() =>     {         _sysMenuRoleRelationRepository.DeleteByRoleId(roleMenuExend.roleId);         _sysMenuRoleRelationRepository.BatchInsert(list);     });     return receiveStatus; }

通过以上3个接口,我们就能自由的分配角色与菜单的关系。

5、建立用户与角色的关系

有了角色与菜单关系后,我们还需要建立用户与角色的关系,也就是要往【Sys_UserRoleRelation】中插入数据。

同理我们要实现以下2张图的业务逻辑

菜单权限的设计与实现

菜单权限的设计与实现

同样,要建立用户与角色的关系,我们同样要实现3个接口

第一个接口:获取系统角色数据

/// <summary> /// 获取树形结构角色数据 /// </summary> /// <param name="corporationKey">公司key</param> /// <param name="systemKey">系统key</param> /// <returns></returns> public ReceiveStatus<TreeOutPut> GetRoleTree(string corporationKey, string? systemKey) {     ReceiveStatus<TreeOutPut> receiveStatus = new ReceiveStatus<TreeOutPut>();     if (string.IsNullOrWhiteSpace(corporationKey))         return ExceptionHelper<TreeOutPut>.CustomExceptionData("请选择公司!");      if (!string.IsNullOrWhiteSpace(systemKey))     {         var systemModel = _sysSystemRepository.GetByKey(systemKey, BaseSqlRepository.sysSystem_selectByKeySql);         if (systemModel == null)             return ExceptionHelper<TreeOutPut>.CustomExceptionData("系统不存在或已删除!");     }      var corporationModel = _sysCorporationRepository.GetByKey(corporationKey, BaseSqlRepository.sysCorporation_selectByKeySql);     if (corporationModel == null)         return ExceptionHelper<TreeOutPut>.CustomExceptionData("公司不存在或已删除!");      var corporationSystemList = _sysRoleRepository.GetRoleList(corporationKey, systemKey);      List<TreeOutPut> list =     [         new TreeOutPut         {             key = corporationModel.CorporationKey,             pKey = "",             label = corporationModel.CorporationName,         },     ];     list.AddRange(corporationSystemList);      //递归获取角色树     var tree = list.ConvertTreeData();     receiveStatus.data = tree;     return receiveStatus; }

该接口就是,获取系统中所有的角色
第二个接口:获取已分配用户的角色数据

 /// <summary>  /// 根据用户id获取对应角色  /// </summary>  /// <param name="userId">用户ID</param>  /// <returns>返回用户所属角色key</returns>  public ReceiveStatus<int> GetUserRole(string userId)  {      ReceiveStatus<int> receiveStatus = new();       //获取用户角色,并验证      var model = _sysUserRepository.GetByKey(userId, BaseSqlRepository.sysUser_selectByKeySql);      if (model == null)          return ExceptionHelper<int>.CustomExceptionData("用户不存在或已停用");       var userRoleList = _sysUserRoleRelationRepository.GetSysUserRoleRelationsByUserId(userId);      var roleIdList = userRoleList.Select(f => f.RoleId).ToList();       receiveStatus.data = roleIdList;      return receiveStatus;  }

第三个接口:设置(保存)用户角色

/// <summary> /// 设置用户角色 /// </summary> /// <param name="userOrRoleInput">传入模型</param> /// <param name="userId">修改用户id</param> public ReceiveStatus SetUserRole(UserRoleInput userOrRoleInput, string userId) {     ReceiveStatus receiveStatus = new ReceiveStatus();     if (userOrRoleInput.RoleId.Count == 0)         return ExceptionHelper.CustomException("没有选择任何角色!");     var userModel = _sysUserRepository.GetByKey(userOrRoleInput.UserId, BaseSqlRepository.sysUser_selectByKeySql);     if (userModel == null)         return ExceptionHelper.CustomException("该用户不存在");      //写入数据库,加入事务     TransactionHelper.ExecuteTransaction(() =>     {         _sysUserRoleRelationRepository.DeleteByUserId(userOrRoleInput.UserId);         var dateTime = DateTime.Now;         foreach (var item in userOrRoleInput.RoleId)         {             SysUserRoleRelation sysUserRoleRelation = new SysUserRoleRelation             {                 RoleId = item,                 UserId = userOrRoleInput.UserId,                 CreateUser = userId.ToString(),                 CreateTime = dateTime             };             _sysUserRoleRelationRepository.Insert(sysUserRoleRelation, BaseSqlRepository.sysUserRoleRelation_insertSql);         }     });     return receiveStatus; }

该接口,就是保存角色与用户的关系。

6、登录验证

有了以上的关系,我们就需要再登录是做好验证。

大致的验证有几点

1、用户是否存在

2、用户是否启用

3、用户是否分配角色

4、角色是否启用

5、角色是否分配菜单

/// <summary> /// 获取用户所属菜单 /// </summary> /// <param name="userId">用户id</param> /// <param name="corporationKey">公司key</param> /// <returns>返回用户所属菜单</returns> public ReceiveStatus<SysMenuOutPut> GetMenuByUserId(string userId, string corporationKey) {     ReceiveStatus<SysMenuOutPut> receiveStatus = new ReceiveStatus<SysMenuOutPut>();              //获取用户,并验证     var userModel = _sysUserRepository.GetByKey(userId, BaseSqlRepository.sysUser_selectByKeySql);     if (userModel == null)         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户Id【{0}】不存在", userId));     if (!userModel.IsOpen)         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未启用", userModel.UserName));      //获取用户角色,并验证     var userRoleList = _sysUserRoleRelationRepository.GetSysUserRoleRelationsByUserId(userModel.UserId ?? string.Empty).Where(f => f.IsOpen).ToList();     if (userRoleList == null || userRoleList.Count == 0)         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未分配角色,或绑定角色未开启", userModel.UserName));     var roleIdList = userRoleList.Select(f => f.RoleId).ToList();      //递归获取角色下,说有角色,并去重  ps:因为我们的角色有继承关系,上级角色会继承下级角色的所有权限     List<int> roleKeyList = new List<int>();     foreach (var role in roleIdList)     {         var key = RoleCore.GetChildrenRoleById(role, corporationKey, Const.OverallAuth_SystemKey);         roleKeyList.AddRange(key);         roleKeyList.Add(role);     }     roleKeyList = roleKeyList.Distinct().ToList();      //获取角色菜单     var menuRoleList = _sysMenuRoleRelationRepository.GetSysMenuRoleRelationByRoleId(roleKeyList);     if (menuRoleList == null || menuRoleList.Count == 0)         return ExceptionHelper<SysMenuOutPut>.CustomExceptionData(string.Format("用户【{0}】未分配菜单", userModel.UserName));      //递归菜单并返回     var menuIdList = menuRoleList.GroupBy(f => f.MenuId).Select(f => f.Key).ToList();     var menuList = _menuRepository.GetMenusByMenuIdList(menuIdList);     List<SysMenuOutPut> list = new();      //模型的转换     foreach (var item in menuList)     {         SysMenuOutPut model = new()         {             Id = item.Id,             Pid = item.Pid,             CorporationKey = item.CorporationKey,             SystemKey = item.SystemKey,             Path = item.Path,             Name = item.Name,             MenuIcon = item.MenuIcon,             Component = item.Component,             IsOpen = item.IsOpen,             Sort = item.Sort,             RequireAuth = item.RequireAuth,             Redirect = item.Redirect,             CreateTime = item.CreateTime,             CreateUser = item.CreateUser,         };         list.Add(model);     }      //把菜单递归转换成前端能识别的树形接口     var menuDaoList = MenuCore.GetMenuTreeList(list);     receiveStatus.data = menuDaoList;     receiveStatus.msg = "获取成功";     return receiveStatus; }

该接口就是用户在登录之后,获取属于自身的菜单,然后用于绑定到路由菜单中。

 

提示:如果你是跟着本人的系列文章搭建的系统。那么需要做以下修改

找到路径:store->user.ts 把loadMenus()方法中的getMenuTreeData()方法替换成getMenuByUserId()方法即可实现菜单权限。

需要源码的,关注公众号,发送【权限】获取源码

以上就是本篇文章的全部内容,感谢耐心观看

后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html

前端vue 预览地址:http://139.155.137.144:8881

关注公众号:发送【权限】,获取源码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

菜单权限的设计与实现

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

发表评论

评论已关闭。

相关文章