数据权限设计——基于EntityFramework的数据权限设计方案:一种设计思路

 前言:“咱们有一个订单列表,但愿可以根据当前登录的不一样用户看到不一样类型的订单数据”、“咱们但愿不一样的用户能看到不一样时间段的扫描报表数据”、“咱们系统须要不一样用户查看不一样的生产报表列”。诸如此类,最近常常收到项目上面的客户提出的这种问题,即所谓的“数据权限”,通过开会讨论决定:在目前的开发框架上面搭建一套通用的数据权限功能。html

本文原创地址:http://www.cnblogs.com/landeanfen/p/7760803.htmlsql

1、大话权限模块

有了上面的引言,天然而然就引出了今天须要和你们讨论的话题——数据权限。做为开发人员,咱们确定知道,通常的系统都离不开权限模块,它是支撑整个系统运行的基础模块。而根据项目类型和需求的不一样,权限模块的设计更是截然不同。但无论怎么变,权限模块从大的方面来讲,能够分为两种大的类型:功能权限数据权限数据库

  • 功能权限:主要控制不一样的资源主体(用户、角色、组织等)有操做不一样的资源的权限。好比常见的不一样的角色能访问不一样的页面(菜单权限),以及具备操做同一页面的不一样功能(按钮权限)等等,都数据功能权限的范畴,这种设计相对比较简单,也比较为大多数系统所通用。固然网上资料、设计思路也能够找到不少。
  • 数据权限:主要控制不一样的资源主体(用户、角色、组织等)有查看不一样的数据信息的权限,通常来讲,数据权限又分为数据行权限数据列权限,经过字面意思不难理解这二者的区别,好比上文“咱们有一个订单列表,但愿可以根据当前登录的不一样用户看到不一样类型的订单数据” 这就是一个典型的数据行权限,而“咱们系统须要不一样用户查看不一样的生产报表列”这就是数据列权限的范畴。因为数据权限和系统的业务逻辑关系很是密切,因此不一样的系统设计差别性会很是大。从另外一方面来讲,因为数据权限和业务逻辑关联性很是强,若是系统的业务逻辑很是复杂,数据权限设计起来也会相对复杂,因此关于数据权限的设计一直没有一种相对通用和使用简单的设计方案。

若是你动手去设计数据权限,当你去各大平台、百度、谷歌查找设计思路的时候,你会发现很难找到有用的资料,不少设计思路局限性很是大。其实缘由很简单:数据权限的设计和他人系统关系紧密,通常不太容易拿到你的项目上面直接使用。框架

固然也有另外部分人说“数据权限并不能做为权限模块去设计”,好比博主看到这样一条评论post

从必定程度上来讲,这样理解也不为过,若是你以为你的系统灵活性和配置性不须要那么高,把数据权限的规则在代码里面写死又何妨,博主所在公司的另一个部门就是这么干的,除了编码量大一点,其实也没什么太大的问题!其实博主说这么多无非是想表达一个观点:没有绝对通用的数据权限设计思路,关键看合不合你用!固然本文的设计思路也是同样,不强制要求,不提通用。设计思路供你参考!测试

2、一种设计思路

关于权限设计的杂谈就告一段落,凡是点到即止,再说了多了就说烂了。到目前为止,博主找到的一篇写得相对比较好的文章 通用权限管理设计 之 数据权限。这位博主是用sql去实现的,若是是把这个运用到EF里面的话,考虑到EF复杂的导航属性,会有一些问题。接下来讲说博主这边想到的设计思路。编码

先说说博主所在项目的状况,和数据打交道的部分采用EntityFramework+Repository的传统模式去实现的,整个项目从上到下,就是一种典型的"伪DDD",什么是”伪DDD“?这里不作过多说明,使用过DDD的同仁应该很清楚。下面是设计思路流程图:url

第一步:配置数据规则
spa

第二步:页面使用数据规则设计

 

 以上是一个大体的思路图,总的来讲,要实现基于EF的数据权限设计,主要分为两大步骤

一、配置数据规则

配置数据规则这里有三个大的方面:功能模块数据资源角色

  • 功能模块:为何这里要加上功能模块的约束?是由于博主以为咱们某一个页面在查询数据的时候,会有一个查询的范围,好比订单查询页面确定只能查询和订单有关联的实体功能,而不可能查询和它没有任何关联的业务。加这个约束更大的意义在于咱们动态的构造Lambda去查询实体的时候不会产生”找不到相关联的实体“之类的错误
  • 数据资源:具体对哪一种数据资源作数据权限,好比订单的状态不等于取消状态、订单的下单时间小于当前月份等等。
  • 角色:数据资源的主体,还能够是用户、部门、组织等等。

