用最基本的EF+MVC+JQ+AJAX+bootstrap实现权限管理的简单实例 之登陆和操作权限

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用最基本的EF+MVC+JQ+AJAX+bootstrap实现权限管理的简单实例 之登陆和操作权限相关的知识,希望对你有一定的参考价值。

先来一堆关于上篇文章的废话(不喜者点此隐藏)

今天把博客加了个小功能,就是点标题可以隐藏或展示相关内容,做法很傻,就是引用了bootstrap的两个css类和全部的js文件,其实这样的小功能完全应该自己做的,主要还是因为前端差,还有就是懒。请大家不要太过在意命名和前端样式,我并没有进入公司工作,命名没有具体规范,都是随心所欲。前端实在太差,如果你觉得颜色样式太差,只能说明我的审美有问题,咱们主要看功能实现。

上篇文章发布后有一定的推荐量和浏量,对一个初学者来说,自认为还是挺不错的。最主要的是收到了不少的评论,有鼓励的,有建议的,当然也有反对的。最重要的是有一大牛级的人物对我表示了特别的关心,此人工作经验大于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; }
    }
}
User
技术分享
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>();        
    }
}
Role
技术分享
using YZY_RBAC.Core;

namespace Domain.DataModels
{
    public class Relevance:BaseRelevance<int>
    {
        public Relevance() : base(0) { }
    }
}
Relevance
技术分享
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; }
    }
}
Module
技术分享
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; }
    }
}
Menu
技术分享
using YZY_RBAC.Core;

namespace Domain.DataModels
{
    public class HideFiled:BaseHideFiled<int>
    {
        public HideFiled() : base(0) { }
    }
}
HideFiled
技术分享
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; }

    }
}
Employee
技术分享
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; }
    }
}
Deparentment
技术分享
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; }
    }
}
Dep_Role
技术分享
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; }
    }
}
Dep_Emp
技术分享
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; }
    }
}
ActionButton

 上面这些代码这真心是蛮不好意思贴出来的。服务类就是些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);
            }
        }

    }
}
AuthorizeService
技术分享
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实现权限管理的简单实例 之登陆和操作权限的主要内容,如果未能解决你的问题,请参考以下文章

带有 jQ​​uery Ajax 调用的 MVC 无法正确绑定空数组/可枚举

面经jq 中 ajax 和 axios 区别,瀑布流布局,添加删除事件

如何在 Java Spring Boot MVC 中使用 Ajax 删除多个项目

spring boot 和 spring MVC 使用的和配置的区别。

MVC总结

MVC5+EF+AutoFac+AutoMapper轻型架构