菜单权限的设计与实现

news/2025/1/21 10:48:05/文章来源:https://www.cnblogs.com/cyzf/p/18683001

说明

    该文章是属于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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/872703.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十一章 成本管理(2025年详细解析版)

目录导语章节介绍什么是成本管理?关注两类成本11.1 管理基础项目成本管理的作用和意义项目成本失控原因(了解)成本类型机会成本沉没成本发展趋势和新兴实践如何计算进度偏差?11.2 项目成本管理过程过程概述项目成本管理过程裁剪时需要考虑的因素(不重要)在敏捷或适应型环…

寒假

今天继续学习Android Studio,今天学习的基础空间Button和EditText两个组件,了解了它们的属性,设计了一个简单的登录页面尝试获取前端的输入的信息,目前还未成功,经过学习,觉得融会贯通,目前认为,xml对应javaweb中的前端html页面,activity对应javaweb中的后端中的Contr…

操作系统课程设计:模拟进程调度

对 N 个进程应用模拟五种不同的进程调度算法,包括先来先服务(FCFS)、短进程优先(SJF)、时间片轮转(RR)、高响应比优先(HRRN)、动态优先级调度(PR)。2024年末《操作系统》课程设计大作业 模拟进程调度 对 N 个进程应用模拟五种不同的进程调度算法,包括先来先服务(F…

VAE模型简析和精要(原理和代码)

1. 前言 这篇博客主要用于记录VAE的原理部分。 一方面便于日后自己的温故学习,另一方面也便于大家的学习和交流。 如有不对之处,欢迎评论区指出错误,你我共同进步学习! 图均引用自4部分的博客!!!!!!! 2. 正文 这篇博客集各博客之长,比较简洁易懂:因为有的博客交代…

有效提高BT下载速度:Tracker 正确的使用知识

前言BT下载对 DHT 和 Tracker 是非常依赖,然而网上有不少的 Tracker 列表项目,每次更新都要全部添加一遍,显然很麻烦。那么就需要一个每次可以一键更新的列表页面,每天自动获取热门 Tracker 列表项目,去重复后制作成一个 Tracker 列表合集。这样你不需要去一个个添加 Trac…

通过sqlserver添加修改系统管理员账号.120510

一,思路: 在其他服务器B,通过sa账号远程登录需增加/修改系统管理员账号的服务器A,用sqlserver的xp_cmdshell功能,模拟cmd通过net user命令进行添加或者修改系统管理员账号。 特别提示:此解决方案只适用于服务器没有加域且忘记管理员密码的用户使用,请不要作为非法用途!…

MySQL架构总览_查询执行流程_SQL解析顺序

目录MySQL 架构总览查询执行流程连接处理结果SQL 解析顺序准备工作FROMWHEREGROUP BYHAVINGSELECTORDER BYLIMIT总结参考书籍 MySQL 架构总览 架构最好看图,再配上必要的说明文字。 下图根据参考书籍中一图为原本,再在其上添加上了自己的理解。从上图中我们可以看到,整个架构…

BAT批处理文件语法.061024

bat是dos下的批处理文件 .cmd是nt内核命令行环境的另一种批处理文件。 从更广义的角度来看,unix的shell脚本以及其它操作系统甚至应用程序中由外壳进行解释执行的文本,都具有与批处理文件十分相似的作用,而且同样是由专用解释器以行为单位解释执行,这种文本形式更通用的称谓…

AD中域用户密码策略不生效的解决方案.100301

每到一个月,AD就会提示修改密码,改就改吧,但是还提示一些乱七八糟的规则。 我把这些规则都禁用或是设为没有定义了,但还是报“不能和之前的0个密码相同”, 最后, 解决方案: 在域控制器服务器中: 运行以下指令: gpupdate /force好了。.zstitle { width: 280px; text-al…

金融波动率的多模型建模研究:GARCH族与HAR模型的Python实现与对比分析

金融资产波动率建模在现代金融工程中具有重要地位,其应用涵盖风险管理、衍生品定价和投资组合优化等核心领域。本文着重探讨三种主流波动率建模方法:广义自回归条件异方差模型(GARCH)、Glosten-Jagannathan-Runkle-GARCH模型(GJR-GARCH)以及异质自回归模型(HAR)。本文将系统阐…

「youlai-boot」入门篇:从0到1搭建 Java、Spring Boot、Spring Security 企业级权限管理系统

本文介绍了如何使用 Java 和 Spring Boot 3,从零开始构建开源的 youlai-boot 企业级权限管理系统,涵盖 MySQL、Redis 整合及 Spring Security,适合前端开发者和后端初学者。🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-u…

SQL Server 2005部署备份任务.120308

环境:SQL Server 2005 任务: 1,每日凌晨1点给本地sql server做本地完整备份,且只保留7天的本地备份; 2,每日凌晨5点将本地的备份打包上传到存储服务器。 思路: 1,sql server2005不支持SQL Server2000的sqlmaint命令,所以,需要通过数据库维护计划进行备份,而不能一步…