实际上权限系统老早以前我就在一直开发,大概在刚毕业没多久就想一我的写一个系统,断断续续一直坚持到如今,毕竟本身亲动手自写的系统才有收获,本篇仅介绍权限。前端
小小系统上不了台面,望各位大神勿喷。git
目前采用的是.Net Core微服务的方式实现,本文不讨论具体的中间件主要是(ocelot + consul等),一直参考微软的 eShopOnContainers ,进行简单的实现,可是ORM是用的Dapper,并简单进行封装 传送门 ,固然本身也封装了一些简单的插件进行复用:传送门,以下:github
权限系统实现很简单,权限的划分我以为能够分为三种:数据库
一、菜单权限二、按钮权限三、数据权限后端
简单介绍下:一、菜单权限。表示用户是否可以访问该页面(角色挂钩)安全
二、按钮权限。表示用户是否可以操做该页面上的功能(角色挂钩)app
三、数据权限。表示用户访问页面时进行数据筛选(该功能暂未实现,这个要与具体的业务结合才能写),与部门挂钩,这个不太好理解,固然通常的权限系统这个功能也不会作,举个简单例子,OA系统里面我查看个人工资条,我应该只能看到我本身的数据,可是个人部门经理,他能够有权限看到该部门的所有数据,这个就是数据权限。框架
为何写这个系统?async
以前待过好几家公司,发现他们的系统都是对菜单进行分配,固然了,业务需求只要这个就当我没说,我只是以为这样作太不安全而且我以为以前系统的实现方式能够进行一些优化,因此就一直写到如今,可能代码质量不如哪些大神的优秀,系统在我看来过小,就简单搭了个框架实现。你过条小水沟,不必造条桥。微服务
要使用该系统前提条件:前端:Sea.js和Vue,对于sea.js,在前端这块感受已经没多少人用了,可是这中CMD思想是不会被淘汰的,你看最近比较火的layerui也是的,对于Vues只是简单的应用,也就用到双向绑定而已,开发复杂的页面确实比较方便,可是简单的页面就得不偿失了。
后端:consul、rabbitmq ,具体怎么安装不在描述
大概的用户访问流程描述以下:
用户登陆 =====》 获取该用户角色 ===》 经过角色获取该角色对应的权限 并集 ===>返回相应数据
sys_user_role sys_role_resource
系统关系图以下(MySQL):
具体功能实现请看代码,这里不作阐述,菜单权限的分配经过角色表和菜单表的关联表操做便可,可是按钮的权限分配如何实现?个人实现方式是:把按钮的操做也当作一种菜单的资源分配,只不过比较特殊,我这里不只仅是对按钮的显示进行控制,我作的比较绝,也对后台方法访问权限也作了控制,这样比较安全,对于按钮权限的控制,其实是明确的,比方说,一个删除按钮,它只能对应后台的一个删除方法,这个方法是明确的,对于页面的按钮的类型和个数是固定的,否则你没办法分配,基于这个前提,我对菜单的生成进行代码控制从而达到控制目的,所以,菜单和按钮和在一块儿称之为资源表 sys_resource 。具体的实现代码也不是很复杂,一层一层判断便可,权限过滤器以下:
1 public class PermissionAuthorizationRequirement : IAuthorizationRequirement 2 { 3 public UrlAndButtonType UrlAndButtonType { get; } 4 5 public PermissionAuthorizationRequirement(string url, ButtonType buttonType, bool isPage) 6 { 7 UrlAndButtonType = new UrlAndButtonType() 8 { 9 Url = url, 10 ButtonType = (byte)buttonType, 11 IsPage = isPage 12 }; 13 } 14 public PermissionAuthorizationRequirement(string url, byte buttonType, bool isPage) 15 { 16 UrlAndButtonType = new UrlAndButtonType() 17 { 18 Url = url, 19 ButtonType = buttonType, 20 IsPage = isPage 21 }; 22 } 23 } 24 /// <summary> 25 /// 权限过滤器 26 /// </summary> 27 [Authorize] 28 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 29 public sealed class PermissionAttribute : TypeFilterAttribute 30 { 31 /// <summary> 32 /// 构造器 33 /// </summary> 34 /// <param name="url">地址</param> 35 /// <param name="buttonType">按钮类型</param> 36 /// <param name="isPage">是不是页面</param> 37 public PermissionAttribute(string url = default(string), ButtonType buttonType = ButtonType.View, bool isPage = true) : 38 base(typeof(RequiresPermissionAttributeExecutor)) 39 { 40 Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) }; 41 } 42 /// <summary> 43 /// 构造器 44 /// </summary> 45 /// <param name="url">地址</param> 46 /// <param name="buttonType">按钮类型</param> 47 /// <param name="isPage">是不是页面</param> 48 public PermissionAttribute(string url, byte buttonType, bool isPage = true) : 49 base(typeof(RequiresPermissionAttributeExecutor)) 50 { 51 Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) }; 52 } 53 54 private class RequiresPermissionAttributeExecutor : Attribute, IAsyncResourceFilter 55 { 56 private IPermissionStorageContainer _permissionStorage; 57 private PermissionAuthorizationRequirement _requiredPermissions; 58 59 public RequiresPermissionAttributeExecutor( 60 IPermissionStorageContainer permissionStorage, PermissionAuthorizationRequirement requiredPermissions) 61 { 62 _permissionStorage = permissionStorage; 63 _requiredPermissions = requiredPermissions; 64 } 65 66 public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) 67 { 68 string menuUrl = _requiredPermissions.UrlAndButtonType.Url; 69 //判断用户权限 70 if (string.IsNullOrEmpty(menuUrl)) 71 { 72 //区域判断 73 string area = context.RouteData.Values["area"].ToString(); 74 if (string.IsNullOrEmpty(area)) 75 { 76 menuUrl = "/" + context.RouteData.Values["controller"] + "/" + context.RouteData.Values["action"]; 77 } 78 else 79 { 80 menuUrl = "/" + area + "/" + context.RouteData.Values["controller"] + "/" + context.RouteData.Values["action"]; 81 } 82 } 83 menuUrl = menuUrl.Trim().ToLower(); 84 var dbpermission = await _permissionStorage.GetPermissionAsync(); 85 var menu = dbpermission.Menus.FirstOrDefault(m => m.MenuUrl != null && m.MenuUrl.Trim().ToLower() == menuUrl); 86 if (menu != null)//地址存在 87 { 88 if (_requiredPermissions.UrlAndButtonType.ButtonType == default(byte)) 89 { 90 await next(); 91 } 92 else 93 { 94 byte buttonType = (byte)_requiredPermissions.UrlAndButtonType.ButtonType; 95 if (menu.MenuButton.Select(m => m.ButtonType).Contains(buttonType))//拥有操做权限 96 { 97 await next(); 98 } 99 else 100 { 101 //没有操做权限 102 if (_requiredPermissions.UrlAndButtonType.IsPage) 103 { 104 context.Result = new RedirectResult("/error/noauth"); 105 } 106 else 107 { 108 context.Result = new ContentResult() 109 { 110 Content = PermissionStatusCodes.Status2Unauthorized.ToString() 111 }; 112 } 113 await context.Result.ExecuteResultAsync(context); 114 } 115 } 116 } 117 else 118 { 119 //没有操做权限 120 if (_requiredPermissions.UrlAndButtonType.IsPage) 121 { 122 context.Result = new RedirectResult("/error/noauth"); 123 } 124 else 125 { 126 context.Result = new ContentResult() 127 { 128 Content = PermissionStatusCodes.Status2Unauthorized.ToString() 129 }; 130 } 131 await context.Result.ExecuteResultAsync(context); 132 } 133 } 134 } 135 136 }
在对于的页面添加过滤器便可,以下:
1 [HttpGet] 2 [Permission] 3 public async Task<IActionResult> Index(int pageIndex=1,int pageSize=10) 4 { 5 var res = await _messageService.GetPageAsync(pageIndex, pageSize); 6 return View(res); 7 } 8 [HttpGet] 9 [Permission("/Sys/Message/Index", ButtonType.View)] 10 public IActionResult Show() 11 { 12 return View(); 13 }
系统界面展现图:后台模板是以前从网上找的并本身简单改了一下,将就能看吧,实在不想花功夫在前端上面了@-^-@
运行步骤:一、确保数据库mssystem和mssystemlog存在 github文档中
二、consul服务启动,以下回车运行
三、VS项目启动
管理员登陆帐号wms,密码:全部帐号密码都是123
代码地址:
https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps
若是以为有点做用的话,能够 start 下,后续会持续更新