權限管理系統,一直是很多Mis系統和一些常見的管理系統所需要的,所以一般可以作為獨立的模塊進行開發,需要的時候進行整合即可,不需要每次從頭開發,除非特殊的系統需求。我在Winform開發框架介紹中的隨筆中,很早之前在《Winform開發框架之權限管理系統》就寫過關於我的通用權限管理系統的一些介紹,當時這個版本的還是傳統樣式的,界面如下所示。
由於我的Winform開發框架需要,我把權限管理系統、字典管理模塊、分頁控件等都擴展了支持傳統樣式、DotNetBar控件樣式,以及DevExpress控件樣式。這些樣式的界面之前也已經介紹不少了,本文主要介紹使用DevExpress控件樣式的權限管理系統,對一些新的功能擴展以及完善,包括系統類型管理、菜單管理、功能管理、登陸日志管理等模塊進行介紹。
本權限管理系統包括用戶管理、組織機構管理、角色管理、系統類型定義、功能管理、菜單管理、用戶登錄日志管理,以及對相應對象的權限控制管理等功能。整個權限管理系統是基於RBAC(基於角色的訪問控制)方式進行權限控制,是一個獨立的權限管理系統,需要的時候業務系統與之進行整合即可,實現通用模塊的高效利用和統一管理等方面。
1、系統類型管理
系統類型,是我們在權限系統中定義的一個系統分類,對於不同的系統,我們通過這個定義進行區分,可以實現多個業務系統的管理(雖然一般情況下,我們只是管理一個系統)。系統類型管理界面如下所示。

這個對象的管理,以前一直在權限系統中,通過修改數據庫進行初始化,不過我覺得把它作為一個獨立的數據進行維護,然后為功能定義、菜單、登陸日志等和系統類型關聯的數據進行設置管理,應該是比較方便的。
因為,功能定義是基於某個系統而增加的一系列功能控制ID的定義,方便實現按鈕級別和數據級別的權限控制的基礎。
菜單的定義,是用來實現基於動態配置功能模塊的菜單,引入該模塊的初衷是用來方便動態配置Winform或者Web的功能菜單,這個也是不同的系統肯定有不同的菜單了。
登陸日志管理,是用來方便記錄來自業務系統對權限系統接口的重要接口調用的記錄,如登陸、修改密碼等操作,一般情況下,我們用不同的業務系統來區分它們的數據。
2、菜單管理
剛才說到,這里的菜單管理,是指用來實現基於動態配置功能模塊的菜單,用來實現基於Winform、Web菜單的集成統一,對於不同的系統,菜單定義是不同的,因此他們是基於某個系統類型下面的數據管理。
為了有效管理菜單數據,我把菜單的關系用樹形控件進行展示,並通過SplitContainer控件實現布局的合理切分,可以實現自由拖動,界面效果如下所示。