这三者配置以后获得的一个结果就是某一类角色的某一个功能模块对哪一个数据资源的数据规则是什么样的。好比有一条销售总监的数据规则,配置销售总监在订单模块里面订单这个实体的订单类型是销售订单的全部数据,这就是针对销售总监在订单模块的数据规则。可能最终数据库存储获得的数据相似这样:

RoleId FunctionCode Rules
2 OrderQuery

{"rules":[{"field":"Order_Status","operate":"in","value":"[0,1,2]"},{"field":"Order_Type","op":"equal","value":"1"}],"logicoperate":"and"}

3 OrderQuery

{"rules":[{"field":"Order_Status","operate":"in","value":"[0]"},{"field":"Product.Categary.Type","equal":"equal","value":"1"}],"logicoperate":"and"}

5 Product

{"groups":[

{"rules":[{"field":"Order_Status","operate":"in","value":"[0,5,10]"},{"field":"Order_Type","op":"equal","value":"1 "}],"logicoperate":"and"},

{"rules":[{"field":"LineName","operate":"equal","value":"fenzhuangxian"}]}

],"logicoperate":"or"}

 

须要特别说明的是:因为EF有导航属性,这里的Rules在保存的时候若是遇到导航属性,咱们的字段值须要这样保存——Product.Categary.Type。由于在咱们转换成为lambda表达式的时候导航属性会是这样写:x=>x.Product.Categary.Type==1。这个咱们在后面使用这个规则的时候加以说明。

 

二、使用数据规则

 有了上面的数据规则,接下来就是咱们在取数据的时候如何使用了,这里有一点须要说明的是:咱们这里须要传两个参数,一个是模块的名称,好比上面的OrderQuery、Product等;第二个是当前用户的角色id,这个能够经过当前登录用户的id获取到角色。

要使用数据规则,以前博主分享过两篇关于动态Lambda的文章,如今派上用场了。只不过原来只是一些基础类型转lambda,如今涉及到了导航属性,不知道是否可行。博主查阅了一些资料,最终找到了解决方案。

     //遍历获得属性(包括遍历导航属性)
        public Expression GetProperty(Expression source, ParameterExpression para, string Name)
        {
            string[] propertys = Name.Split('.');
            if (source == null)
            {
                source = Expression.Property(para, typeof(Entity).GetProperty(propertys.First()));
            }
            else
            {
                source = Expression.Property(source, propertys.First());
            }
            foreach (var item in propertys.Skip(1))
            {
                source = GetProperty(source, para, item);
            }
            return source;
        }

而后测试以下

            var oLamadaExtention = new LambdaExpression<Order>();
                    var left = oLamadaExtention.GetProperty(null, Expression.Parameter(typeof(Order), "x"), "Product.Categary.Type");
                    var value = Expression.Constant("1", left.Type);
                    //动态转换类型
                    var right = Expression.Constant(value, left.Type);
                    Expression expRes = Expression.Equal(left, right);

测试获得的查询lambda结果为x=>x.Product.Categary.Type=="1",测试成功!

三、补充一点

对于配置数据规则的时候还有一点比较麻烦的是,若是如何知道哪一个功能模块使用哪些实体?不可能直接让用户去写Product.Categary.Type这些复杂的功能吧,若是是这样,谈何体验。那么只有使用另一种解决思路了——反射EF实体。

反射EF实体的时候若是是导航属性,还得继续反射导航属性的实体,这样一层一层反射下去,最终确实是能够获得形如Product.Categary.Type这个的结构体,但界面如何展示还有待思考。好比思路以下:

3、总结

以上只是一个设计思路,理论上来讲是能够实现的,若有不足,欢迎斧正,谢谢。若是思路没有问题,后续博主会抽时间将这种设计的实现过程展示出来供你们参考,欢迎关注。其中的难点有两个:

一、逐级反射EF的导航属性,以及这个过程如何展示。是经过特性标记,仍是开发人员配置;

二、动态Expression在构造Lambda的时候和配置数据的兼容性问题,好比数据类型的兼容性有点难控制。

本文原创出处:http://www.cnblogs.com/landeanfen/

欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利