用最基本的EF+MVC+JQ+AJAX+bootstrap实现权限管理的简单实例 之登陆和操作权限
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用最基本的EF+MVC+JQ+AJAX+bootstrap实现权限管理的简单实例 之登陆和操作权限相关的知识,希望对你有一定的参考价值。
先来一堆关于上篇文章的废话(不喜者点此隐藏)
上篇文章发布后有一定的推荐量和浏量,对一个初学者来说,自认为还是挺不错的。最主要的是收到了不少的评论,有鼓励的,有建议的,当然也有反对的。最重要的是有一大牛级的人物对我表示了特别的关心,此人工作经验大于10年,准备单干。加了QQ聊了一阵,当然不是技术讨论,我估计也不够格跟他谈,主要是他表示关心和鼓励,虽然没有收留我的意向,再此仍深表谢意。以后可能会有技术问题咨询。我写文章最大的意图和乐趣在于把自己的想法写出来,然后希望能收获到比较好的建议和指正,从中找出自己的毛病,扩大自己的视野。
评论中,其实也就那么一两条是关于我实现想法的建议。其中有一个说到了操作权限,以下是原文引用:
“博主这个思路有个很大的问题,就是很难收敛。举个栗子吧,一个订单管理有新建、编辑、删除、归档这些操作,这样的聚合根就很难应用你的权限。增删改查很容易对应数据库操作,但是归档是个什么鬼?从业务上来看,归档就是把订单从业务库移动到历史库里面去,又增又删,还操作两个库。我看不出来你的系统能解决这个问题”
另外的都是觉得权限是与业务藕合,根本无法抽象。其中一个原文引用:
“学生信息管理:
班主任看自己所带班级的
年级组长看自己所担任年级的
校领导看全校的
而这种业务数据的界定,根本不是你角色控制的。
这是职务or岗位权限!
属于业务的一部分!
如果你要角色控制,也最多这这三种角色,但是你最后还是需要有职务信息(这种业务数据)来辅助你实现数据查询边界
还有一个特别懂我的人,相信他应该是认真的看过了,才会如此懂我。原文:
“反对的原因无非是无法完全抽象
一个行业有一个行业的权限
不同行权限有细微而又本质的区别导致无法抽象共用
也即是说 对象可能是这样 但是对对象的限制 层级关系 相互约束确是千奇百怪的
不过这个本文其实没有太大联系 博主写的权限 相信最终还是依赖其公司所面向的行业 而能从中看到些什么 从而优化我们的自己的权限 我觉得才是最重要的”
在这些评论后,我马上想到了我所忽略的真正重要的问题。于是在原来的基础上加了一个User的抽象类,一个角色抽象类,一个处理登陆用户权限的类,把用户的权限和他拥有的角色的权限综合。对于数据权限我目前有一个自认为比较可行的抽象方案,但是本文没有涉及,我想着如果这篇能有比较好的反响的话,我想另外写篇讨论一下。
细谈我对权限系统的简单理解
还是先申明一下:以下观点纯属性一个业外人士胡思乱想,不保证实用价值。如果你认同本人观点,你可以在此基础上进行优化。如果你认为有需要改造的地方,还请不吝赐教。
对于MVC服务系统,操作权限主要就是在登陆用户和一个具体控制器方法之间发生,我把一个方法设计成界面的一个按钮(操作权限),而按钮又被包含在一个菜单中(菜单权限),菜单又被包含在一个模块中。模块是一个比较大的权限,我在例子中主要是用来对用户身份做一个区分,比如有以开发人员身份登陆,我们就有一个开发人员功能模块,有管理员登陆的管理员模块,模块主要针对的是一个MVC的控制器。菜单主要用于区分一个模块下管理的对象,比如管理的角色,就有角色管理菜单,管理的是部门,就有一个部门管理菜单,这个菜单下的方法,也就是这个菜单界面的按钮,主要就是针对这个对象的一些特定操作。下面是一张草图,以展示从用户到一个具体操作的流程。但愿这段文字加上下面的图能表达出我的思路。对于操作权限的实现,我便是依据这个图来开展的。
一个用户在登陆后,他拥有的模块,菜单及菜单下的按钮已经被加载好了,而界面部分已经针对他的菜单权限有了不同的展示,所有可操作权限按菜单分类分配到不同菜单下对应的按钮。下面展示一下我实现的部分小功能效果展示。请不要介意界面的好看与否。主要看不同点,红色框起部分,几乎每个地方都有不同的。但是在同一个页面,对于小实例,我认为一个页面足已应付各种不同身份的登陆用户。由于我认为模块,菜单和按钮的管理是应该由开发人员来执行的,而权限对于开发人员是没有约束的,所以开发人员模块下,有两个固定的按钮,一个是添加,一个是授权。而其它身份的用户则不应该有操作这些的权限。
看看开发人员登陆点开发人员模块下的模块管理菜单效果
点管理员模块下的角色管理菜单下的变化
一般的无权限的用户登陆后的界面
在浏览器地址栏输入地址访问无权限的操作
搞一个开发者授权的菜单出来,列出所有的用户,给用户分配最大的管理权限
搞清流程和职责,代码实现喜欢怎么搞就怎么搞
到目前为止,开发人员这块我感觉差不多了。开发人员的职责无非就是按业务需要和逻辑开发一个MVC控制器和控制器下的方法,这跟平常的没有权限管理是一样一样的,如果要加权限管理,你只需要把它逻辑分类,保存到数据库,加上一些验证标签然后扔给前前端设计和管理员去管理分配。
模型都是最基本的属性,全部用的贫血的,如果用充血模型,API逻辑应该更清晰。比如用户隐藏字段的权限,可以写到用户类中去的。调用仓储接口来持久化,你可以用user.HideFiledToxx。
不多说了。我是把上篇文章的EF操作和水货RBAC生成DLL引用到这是测试项目来的。
后端就两个项目,非常简单。Domain层就一个服务是对上篇的进行二次封装,以持久化到数据库,另外一个是登陆用户服务类这两个类比较特别。其它都是一般的服务。这两个点是根据业务和数据库变化的。
namespace Domain.DataModels { public class User:YZY_RBAC.Core.YZYUser<int> { public User() : base(0) { } public string Name { get; set; } public string Telphone { get; set; } } }
using System.Collections.Generic; using YZY_RBAC.Core; namespace Domain.DataModels { public class Role:YZYRole<int> { public Role() : base(0) { } /// <summary> /// 指定角色可操作的门 /// </summary> public virtual ICollection<Dep_Role> ManageDeps { get; set; } = new List<Dep_Role>(); } }
using YZY_RBAC.Core; namespace Domain.DataModels { public class Relevance:BaseRelevance<int> { public Relevance() : base(0) { } } }
using System.Collections.Generic; using YZY_RBAC.Core; namespace Domain.DataModels { public class Module:BaseEntity<int> { public Module() : base(0) { } public string Name { get; set; } public virtual ICollection<Menu> Menus { get; set; } public string Type { get; set; } public bool IsAdminModule { get; set; } public string Url { get; set; } } }
using System.Collections.Generic; using YZY_RBAC.Core; namespace Domain.DataModels { public class Menu:BaseEntity<int> { public Menu() : base(0) { } public string Url { get; set; } public string RoleType { get; set; } public virtual ICollection<ActionButton> Buttons { get; set; } = new List<ActionButton>(); public string Name { get; set; } public virtual Module Module { get; set; } public int? ModuleId { get; set; } } }
using YZY_RBAC.Core; namespace Domain.DataModels { public class HideFiled:BaseHideFiled<int> { public HideFiled() : base(0) { } } }
using System.Collections.Generic; namespace Domain.DataModels { public class Employee:User { public virtual ICollection<Dep_Emp> Depatments { get; set; } = new List<Dep_Emp>(); /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 性别 /// </summary> public string Gender { get; set; } /// <summary> /// 年龄 /// </summary> public int Age { get; set; } /// <summary> /// 工资 /// </summary> public double Salary { get; set; } } }
using System.Collections.Generic; using YZY_RBAC.Core; namespace Domain.DataModels { public class Deparentment:BaseEntity<int> { public Deparentment() : base(0) { } public string Name { get; set; } public string Number { get; set; } public int? ParentId { get; set; } public virtual Deparentment Parent { get; set; } public virtual ICollection<Dep_Emp> Employees { get; set; } = new List<Dep_Emp>(); public virtual ICollection<Dep_Role> Roles { get; set; } = new List<Dep_Role>(); public virtual ICollection<Deparentment> Childs { get; set; } = new List<Deparentment>(); public bool IsTop { get; set; } } }
using YZY_RBAC.Core; namespace Domain.DataModels { public class Dep_Role:BaseEntity<int> { public Dep_Role() : base(0) { } public int DepId { get; set; } public virtual Deparentment Dep { get; set; } public int RoleId { get; set; } public virtual Role Role { get; set; } } }
using YZY_RBAC.Core; namespace Domain.DataModels { public class Dep_Emp:BaseEntity<int> { public Dep_Emp() : base(0) { } public Deparentment Dep { get; set; } public int DepId { get; set; } public Employee Emp { get; set; } public int EmpId { get; set; } } }
using YZY_RBAC.Core; namespace Domain.DataModels { public class ActionButton:BaseEntity<int> { public ActionButton() : base(0) { } public int? MenuId { get; set; } public virtual Menu Menu { get; set; } public string Url { get; set; } public string Icon { get; set; } public string RoleType { get; set; } public string Name { get; set; } } }
上面这些代码这真心是蛮不好意思贴出来的。服务类就是些CURD我就不贴出来了,把两个特别点的贴出来。AuthorizeService就是个授权服务,将两个实体间的权限通过权限类型进行关联。LoginUserService是根据当前登陆用户的身份加载用户的所有权限集合,供前端用。但是我没有写测试代码,功能现在还不知道有没有问题。
using Domain.DataModels; using System; using System.Collections.Generic; using System.Linq; using YZY_RBAC.Core; namespace Domain.Service { public class AuthorizeService<TEntity>where TEntity:BaseEntity<int> { public static RBACManage<TEntity, int> rbac { get; set; } = new RBACManage<TEntity, int>(); private static RelevanceService releService; private static HideFiledService hideService; /// <summary> /// 初始化RBAC /// </summary> public static void InitiaRbac() { releService = new RelevanceService(); hideService = new HideFiledService(); rbac.RelevanceQuery = releService.Find(); rbac.HideFiledQuery = hideService.Find(); } /// <summary> /// 为当前实体授权指定的权限 /// </summary> /// <param name="entity"></param> /// <param name="per"></param> /// <param name="type"></param> public static void Authorize<TPer>(TEntity entity,TPer per,PermissionType type)where TPer:BaseEntity<int> { var rel = rbac.GetBindRelevance(entity, new Relevance(), per, type) as Relevance; if (!releService.Find().Any(o => o.EntityId == entity.Id && o.PermisId == per.Id)) releService.Add(rel); releService.Commit(); InitiaRbac(); } /// <summary> /// 为当前实体授权指定的权限 /// </summary> /// <param name="entity"></param> /// <param name="pers"></param> /// <param name="type"></param> public static void Authorize<TPer>(TEntity entity,IEnumerable<TPer>pers,PermissionType type) where TPer : BaseEntity<int> { if(pers!=null&&pers.Count()>0) { foreach(var p in pers) { var rel = rbac.GetBindRelevance(entity, new Relevance(), p, type) as Relevance; if (!releService.Find().Any(o => o.EntityId == entity.Id && o.PermisId == p.Id)) releService.Add(rel); } releService.Commit(); InitiaRbac(); } } /// <summary> /// 设置当前实体对其它实体隐藏指定的权限 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="per"></param> /// <param name="filedName"></param> public static void SetHideFiledToOther<T>(TEntity entity, T per, string filedName) where T:BaseEntity<int> { //保存申请的隐藏字段权限 var hide = rbac.GetRegisteHideFiled(entity, filedName, new HideFiled()) as HideFiled; if (!hideService.Find().Any(o => o.EntityId == entity.Id && o.HidePropertyName == filedName)) { hideService.Add(hide); hideService.Commit(); InitiaRbac(); } var rel = rbac.GetBindHideFiledToOtherEntityRelevance(entity, new Relevance(), per, filedName); //如数据库中不存在这个关联,则添加 if (!releService.Find().Any(o => o.EntityId == per.Id && o.PermissionType == PermissionType.Propety.ToString() && o.PermisId == hide.Id)) { releService.Add(rel as Relevance); } releService.Commit(); InitiaRbac(); } /// <summary> /// 设置当前实体对其它实体隐藏指定的权限 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="filedName"></param> /// <param name="others"></param> public static void SetHideFiledToOther<T>(TEntity entity,string filedName,IEnumerable<T>others)where T:BaseEntity<int> { if(others!=null&&others.Count()>0) { foreach(var o in others) { SetHideFiledToOther(entity, o, filedName); } } } /// <summary> /// 取消隐藏字段 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="filedName"></param> public static void ClearHideFiled(TEntity entity, string filedName) { if (hideService.Find().Any(o => o.EntityId == entity.Id && o.HidePropertyName == filedName)) { var hide =hideService.Find().Where(o => o.EntityId == entity.Id && o.HidePropertyName == filedName).FirstOrDefault(); hideService.Remove(hide); if (releService.Find().Any(o => o.PermisId == hide.Id && o.PermissionType == PermissionType.Propety.ToString())) { var rels = releService.Find().Where(o => o.PermisId == hide.Id && o.PermissionType == "Porpety").ToList(); foreach (var r in rels) { releService.Remove(r); } releService.Commit(); hideService.Commit(); InitiaRbac(); return; } } throw new Exception("没有找到指定的字段"); } /// <summary> /// 取消对指的其它实体的隐藏字段 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="filedName"></param> /// <param name="other"></param> public static void ClearHideFiledToOther<T>(TEntity entity,string filedName,T other)where T:BaseEntity<int> { if(hideService.Find().Any(o=>o.EntityId==entity.Id&&o.HidePropertyName==filedName)) { var hide = hideService.Find().Where(o => o.EntityId == entity.Id && o.HidePropertyName == filedName).FirstOrDefault(); hideService.Remove(hide); if(releService.Find().Any(o=>o.EntityId==other.Id&&o.PermisId==hide.Id&&o.PermissionType==PermissionType.Propety.ToString())) { var rel = releService.Find(o => o.EntityId == other.Id && o.PermissionType == "Propety" && o.PermisId == hide.Id).FirstOrDefault(); releService.Remove(rel); releService.Commit(); } hideService.Commit(); InitiaRbac(); return; } throw new Exception("没有找到指定的字段"); } /// <summary> /// 取消对指的其它实体的隐藏字段 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="filedName"></param> /// <param name="others"></param> public static void ClearHideFiledToOther<T>(TEntity entity, string filedName, IEnumerable<T>others) where T : BaseEntity<int> { if(others!=null&&others.Count()>0) { foreach (var o in others) ClearHideFiledToOther(entity, filedName, o); } } /// <summary> /// 取消当前实体的指定授权 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="other"></param> /// <param name="relFilter"></param> public static void ClearAuthorizetion<T> (TEntity entity,T other,Func<Relevance,bool>relFilter=null) where T:BaseEntity<int> { Relevance rel = null; if(relFilter==null) { if(releService.Find(o=>o.EntityId==entity.Id&&o.PermisId==other.Id).Any()) { rel = releService.Find(o => o.EntityId == entity.Id && o.PermisId == other.Id).FirstOrDefault(); releService.Remove(rel); releService.Commit(); InitiaRbac(); return; } } if(releService.Find(o=>o.EntityId==entity.Id&&o.PermisId==other.Id).AsEnumerable().Where(relFilter).Any()) { rel = releService.Find(o => o.EntityId == entity.Id && o.PermisId == other.Id).AsEnumerable().Where(relFilter).FirstOrDefault(); releService.Remove(rel); releService.Commit(); InitiaRbac(); return; } throw new Exception("没有找到指定的关联"); } /// <summary> /// 取消当前实体的指定授权 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="others"></param> /// <param name="relFilter"></param> public static void ClearAuthorizetion<T> (TEntity entity, IEnumerable<T> others, Func<Relevance, bool> relFilter = null) where T : BaseEntity<int> { if(others!=null&&others.Count()>0) { foreach (var o in others) ClearAuthorizetion(entity, o, relFilter); } } } }
using Domain.DataModels; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using YZY.Domain; using YZY_RBAC.Core; namespace Domain.Service { public class LoginUserService { public LoginUserService(User loginUser) { mage = new LoginUserMagage<int>(user); mage.HideFiledQuery = new HideFiledService().Find(); mage.RelevanceQuery = new RelevanceService().Find(); mage.RoleQuery = new RoleService().Find(); } private User user = UserManage<int>.GetCacheUser() as User; private Pager pager; private LoginUserMagage<int> mage; private List<ActionButton> btns = new List<ActionButton>(); private List<Deparentment> deps = new List<Deparentment>(); private List<Employee> ems = new List<Employee>(); private List<Menu> menus = new List<Menu>(); private List<Role> roles = new List<Role>(); private List<Module> modules = new List<Module>(); private List<User> users = new List<User>(); #region 属性 /// <summary> /// 当前登陆用户 /// </summary> public User LoginUser { get { return user; } } /// <summary> /// 当前登陆用户可访问或操作的按钮 /// </summary> public IEnumerable<ActionButton> Buttons { get { return btns; } } /// <summary> /// 登陆用户可访问的模块 /// </summary> public IEnumerable<Module> Modules { get { return modules; } } /// <summary> /// 当前登陆用户可访问或操作部门 /// </summary> public IEnumerable<Deparentment> Departs { get { return deps; } } /// <summary> /// 当前登陆用户可访问或操作员工 /// </summary> public IEnumerable<Employee> Employees { get { return ems; } } /// <summary>
以上是关于用最基本的EF+MVC+JQ+AJAX+bootstrap实现权限管理的简单实例 之登陆和操作权限的主要内容,如果未能解决你的问题,请参考以下文章
带有 jQuery Ajax 调用的 MVC 无法正确绑定空数组/可枚举
面经jq 中 ajax 和 axios 区别,瀑布流布局,添加删除事件
如何在 Java Spring Boot MVC 中使用 Ajax 删除多个项目