一步一步Asp.Net MVC系列_權限管理總結(附MVC權限管理系統源碼)
TZHSWEET:請大家多多反饋問題,我已經在修改中了,已更新版本。。。。。。
如果大家遇到數據庫附加問題,EF連接字符串問題,請自行配置,如果有bug反饋可以私聊,我的qq:409180955。
在上一節中我們總結了關於權限控制的方式,我們這一節講解關於權限控制中角色權限的授予處理等等並做本系列的總結.
首先,我們來談談權限控制中角色權限的控制,上一節只是針對權限攔截中比較粗的控制,如果我們需要對每一個動作做細致的權限認證,我們仍然進一步設計權限處理.
比如:我們分配給一個角色只有瀏覽日志的權限,不允許他進行其他動作,針對這種細的粒度,我們就必須專門進行處理,就是關於角色動作驗證的處理
當然,我們的數據庫設計就必須就需要進一步改進.
這是我們的EF關系圖:
主要看看關於tbModule,tbPermission部分,都是采用樹形設計,為什么這樣設計呢?
首先,我們必須要承認,Module也就是模塊,應該是多層次的,就像我們的菜單,是很多級的一樣,這種多層次需要我們設計成樹形,授予權限,我們就可以很輕松的設計成樹形進行處理.
其次,關於tbPermssion,這個是權限,但是這個權限應該也是樹形結構,比如我們設計一個菜單模塊權限細分分為多種,增刪改查,訪問,等等,一個訪問可能對應多種ajax請求,比如一個ajax讀取Grid信息,一個ajax讀取Tree信息,如果我們要做細致處理就要對應做多級處理,雖然,小型項目用不到,但是,我們的數據庫設計擁有很大的靈活性.
權限可以細分,我們授權的時候,也不會不方便,只需要前端處理的合理,做好關聯的處理,
但是,如果一個Update可能有多個子集Action方法,比如Get方法,例如:部門信息管理,我們一個更新動作,是先Get部門信息,然后在進行修改以后Update,所以,Get動作就是Update的子集操作,如果我們這個都控制,小型項目會變得太過復雜,怎么處理這種東西呢?
這時候我們之前的Attribute設計的多種權限處理就派上用場了.
首先,對於多種ajax動作,如果不是對數據庫很較大影響的,對於小型項目,我們根本不用管它,直接標記為只要登陸即可訪問,比如Get,GetGridTree之類的,讀取信息這類的子集動作,直接給他個默認的LoginAllowView標記(登錄即可訪問),對於更高粒度,我們當然要進行細致的控制.
這樣,我們的小型項目的Action控制基本上就兩級,也就是控制在增刪改查這個級別,
如果我們需要更高粒度的控制,只需要去掉LoginAllowView標記,在程序中對應添加Permission,並繼續給我們的Action添加子集操作,比如Add關聯子集的那些動作,全部錄入到數據庫中,雖然過程會長一點,但是我們可以通過UI設計的更友好來處理它.
把每一個具體操作關聯的Action都錄入處理,我們就可以了,當然我們的數據庫設計就必須合理,必須有ParentID字段,用來控制樹形結構.
針對權限的處理我們就到一段落,接下來,上演關於LigerUI項目中學到的一個東西,公共查詢組件的設計:
曾幾何時我們拼接查詢語句來設計各種搜索的高級功能,搜索,成了一塊心病,這是一年前,剛開始學的時候做一個搜索的設計,拼接查詢語句,
每一次的模塊都是靠后台手工編碼sql來設計邏輯
看到這里是不是已經吐了?????
我看了以前的東西都已經受不了了.....
也接觸到了LigerUI關於Filter過濾器組件以及后台的設計,也發現一位牛人海南胡勇的組合搜索設計,
這給了我一個相當大的思路,就是設計一個通用組合搜索組件,提高復用率,設計一種解析翻譯規則,我們只需要按照規則提供數據,把sql解析的工作交給組件.
這種設計是相當的震撼,而且web的開發方式可以把where放在客戶端,可以在不改變后台代碼的前提下,更大程度上去設計查詢.
LigerUI的作者那個權限管理,我研究了好久,Filter也看了好久,這里談談心得:
主要的模塊就是這幾塊:
FilterGroup:查詢條件組合數組
FilterParam:查詢參數集合
FilterRule:規則數組
FilterTranslator:翻譯機(專門負責把數據以及條件翻譯成sql語句)
重要:當然為了安全性考慮我們必須對翻譯機過程中的數據做校驗處理
1: /// <summary>
2: /// 用於存放過濾參數,比如一個是名稱,一個是值,等價於sql中的Parameters
3: /// </summary>
4: public class FilterParam
5: {
6: public FilterParam(string name, object value)
7: {
8: this.Name = name;
9: this.Value = value;
10: }
11: public string Name { get; set; }
12: public object Value { get; set; }
13: /// <summary>
14: /// 轉化為ObjectParameter可變參數
15: /// </summary>
16: /// <returns></returns>
17: public ObjectParameter ToObjParam()
18: {
19: ObjectParameter param = new ObjectParameter(this.Name,this.Value);
20: return param;
21: }
22: /// <summary>
23: /// 為查詢語句添加參數
24: /// </summary>
25: /// <param name="commandText">查詢命令</param>
26: /// <returns></returns>
27: public static string AddParameters(string commandText,IEnumerable<FilterParam> listfilter)
28: {
29: foreach (FilterParam param in listfilter)
30: {
31: if (param.Value.IsValidInput())
32: {
33: commandText=commandText.Replace("@"+param.Name,"'"+ param.Value.ToString()+"'");
34: }
35:
36: }
37: return commandText;
38: }
39: /// <summary>
40: /// 轉化為ObjectParameter可變參數
41: /// </summary>
42: /// <param name="listfilter"></param>
43: /// <returns></returns>
44: public static ObjectParameter[] ConvertToListObjParam(IEnumerable<FilterParam> listfilter)
45: {
46: List<ObjectParameter> list = new List<ObjectParameter>();
47: foreach (FilterParam param in listfilter)
48: {
49: list.Add(param.ToObjParam());
50: }
51: return list.ToArray();
52: }
1: /* 作者: tianzh
2: * 創建時間: 2012/7/22 22:05:45
3: *
4: */
5: /* 作者: tianzh
6: * 創建時間: 2012/7/22 15:34:19
7: *
8: */
9: namespace TZHSWEET.Common
10: {
11: public class FilterRule
12: {
13: /// <summary>
14: /// 過濾規則
15: /// </summary>
16: public FilterRule()
17: {
18: }
19: /// <summary>
20: /// 過濾規則
21: /// </summary>
22: /// <param name="field">參數</param>
23: /// <param name="value">值</param>
24: public FilterRule(string field, object value)
25: : this(field, value, "equal")
26: {
27: }
28: /// <summary>
29: /// 實例化
30: /// </summary>
31: /// <param name="field">參數</param>
32: /// <param name="value">值</param>
33: /// <param name="op">操作</param>
34: public FilterRule(string field, object value, string op)
35: {
36: this.field = field;
37: this.value = value;
38: this.op = op;
39: }
40: /// <summary>
41: /// 字段
42: /// </summary>
43: public string field { get; set; }
44: /// <summary>
45: /// 值
46: /// </summary>
47: public object value { get; set; }
48: /// <summary>
49: /// 操作
50: /// </summary>
51: public string op { get; set; }
52: /// <summary>
53: /// 類型
54: /// </summary>
55: public string type { get; set; }
56: }
57: }
剩下個工作就是交給翻譯機進行翻譯:(對作者的版本做了修改)
1: /* 作者: tianzh
2: * 創建時間: 2012/7/22 22:05:49
3: *
4: */
5: using System;
6: using System.Collections;
7: using System.Collections.Generic;
8: using System.Text;
9: using System.Linq;
10: using System.Data.Objects;
11: namespace TZHSWEET.Common
12: {
13:
14:
15: /// <summary>
16: /// 將檢索規則 翻譯成 where sql 語句,並生成相應的參數列表
17: /// 如果遇到{CurrentUserID}這種,翻譯成對應的參數
18: /// </summary>
19: public class FilterTranslator
20: {
21: //幾個前綴/后綴
22: /// <summary>
23: /// 左中括號[(用於表示數據庫實體前的標識)
24: /// </summary>
25: protected char leftToken = '[';
26: /// <summary>
27: /// 用於可變參替換的標志
28: /// </summary>
29: protected char paramPrefixToken = '@';
30: /// <summary>
31: /// 右中括號(用於表示數據庫實體前的標識)
32: /// </summary>
33: protected char rightToken = ']';
34: /// <summary>
35: /// 組條件括號
36: /// </summary>
37: protected char groupLeftToken = '(';
38: /// <summary>
39: /// 右條件括號
40: /// </summary>
41: protected char groupRightToken = ')';
42: /// <summary>
43: /// 模糊查詢符號
44: /// </summary>
45: protected char likeToken = '%';
46: /// <summary>
47: /// 參數計數器
48: /// </summary>
49: private int paramCounter = 0;
50:
51: //幾個主要的屬性
52: public FilterGroup Group { get; set; }
53: /// <summary>
54: /// 最終的Where語句(包括可變參占位符)
55: /// </summary>
56: public string CommandText { get; private set; }
57: /// <summary>
58: /// 查詢語句可變參數數組
59: /// </summary>
60: public IList<FilterParam> Parms { get; private set; }
61: /// <summary>
62: /// 是否為Entity To Sql 生成where翻譯語句(Entity To Sql就需要在實體前面加it,例如it.ID=@ID and it.Name-@Name)
63: /// 否則為普通的SQL語句可變參拼接
64: /// </summary>
65: public bool IsEntityToSql { get; set; }
66: public FilterTranslator()
67: : this(null)
68: {
69: IsEntityToSql = false;
70: }
71: /// <summary>
72: /// 構造函數
73: /// </summary>
74: /// <param name="group"></param>
75: public FilterTranslator(FilterGroup group)
76: {
77: this.Group = group;
78: this.Parms = new List<FilterParam>();
79: }
80:
81: /// <summary>
82: /// 翻譯語句成sql的where查詢條件
83: /// </summary>
84: public void Translate()
85: {
86: this.CommandText = TranslateGroup(this.Group);
87: }
88: /// <summary>
89: /// 對多組規則進行翻譯解析
90: /// </summary>
91: /// <param name="group">規則數組</param>
92: /// <returns></returns>
93: public string TranslateGroup(FilterGroup group)
94: {
95: StringBuilder bulider = new StringBuilder();
96: if (group == null) return " 1=1 ";
97: var appended = false;
98: bulider.Append(groupLeftToken);
99: if (group.rules != null)
100: {
101: foreach (var rule in group.rules)
102: {
103: if (appended)
104: bulider.Append(GetOperatorQueryText(group.op));
105: bulider.Append(TranslateRule(rule));
106: appended = true;
107: }
108: }
109: if (group.groups != null)
110: {
111: foreach (var subgroup in group.groups)
112: {
113: if (appended)
114: bulider.Append(GetOperatorQueryText(group.op));
115: bulider.Append(TranslateGroup(subgroup));
116: appended = true;
117: }
118: }
119: bulider.Append(groupRightToken);
120: if (appended == false) return " 1=1 ";
121: return bulider.ToString();
122: }
123:
124: /// <summary>
125: /// 注冊用戶匹配管理,當不方便修改ligerRM.dll時,可以通過這種方式,在外部注冊
126: /// currentParmMatch.Add("{CurrentUserID}",()=>UserID);
127: /// currentParmMatch.Add("{CurrentRoleID}",()=>UserRoles.Split(',')[0].ObjToInt());
128: /// </summary>
129: /// <param name="match"></param>
130: public static void RegCurrentParmMatch(string key,Func<int> fn)
131: {
132: if (!currentParmMatch.ContainsKey(key))
133: currentParmMatch.Add(key, fn);
134: }
135:
136: /// <summary>
137: /// 匹配當前用戶信息,都是int類型
138: /// 對於CurrentRoleID,只返回第一個角色
139: /// 注意這里是用來定義隱藏規則,比如,用戶只能自己訪問等等,
140: /// </summary>
141: private static Dictionary<string, Func<int>> currentParmMatch = new Dictionary<string, Func<int>>()
142: {};
143: /// <summary>
144: /// 翻譯規則
145: /// </summary>
146: /// <param name="rule">規則</param>
147: /// <returns></returns>
148: public string TranslateRule(FilterRule rule)
149: {
150:
151: StringBuilder bulider = new StringBuilder();
152: if (rule == null) return " 1=1 ";
153:
154: //如果字段名采用了 用戶信息參數
155: if (currentParmMatch.ContainsKey(rule.field))
156: {
157: var field = currentParmMatch[rule.field]();
158: bulider.Append(paramPrefixToken + CreateFilterParam(field, "int"));
159: }
160: else //這里實現了數據庫實體條件的拼接,[ID]=xxx的形式
161: {
162:
163: //如果是EF To Sql
164: if (IsEntityToSql)
165: {
166: bulider.Append(" it." + rule.field+" ");
167: }
168: else
169: {
170: bulider.Append(leftToken + rule.field + rightToken);
171: }
172: }
173: //操作符
174: bulider.Append(GetOperatorQueryText(rule.op));
175:
176: var op = rule.op.ToLower();
177: if (op == "like" || op == "endwith")
178: {
179: var value = rule.value.ToString();
180: if (!value.StartsWith(this.likeToken.ToString()))
181: {
182: rule.value = this.likeToken + value;
183: }
184: }
185: if (op == "like" || op == "startwith")
186: {
187: var value = rule.value.ToString();
188: if (!value.EndsWith(this.likeToken.ToString()))
189: {
190: rule.value = value + this.likeToken;
191: }
192: }
193: if (op == "in" || op == "notin")
194: {
195: var values = rule.value.ToString().Split(',');
196: var appended = false;
197: bulider.Append("(");
198: foreach (var value in values)
199: {
200: if (appended) bulider.Append(",");
201: //如果值使用了 用戶信息參數 比如: in ({CurrentRoleID},4)
202: if (currentParmMatch.ContainsKey(value))
203: {
204: var val = currentParmMatch[value]();
205: bulider.Append(paramPrefixToken + CreateFilterParam(val, "int"));
206: }
207: else
208: {
209: bulider.Append(paramPrefixToken + CreateFilterParam(value, rule.type));
210: }
211: appended = true;
212: }
213: bulider.Append(")");
214: }
215: //is null 和 is not null 不需要值
216: else if (op != "isnull" && op != "isnotnull")
217: {
218: //如果值使用了 用戶信息參數 比如 [EmptID] = {CurrentEmptID}
219: if (rule.value != null && currentParmMatch.ContainsKey(rule.value.ObjToStr()))
220: {
221: var value = currentParmMatch[rule.value.ObjToStr()]();
222: bulider.Append(paramPrefixToken + CreateFilterParam(value, "int"));
223: }
224: else
225: {
226: bulider.Append(paramPrefixToken + CreateFilterParam(rule.value, rule.type));
227:
228: }
229: }
230: return bulider.ToString();
231: }
232: /// <summary>
233: /// 創建過濾規則參數數組
234: /// </summary>
235: /// <param name="value"></param>
236: /// <param name="type"></param>
237: /// <returns></returns>
238: private string CreateFilterParam(object value,string type)
239: {
240:
241: string paramName = "p" + ++paramCounter;
242: object val = value;
243:
244:
245: ////原版在這里要驗證類型
246: //if (type.Equals("int", StringComparison.OrdinalIgnoreCase) || type.Equals("digits", StringComparison.OrdinalIgnoreCase))
247: // val = val.ObjToInt ();
248: //if (type.Equals("float", StringComparison.OrdinalIgnoreCase) || type.Equals("number", StringComparison.OrdinalIgnoreCase))
249: // val = type.ObjToDecimal();
250:
251: FilterParam param = new FilterParam(paramName, val);
252: this.Parms.Add(param);
253: return paramName;
254: }
255:
256: /// <summary>
257: /// 獲取解析的參數
258: /// </summary>
259: /// <returns></returns>
260: public override string ToString()
261: {
262: StringBuilder bulider = new StringBuilder();
263: bulider.Append("CommandText:");
264: bulider.Append(this.CommandText);
265: bulider.AppendLine();
266: bulider.AppendLine("Parms:");
267: foreach (var parm in this.Parms)
268: {
269: bulider.AppendLine(string.Format("{0}:{1}", parm.Name, parm.Value));
270: }
271: return bulider.ToString();
272: }
273:
274: #region 公共工具方法
275: /// <summary>
276: /// 獲取操作符的SQL Text
277: /// </summary>
278: /// <param name="op"></param>
279: /// <returns></returns>
280: public static string GetOperatorQueryText(string op)
281: {
282: switch (op.ToLower())
283: {
284: case "add":
285: return " + ";
286: case "bitwiseand":
287: return " & ";
288: case "bitwisenot":
289: return " ~ ";
290: case "bitwiseor":
291: return " | ";
292: case "bitwisexor":
293: return " ^ ";
294: case "divide":
295: return " / ";
296: case "equal":
297: return " = ";
298: case "greater":
299: return " > ";
300: case "greaterorequal":
301: return " >= ";
302: case "isnull":
303: return " is null ";
304: case "isnotnull":
305: return " is not null ";
306: case "less":
307: return " < ";
308: case "lessorequal":
309: return " <= ";
310: case "like":
311: return " like ";
312: case "startwith":
313: return " like ";
314: case "endwith":
315: return " like ";
316: case "modulo":
317: return " % ";
318: case "multiply":
319: return " * ";
320: case "notequal":
321: return " <> ";
322: case "subtract":
323: return " - ";
324: case "and":
325: return " and ";
326: case "or":
327: return " or ";
328: case "in":
329: return " in ";
330: case "notin":
331: return " not in ";
332: default:
333: return " = ";
334: }
335: }
336: #endregion
337:
338: }
339: }
可能大家說,這玩意怎么用呀?LigerUI做了一個專門針對組合查詢的組件,也可以自己去寫,有了開源的代碼,相信我們自己也可以寫出自己的組件.
我們前台的搜索設計就更容易了:
看看我的日志搜索模塊怎么設置的搜索
1: //搜索表單應用ligerui樣式
2: $("#formsearch").ligerForm({
3: fields: [
4: {display: "用戶名", name: "UserName", newline: true, labelWidth: 100, width: 220, space: 30, type: "text",
5: attr: { op: "equal" }, cssClass: "field"}
6: ,
7: { display: "IP地址", name: "IPAddress", newline: false, labelWidth: 100, width: 220, space: 30, type: "text", cssClass: "field"},
8: { display: "開始時間", name: "CreateDate", newline: true, labelWidth: 100, width: 220, space: 30, type: "date", cssClass: "field", attr: { "op": "greaterorequal"}},
9: { display: "結束時間", name: "CreateDate", newline:false , labelWidth: 100, width: 220, space: 30, type: "date", cssClass: "field", attr: { "op": "lessorequal"}}
10: ],
11: appendID: false,
12: toJSON: JSON2.stringify
13: });
14:
15: //增加搜索按鈕,並創建事件
16: LG.appendSearchButtons("#formsearch", grid);
也就是說,我們只需要設置規則,甚至可以自己去按照json格式傳遞給后台我們的規則就可以了,比如:
如果我們的Grid想設計條件,可以直接這么加
直接把這個where條件json化傳遞給后台就可以實現我們的按照條件查詢Grid功能了.
這時候,大家想到了什么?
我們把條件where的部分更多的分擔在UI層,我們的后台業務邏輯只需要解析where就可以再不改變業務邏輯的的條件下實現更復雜的業務邏輯.
其實,海南胡勇那位牛人設計的winform查詢組件也是這個道理.
如果你還停留在拼接查詢語句階段,可以看看他們的設計.
最后,整個項目實際上,我非常不滿意的是架構,這部分太差了,沒有真正的公司工作經驗,僅僅是粗淺的理解,深入學習,<<企業架構模式>>這本書買了也看不懂,缺少真正的工作經驗,談架構就是扯淡,所以,大家見諒.
如果覺得不錯就推薦一下,支持一下吧!呵呵!
感謝,博客園的眾多牛人提供了太多的學習資料和項目,讓我們這些沒有畢業的學生也有更多的學習資料.
這里也提供源碼分享給更多學習的人.
注:推薦使用IE9,或者谷歌!IE8出現了BUG......呃!另外忘了說了,這個是vs2010開發,數據庫是sql2005。。請確保安裝mvc3,vs2010,sql2005。。。。。額。。。
目前1.1下載版本
附帶源碼網盤地址:http://pan.baidu.com/netdisk/singlepublic?fid=511632_114595096
設計的主要截圖: