前言:“我們有一個訂單列表,希望能夠根據當前登陸的不同用戶看到不同類型的訂單數據”、“我們希望不同的用戶能看到不同時間段的掃描報表數據”、“我們系統需要不同用戶查看不同的生產報表列”。諸如此類,最近經常收到項目上面的客戶提出的這種問題,即所謂的“數據權限”,經過開會討論決定:在目前的開發框架上面搭建一套通用的數據權限功能。
本文原創地址:http://www.cnblogs.com/landeanfen/p/7760803.html
一、大話權限模塊
有了上面的引言,自然而然就引出了今天需要和大家討論的話題——數據權限。作為開發人員,我們肯定知道,一般的系統都離不開權限模塊,它是支撐整個系統運行的基礎模塊。而根據項目類型和需求的不同,權限模塊的設計更是大相徑庭。但不管怎么變,權限模塊從大的方面來說,可以分為兩種大的類型:功能權限 和 數據權限。
- 功能權限:主要控制不同的資源主體(用戶、角色、組織等)有操作不同的資源的權限。比如常見的不同的角色能訪問不同的頁面(菜單權限),以及具有操作同一頁面的不同功能(按鈕權限)等等,都數據功能權限的范疇,這種設計相對比較簡單,也比較為大多數系統所通用。當然網上資料、設計思路也可以找到很多。
- 數據權限:主要控制不同的資源主體(用戶、角色、組織等)有查看不同的數據信息的權限,一般來說,數據權限又分為數據行權限和數據列權限,通過字面意思不難理解這兩者的區別,比如上文“我們有一個訂單列表,希望能夠根據當前登陸的不同用戶看到不同類型的訂單數據” 這就是一個典型的數據行權限,而“我們系統需要不同用戶查看不同的生產報表列”這就是數據列權限的范疇。由於數據權限和系統的業務邏輯關系非常密切,所以不同的系統設計差異性會非常大。從另一方面來說,由於數據權限和業務邏輯關聯性非常強,如果系統的業務邏輯非常復雜,數據權限設計起來也會相對復雜,所以關於數據權限的設計一直沒有一種相對通用和使用簡單的設計方案。
如果你動手去設計數據權限,當你去各大平台、百度、谷歌查找設計思路的時候,你會發現很難找到有用的資料,很多設計思路局限性非常大。其實原因很簡單:數據權限的設計和他人系統關系緊密,一般不太容易拿到你的項目上面直接使用。
當然也有另外部分人說“數據權限並不能作為權限模塊去設計”,比如博主看到這樣一條評論
從一定程度上來說,這樣理解也不為過,如果你覺得你的系統靈活性和配置性不需要那么高,把數據權限的規則在代碼里面寫死又何妨,博主所在公司的另外一個部門就是這么干的,除了編碼量大一點,其實也沒什么太大的問題!其實博主說這么多無非是想表達一個觀點:沒有絕對通用的數據權限設計思路,關鍵看合不合你用!當然本文的設計思路也是一樣,不強制要求,不提通用。設計思路供你參考!
二、一種設計思路
關於權限設計的雜談就告一段落,凡是點到即止,再說了多了就說爛了。到目前為止,博主找到的一篇寫得相對比較好的文章 通用權限管理設計 之 數據權限。這位博主是用sql去實現的,如果是把這個運用到EF里面的話,考慮到EF復雜的導航屬性,會有一些問題。接下來說說博主這邊想到的設計思路。
先說說博主所在項目的情況,和數據打交道的部分采用EntityFramework+Repository的傳統模式去實現的,整個項目從上到下,就是一種典型的"偽DDD",什么是”偽DDD“?這里不做過多說明,使用過DDD的同仁應該很清楚。下面是設計思路流程圖:
第一步:配置數據規則
第二步:頁面使用數據規則
以上是一個大致的思路圖,總的來說,要實現基於EF的數據權限設計,主要分為兩大步驟
1、配置數據規則
配置數據規則這里有三個大的方面:功能模塊、數據資源、角色
- 功能模塊:為什么這里要加上功能模塊的約束?是因為博主覺得我們某一個頁面在查詢數據的時候,會有一個查詢的范圍,比如訂單查詢頁面肯定只能查詢和訂單有關聯的實體功能,而不可能查詢和它沒有任何關聯的業務。加這個約束更大的意義在於我們動態的構造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。這個我們在后面使用這個規則的時候加以說明。
2、使用數據規則
有了上面的數據規則,接下來就是我們在取數據的時候如何使用了,這里有一點需要說明的是:我們這里需要傳兩個參數,一個是模塊的名稱,比如上面的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",測試成功!
3、補充一點
對於配置數據規則的時候還有一點比較麻煩的是,如果如何知道哪個功能模塊使用哪些實體?不可能直接讓用戶去寫Product.Categary.Type這些復雜的功能吧,如果是這樣,談何體驗。那么只有使用另外一種解決思路了——反射EF實體。
反射EF實體的時候如果是導航屬性,還得繼續反射導航屬性的實體,這樣一層一層反射下去,最終確實是可以得到形如Product.Categary.Type這個的結構體,但界面如何展現還有待思考。比如思路如下:
三、總結
以上只是一個設計思路,理論上來說是可以實現的,如有不足,歡迎斧正,謝謝。如果思路沒有問題,后續博主會抽時間將這種設計的實現過程展現出來供大家參考,歡迎關注。其中的難點有兩個:
1、逐級反射EF的導航屬性,以及這個過程如何展現。是通過特性標記,還是開發人員配置;
2、動態Expression在構造Lambda的時候和配置數據的兼容性問題,比如數據類型的兼容性有點難控制。
本文原創出處:http://www.cnblogs.com/landeanfen/
歡迎各位轉載,但是未經作者本人同意,轉載文章之后必須在文章頁面明顯位置給出作者和原文連接,否則保留追究法律責任的權利