菜單的數據顯示,左邊使用了樹形控件,數據通過遞歸方式進行綁定。右邊數據是通過我的分頁控件進行數據綁定,並且綁定的數據是通過了層級的縮進格式化了的。
樹菜單綁定的核心遞歸代碼如下所示。
/// <summary> /// 綁定樹形數據 /// </summary> private void InitTree() { treeView1.Nodes.Clear(); treeView1.BeginUpdate(); Cursor.Current = Cursors.WaitCursor; //先獲取系統類型,然后對不同的系統類型下的菜單進行綁定顯示 List<SystemTypeInfo> typeList = BLLFactory<SystemType>.Instance.GetAll(); foreach (SystemTypeInfo typeInfo in typeList) { TreeNode pNode = new TreeNode(); pNode.Text = typeInfo.Name;//系統類型節點 pNode.Name = typeInfo.OID; pNode.ImageIndex = 0; pNode.SelectedImageIndex = 0; this.treeView1.Nodes.Add(pNode); string systemType = typeInfo.OID;//系統標識ID //綁定樹控件 //一般情況下,對Ribbon樣式而言,一級菜單表示RibbonPage;二級菜單表示PageGroup;三級菜單才是BarButtonItem最終的菜單項。 List<MenuNodeInfo> menuList = BLLFactory<SysMenu>.Instance.GetTree(systemType); foreach (MenuNodeInfo info in menuList) { TreeNode item = new TreeNode(); item.Name = info.ID; item.Text = info.Name;//一級菜單節點 item.Tag = info;//對菜單而言,記錄其MenuNodeInfo到Tag中,作為判斷依據 item.ImageIndex = 1; item.SelectedImageIndex = 1; pNode.Nodes.Add(item); AddChildNode(info.Children, item); } } Cursor.Current = Cursors.Default; treeView1.EndUpdate(); this.treeView1.ExpandAll(); } private void AddChildNode(List<MenuNodeInfo> list, TreeNode fnode) { foreach (MenuNodeInfo info in list) { TreeNode item = new TreeNode(); item.Name = info.ID; item.Text = info.Name;//二、三級菜單節點 item.Tag = info;//對菜單而言,記錄其MenuNodeInfo到Tag中,作為判斷依據 int index = (fnode.ImageIndex + 1 > 3) ? 3 : fnode.ImageIndex + 1; item.ImageIndex = index; item.SelectedImageIndex = index; fnode.Nodes.Add(item); AddChildNode(info.Children, item); } }
菜單的列表數據,通過分頁控件綁定的代碼如下所示。
/// <summary> /// 根據查詢條件構造查詢語句 /// </summary> private string GetConditionSql() { SearchCondition condition = new SearchCondition(); condition.AddCondition("Name", this.txtName.Text, SqlOperator.Like); condition.AddCondition("FunctionId", this.txtFunctionId.Text, SqlOperator.Like); condition.AddCondition("Visible", this.txtVisible.Checked ? 1 : 0, SqlOperator.Equal); condition.AddCondition("WinformType", this.txtWinformType.Text, SqlOperator.Like); condition.AddCondition("Url", this.txtUrl.Text, SqlOperator.Like); string where = condition.BuildConditionSql().Replace("Where", ""); return where; } /// <summary> /// 綁定列表數據 /// </summary> private void BindData() { //entity this.winGridViewPager1.DisplayColumns = "Name,Icon,Seq,FunctionId,Visible,WinformType,Url"; #region 添加別名解析 this.winGridViewPager1.AddColumnAlias("ID", ""); this.winGridViewPager1.AddColumnAlias("Name", "顯示名稱"); this.winGridViewPager1.AddColumnAlias("Icon", "圖標"); this.winGridViewPager1.AddColumnAlias("Seq", "排序"); this.winGridViewPager1.AddColumnAlias("FunctionId", "功能ID"); this.winGridViewPager1.AddColumnAlias("Visible", "菜單可見"); this.winGridViewPager1.AddColumnAlias("WinformType", "Winform窗體類型"); this.winGridViewPager1.AddColumnAlias("Url", "Web界面Url地址"); #endregion string where = GetConditionSql(); List<MenuInfo> list = BLLFactory<SysMenu>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo); list = CollectionHelper<MenuInfo>.Fill("-1", 0, list, "PID", "ID", "Name"); this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<MenuInfo>(list); this.winGridViewPager1.PrintTitle = "功能菜單信息報表"; }
菜單的編輯界面如下所示。

本篇主要是介紹菜單的管理,對於菜單的動態加載管理,我會在另外一篇Winform開發框架中進行介紹。菜單定義數據里面的功能控件ID,是來自功能模塊的功能控件ID,是用來控制不同用戶所能訪問的菜單資源的。
3、功能管理
功能定義,也是基於某個系統類型下面的,對於不同的業務系統,我們可以集中放在一個權限管理系統里面進行管理,但是功能的定義,根據不同的系統類型進行區分即可,這樣的目標是用來實現多種企業應用的有效整合。
功能的管理,是整個權限系統的核心,因為通過對他們的定義,以及權限分配,都會影響各個角色用戶可訪問的功能;功能定義的數據,其實也是一個樹形結構,可以用樹控件進行展示出來,如下所示。

4、日志管理
我們知道,一般權限系統,管理用戶很常見的,因此用戶的登陸日志,一般情況下是由權限管理系統記錄即可, 如每次用戶登陸的時候,我們記錄用戶的登陸日志;如果用戶修改密碼,我們也做一個重要記錄,這樣對於在業務系統端的管理,我們就不需要管理他們的登陸方面的事件了。
日志管理分為兩個部分,一個是通過權限管理系統本身的登錄入口進行登錄的,一個是通過與之集成的業務系統,通過API調用方式進行登錄驗證的, 他們的登錄接口基本一樣,只是部分數據不同。
try { //判斷用戶是否登錄成功 string ip = NetworkUtil.GetLocalIP(); string macAddr = HardwareInfoHelper.GetMacAddress(); string identity = BLLFactory<User>.Instance.VerifyUser(this.txtLogin.Text, this.txtPassword.Text, "WareMis", ip, macAddr); if (!string.IsNullOrEmpty(identity)) { //進一步判斷用戶角色 if (BLLFactory<User>.Instance.UserInRole(this.txtLogin.Text, RoleInfo.AdminName)) { MessageUtil.ShowTips(string.Format("用戶【{0}】身份驗證正確", this.txtLogin.Text)); } else { MessageUtil.ShowWarning("該用戶沒有管理員權限"); return; } } else { MessageUtil.ShowWarning("用戶名或密碼錯誤"); return; } } catch (Exception err) { MessageUtil.ShowError(err.Message); }
權限管理系統對日志進行統一管理和展示,具體界面如下所示。

整個權限管理系統,目的提高系統開發速度和效率,因此通過獨立開發,模塊重用,易於集成等方式實現我們生產效率的最大化。
