在上一篇中我們講了大致怎么搭建一個項目,以及一個項目的基本構架,這一次,我們講解基本的權限管理思路.
說道權限管理,相信大家都不太陌生,這個東西幾乎什么系統都會涉及到,因此,抽出時間去思考,去研究復用的模塊,架構,就是一個非常好的提升水平的方式,特別是對於我們這些學生來說,沒有太多經驗,更需要去研究這些東西來更多的掌握實戰方面的技巧.數據庫設計是一個幾乎人人都要面對的一個話題,我就來講,我的數據庫設計.當然只是個人見解,博客園的很多人都設計的各種各樣的權限,各種各樣的思路,但是可能大多數設計的比較復雜,我只是從一個菜鳥的角度講解,我能夠理解的權限管理.
首先是數據庫設計:
在此期間,我們貼上powerdesinger設計圖
大致就是7張表:
tbModule模塊表
tbModulePermission模塊權限表
tbPermission權限表
tbRole角色表
tbRolePermission角色權限表
tbUserRole用戶角色表
tbUser用戶表
首先要注意的就是命名:
可以看到,我的數據庫命名風格是非常統一的,呵呵,這個可能是習慣,tb代表table,如果是視圖我就在前面加上v,后面全部是Pascal風格的命名,字母大寫的方式.
一個清晰的命名可以讓我們很容易看清細節,起一個好名字非常重要.
這也告訴我們,我們平時注意細節,命名,代碼規范統一,讓我們節省大量的時間.
在博客園看到很多權限設計思路,
我覺得上面這個可能是比較清晰的,呵呵,參考了好多人的設計,
權限設計要設計那些呢?
首先,一個模塊對應多種權限,增刪改查,導入導出等等等等,這里權限在mvc里面可以控制到每一個請求,每一個ajax響應,在mvc里面有一個很好的對應關系,一個模塊對應就是一個控制器,一個action就是一個權限(當然這種可能太細了,但是我們可以先這么看),這樣我們就需要在tbModule和tbPermission中間加一個tbModulePermission表了,因為不同角色的同一模塊的權限可能都是不一樣的,所以這里要加入一個tbModulePermission表.
其次,角色的設計,角色擁有模塊權限,每個用戶對應多種角色,這個用戶可能即是普通管理員,又是其他后台編輯,等等,權限當然是不一樣的.
這樣一個思路就很明顯了,角色直接擁有各種模塊的功能權限,而用戶可以扮演各種角色.
至於字段的設計,我看了很多人的設計,很多細節的字段,比如IsDeleted,CreateUserID,CreateDate,ModifyUserID,ModifyDate,Description這些幾乎大部分表都是需要的,多加一些表,更是讓設計更靈活.
數據庫的設計ID用GUID的習慣,我還沒有養成,可能現在沒遇到麻煩吧,也是沒有真正的工作經驗,呵呵,別人都說應該主鍵用GUID,呵呵!
我看到很多人直接設計Menu和Button來表示tbModule和tbPermission,其實感覺從命名來講,頁面任何一個ajax請求都可以算是一種功能權限,可能不是Button,如果用Permission表示可能更好一點,至於Menu,我認為用Module來命名可能更好,在tbModule里面有一個IsMenu字段來表示是否是菜單,里面有一個Controller字段,其實大致對應一個Controller控制器,每一個模塊在mvc中以控制器的方式展示,我們在設計UI菜單的時候也可以通過反射的方式來獲取Controller,對於菜單的設計更友好,不需要自己去配置URL,更自由.當然,在Permission表里面有PermissionAction和PermissionController字段表示,每一個動作請求和對應的控制器.
對於菜單的添加我們可以做到非常輕松,點點點就能夠配置好,以前需要在數據庫輸入的一些預定義的數據!
數據設計好了,這種數據庫可能我感覺比較清晰.呵呵!
接下來,我們繼續談關於設計的問題,上次基本上已經講完關於數據訪問層的設計,這次講解,關於ViewModel的設計和公共類庫的設計,
ViewModel呵呵,我用它來干什么,我用它來做UI層和業務邏輯層交互的實體對象.
可能大家覺得EF已經幫我們設計了數據庫的實體對象,為什么還要另外一個單獨的層來定義交互的實體對象?
我把這個ViewModel就是和界面交互的實體對象,在EF基本上是根據數據庫直接生成的實體,所以命名字段都是跟數據庫緊密結合在一起的,往往我們給UI層展示的時候,要做大量的處理,而UI交互,經常性我們用的后台框架都要求提供指定格式的json數據,我們以前甚至這么干過,從數據訪問層直接select DeptID as id,name as title from deptment……………這種代碼簡直太坑爹了,而幾乎數據庫的東西和界面都關聯到一起了,如果我們修改個字段,界面改點東西,兩頭都要動,所以,這種模式太痛苦了.
因此,我專門獨立了一層,用來做UI和業務邏輯層的交互使用,這樣ViewModel與UI更友好,用例子說說吧!
數據庫的Permission在界面中大量顯示為Button,我們就可以直接定義一個ViewModelButton類,來做UI和業務邏輯的交互,
如果對於普通字段的添加,我相信在這個代碼中,你的改動非常少,幾乎只是EF模型重新生成,ViewModel,和html界面傳參的修改,我們的目標是,讓修改更簡單,業務的變動,我們仍然能夠清晰的把握細節.
1: /* 作者: tianzh
2: * 創建時間: 2012/7/26 15:52:19
3: *
4: */
5: using System;
6: using System.Collections.Generic;
7: using System.Linq;
8: using System.Text;
9: using System.Web;
10: using TZHSWEET.Entity;
11:
12: namespace TZHSWEET.ViewModel
13: {
14: public class ViewModelButton
15: {
16: #region - 屬性 -
17:
18: public int ID { get; set; }
19:
20: public string Action { get; set; }
21:
22: public string Name { get; set; }
23:
24: public bool IsVisible { get; set; }
25:
26: public string Script { get; set; }
27:
28: public string Icon { get; set; }
29:
30: public string Controller { get; set; }
31:
32: public string Description { get; set; }
33:
34: #endregion
35:
36: #region - 構造函數 -
37:
38: public ViewModelButton()
39: {
40:
41: }
42:
43: public ViewModelButton(HttpContextBase context)
44: {
45: ID = Convert.ToInt32(context.Request["ID"]);
46: Action = context.Request["Action"];
47: Name = context.Request["Name"];
48: IsVisible = context.Request["IsVisible"] == "1";
49: Script = context.Request["Script"];
50: Icon = context.Request["Icon"];
51: this.Controller = context.Request["Controller"];
52:
53:
54: }
55:
56: #endregion
57:
58: #region - 方法 -
59: #region ToEntity
60: public static tbPermission ToEntity(ViewModelButton permission)
61: {
62: tbPermission item = new tbPermission();
63: item.PermissionID = permission.ID;
64: item.PermissionName = permission.Name;
65: item.PermissionAction = permission.Action;
66: item.Script = permission.Script;
67: item.Icon = permission.Icon;
68: item.IsVisible = permission.IsVisible;
69: item.PermissionController = permission.Controller;
70: item.Description = permission.Description;
71: return item;
72: }
73:
74: #endregion
75: #region ToViewModel
76: /// <summary>
77: /// 轉化為ViewModel
78: /// </summary>
79: /// <param name="permission"></param>
80: /// <returns></returns>
81: public static ViewModelButton ToViewModel(tbPermission permission)
82: {
83: ViewModelButton item = new ViewModelButton();
84: item.ID = permission.PermissionID;
85: item.Name = permission.PermissionName;
86: item.Action = permission.PermissionAction;
87: item.IsVisible = permission.IsVisible.Value;
88: item.Icon = permission.Icon;
89: item.Script = permission.Script;
90: item.Controller = permission.PermissionController;
91: item.Description = permission.Description;
92: return item;
93: }
94:
95: /// <summary>
96: /// 轉化為List集合
97: /// </summary>
98: /// <param name="list"></param>
99: /// <returns></returns>
100: public static IEnumerable<ViewModelButton> ToListViewModel(IEnumerable<tbPermission> list)
101: {
102: var listModel = new List<ViewModelButton>();
103: foreach (tbPermission item in list)
104: {
105: listModel.Add(ViewModelButton.ToViewModel(item));
106: }
107: return listModel;
108: }
109: #endregion
110:
111: #endregion
112:
113:
114: }
115: }
一般情況下,我們只需要定義實體,和一個構造函數,這個構造函數,大家可能覺得為什么要傳遞HttpContext?
原因就是,封裝Controller中的請求,把Controller中表單的數據請求單獨用ViewModel處理,看看關於Controller的一個函數處理
我們把關於請求的數據從Controller中分離處理啊,Controller就相當簡潔了,很清晰的看到具體的思路,而我們的業務邏輯層,就可以相當簡潔的調用
而這個T是什么呢?T就是tbPermission實體這個東西了,是不是發現頁面非常清晰,簡潔?
而我們經常通用的幾種UI格式,LigerUI Grid格式請求,Select格式請求,GridTree格式請求,Tree格式請求,都是ViewModel中的封裝,
GridRequest請求的ViewModel:
1: /* 作者: tianzh
2: * 創建時間: 2012/7/19 18:16:08
3: *
4: */
5: /* 作者: tianzh
6: * 創建時間: 2012/7/16 15:31:37
7: *
8: */
9:
10: using System;
11: using System.Collections.Generic;
12: using System.Linq;
13: using System.Text;
14: using System.Web;
15: namespace TZHSWEET.ViewModel
16: {
17: public class LigerUIGridRequest
18: {
19:
20: private string sortOrder;
21:
22: private int pageSize;
23: /// <summary>
24: /// 字段查看視圖(暫時沒做到)
25: /// </summary>
26: public string View
27: {
28: get;
29: set;
30: }
31: /// <summary>
32: /// 排序字段名稱
33: /// </summary>
34: public string SortName { get; set; }
35: /// <summary>
36: /// 排序規則
37: /// </summary>
38: public string SortOrder
39: {
40: get
41: {
42: if (this.sortOrder == "desc")
43: return this.sortOrder;
44: else
45: return "asc";
46: }
47:
48: set
49: {
50: this.sortOrder = value;
51: }
52: }
53: private int pageNumber;
54: /// <summary>
55: /// 頁號
56: /// </summary>
57: public int PageNumber
58: {
59: get
60: {
61: if (this.pageNumber <= 0)
62: return 1;
63: else
64: return pageNumber;
65: }
66: set
67: {
68: if (value <= 0)
69: pageNumber = 1;
70: else
71: pageNumber = value;
72: }
73: }
74: /// <summary>
75: /// 每頁的多少條數據
76: /// </summary>
77: public int PageSize
78: {
79: get
80: {
81: return this.pageSize;
82: }
83: set
84: {
85: this.pageSize= (value==0?10:value);
86: }
87: }
88: /// <summary>
89: /// 查詢條件
90: /// </summary>
91: public string Where { get; set; }
92: /// <summary>
93: /// 初始化讀取信息
94: /// </summary>
95: /// <param name="context"></param>
96: public LigerUIGridRequest(HttpContextBase context)
97: {
98: this.View = context.Request["view"];
99: this.SortName= context.Request["sortname"];
100: this.SortOrder = context.Request["sortorder"]=="desc"?"desc":"asc";
101: this.PageNumber =Convert.ToInt32(context.Request["page"]);
102: this.PageSize =Convert.ToInt32(context.Request["pagesize"]);
103: this.Where = context.Request["where"];
104:
105: }
106: }
107: }
在這里我們可以做數據的校驗工作,這樣我們的控制器就非常非常的簡潔了,
在業務邏輯層,我們可以通過Request的ViewModel模型,進行數據處理,
可以看到我們僅僅通過數據的封裝就讓我們的UI和業務邏輯更簡潔,更豐富,
當然這個業務邏輯層寫的很有問題..............就是業務邏輯層竟然直接返回json格式,這個大大的不好,因為我們的業務邏輯層盡可能的跟數據的展示格式沒關系,應該直接返回數據,集合就直接返回IEnumerable<T>接口,而數據如何展示應該在控制器中控制.
一個好一點的方法設計就是這樣子的,
可以看到我們的工作大部分就是讓不同的類減壓,盡量更簡單,單一指責,如果一個類的職責過多就更難維護,特別是控制器,很容易出現大量的代碼,
就像我們的這個
讓代碼更精簡,這樣我們才能更清晰,本來我們返回的Grid數據需要json化,我寫了一個擴展方法,返回json,而且格式針對UI更友好,配合前台的封裝的ajax,讓消息的提示更友好,
前台利用LigerUI中的封裝,
總結:可以看到我們每一部細節的改進,都能讓我們代碼更簡潔,而且讓我們的思路更清晰,清晰,才是我們的追求,清晰的代碼是我們的追求!