一步一步Asp.Net MVC系列_权限管理总结(附MVC权限管理系统源码)
TZHSWEET:请大家多多反馈问题,我已经在修改中了,已更新版本。。。。。。
如果大家遇到数据库附加问题,EF连接字符串问题,请自行配置,如果有bug反馈可以私聊,我的qq:409180955。
项目已经发布到Google Code上面了,大家如果需要直接去Google Code下载
主页http://code.google.com/p/tzhsweetsourse/
在上一节中我们总结了关于权限控制的方式,我们这一节讲解关于权限控制中角色权限的授予处理等等并做本系列的总结.
首先,我们来谈谈权限控制中角色权限的控制,上一节只是针对权限拦截中比较粗的控制,如果我们需要对每一个动作做细致的权限认证,我们仍然进一步设计权限处理.
比如:我们分配给一个角色只有浏览日志的权限,不允许他进行其他动作,针对这种细的粒度,我们就必须专门进行处理,就是关于角色动作验证的处理
当然,我们的数据库设计就必须就需要进一步改进.
这是我们的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
设计的主要截图: