EasyUI、權限管理 這是個都快被搞爛了的組合,但是easyui的確好用,權限管理在項目中的確實用。一直以來博客園里也不少朋友分享過,但是感覺好的要不沒源碼,要不就是過度設計寫的太復雜看不懂,也懶得去看懂,還有一些不是在推廣自己的代碼生成器就是在賣權限組件,看著漂亮的UI和完善的功能就是沒源碼學習,真是惱人。
前段時間公司項目階段性結束了,就抽空把權限控制的部分抽取出來寫了個html+js+ashx+ado.net的權限管理系統分享給一些初學者,這個權限系統demo沒有MVC、沒有ORM、數據庫表都沒設外鍵關系、級聯刪除等,所有需要級聯操作的地方都是事務提交。界面上的所有操作基本都是jquery發Ajax請求ashx處理,ashx處理后輸出json前臺接收處理并配合easyui的組件響應給用戶?;緵]什么門檻,比較適合初學者。先看項目結構圖:
項目結構基本就是模仿PetShop的,簡單的7層,接口里定義方法,不同數據庫不同實現,工廠負責創建訪問數據庫的對象,具體訪問哪個數據庫寫在配置文件,都是老東西了也沒什么說的。Model、BLL、SQLServerDAL等類庫里的類都跟數據庫表名保持一致,新建類庫的時候修改了默認的命名空間(右鍵類庫 - 屬性 - 應用程序 - 程序集名稱&默認命名空間),調用的時候用解決方案名.類庫名.類名,這是我的個人習慣。具體查看源碼
接下來簡單分享下代碼和貼圖演示,懶的聽我啰嗦的直接跳轉到文章結尾下載源碼。戳我
登陸就是用框架提供的FormsAuthentication類來做的,基本就是寫cookie了,用戶登錄成功就加密下票證寫到cookie里,簡單的SetAuthCookie方法有點太簡單了,只能寫用戶名到cookie里。我一般用FormsAuthenticationTicket類來做,可以把整個用戶對象(userData)都寫到cookie里。如果只把用戶名寫到cookie里,這樣用戶在別的瀏覽器登錄然后執行修改密碼操作,過來之前登陸過的瀏覽器,雖然改了密碼,但還是可以繼續保持登陸狀態(博客園就是),這顯然不符合常理。我的是把用戶名和密碼都保存到cookie里,然后用戶每次訪問都取出用戶名和密碼去數據庫驗證,如果找不到記錄就干掉cookie。說到這肯定有人疑惑,把用戶密碼寫到cookie里會不會不安全,用戶密碼本身就是不可逆的md5,寫入cookie之前也再次進行了加密,我個人相信是比較安全的,且只有你自己看到cookie,如果擔心有人抓包,可以把登陸功能部署到https上(個人想法,歡迎拍磚)。
簡單來說,我的登陸邏輯:用戶訪問登陸頁面就ajax請求后臺驗證cookie,只有用戶名和密碼匹配上(用戶沒修改密碼)、狀態IsAble可用(管理員沒在后臺禁用此用戶)等等的情況下直接跳到首頁,其他都干掉cookie。這樣做的好處就是管理員可以很方便的控制一個用戶的狀態,就算他保存了cookie,因為服務端每次都有驗證IsAble字段,管理員也可以很方便的禁用這個用戶。還有不影響登陸的情況:比如用戶修改了自己的姓名等情況也得重寫cookie,否則從cookie里取出來的用戶名顯示到歡迎區域就不準確了,這里用FormsAuthenticationTicket就完美了,userData參數可以存很多東西。
首次訪問登陸頁面判斷是否登陸和用戶點擊登陸按鈕的示例代碼:
case "iflogin": //System.Threading.Thread.Sleep(5000); if (context.Request.IsAuthenticated) { FormsIdentity id = (FormsIdentity)context.User.Identity; FormsAuthenticationTicket tickets = id.Ticket; //獲取票證里序列化的用戶對象(反序列化) ZGZY.Model.User userCheck = new javaScriptSerializer().Deserialize<ZGZY.Model.User>(tickets.UserData); //執行登錄操作 ZGZY.Model.User userReLogin = new ZGZY.BLL.User().UserLogin(userCheck.UserId, userCheck.UserPwd); if (userReLogin == null) { FormsAuthentication.SignOut(); context.Response.Write("{/"msg/":/"用戶名或密碼錯誤!/",/"success/":false}"); } else if (!userReLogin.IsAble) { FormsAuthentication.SignOut(); context.Response.Write("{/"msg/":/"用戶已被禁用!/",/"success/":false}"); } else { //記錄登錄日志 ZGZY.Model.LoginLog loginInfo = new Model.LoginLog(); loginInfo.UserIp = context.Request.UserHostAddress; loginInfo.City = context.Request.Params["city"] ?? "未知"; //訪問者所處城市 loginInfo.UserName = context.User.Identity.Name; loginInfo.Success = true; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); context.Response.Write("{/"msg/":/"已登錄過,正在跳轉!/",/"success/":true}"); } } else context.Response.Write("{/"msg/":/"nocookie/",/"success/":false}"); break;case "login": //System.Threading.Thread.Sleep(5000); string userIp = context.Request.UserHostAddress; string city = context.Request.Params["city"] ?? "未知"; string remember = context.Request.Params["remember"] ?? ""; //記住密碼天數 string name = context.Request.Params["loginName"]; string pwd = ZGZY.Common.Md5.GetMD5String(context.Request.Params["loginPwd"]); //md5加密 DateTime? lastLoginTime; if (new ZGZY.BLL.LoginLog().CheckLogin(userIp, out lastLoginTime) != null) { DateTime dtNextLogin = Convert.ToDateTime(lastLoginTime); context.Response.Write("{/"msg/":/"密碼錯誤次數達到5次,請在" + dtNextLogin.AddMinutes(30).ToShortTimeString() + "之后再登陸!/",/"success/":false}"); } else { ZGZY.Model.LoginLog loginInfo = new Model.LoginLog(); loginInfo.UserName = name; loginInfo.UserIp = userIp; loginInfo.City = city; ZGZY.Model.User currentUser = new ZGZY.BLL.User().UserLogin(name, pwd); if (currentUser == null) { context.Response.Write("{/"msg/":/"用戶名或密碼錯誤!/",/"success/":false}"); loginInfo.Success = false; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); } else if (currentUser.IsAble == false) { context.Response.Write("{/"msg/":/"用戶已被禁用!/",/"success/":false}"); loginInfo.Success = false; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); } else { //記錄登錄日志 loginInfo.Success = true; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); context.Response.Write("{/"msg/":/"登錄成功!/",/"success/":true}"); DateTime dateCookieExpires; //cookie有效期 switch (remember) { case "notremember": dateCookieExpires = new DateTime(9999, 12, 31); //默認時間 break; case "oneday": dateCookieExpires = DateTime.Now.AddDays(1); break; case "sevenday": dateCookieExpires = DateTime.Now.AddDays(7); break; case "onemouth": dateCookieExpires = DateTime.Now.AddDays(30); break; case "oneyear": dateCookieExpires = DateTime.Now.AddDays(365); break; default: dateCookieExpires = new DateTime(9999, 12, 31); break; } FormsAuthenticationTicket ticket = new FormsAuthenticationTicket ( 2, currentUser.UserId, DateTime.Now, dateCookieExpires, false, new JavascriptSerializer().Serialize(currentUser) //序列化當前用戶對象 ); string encTicket = FormsAuthentication.Encrypt(ticket); HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); if (dateCookieExpires != new DateTime(9999, 12, 31)) //不是默認時間才設置過期時間,否則會話cookie cookie.Expires = dateCookieExpires; context.Response.Cookies.Add(cookie); } } break;
博客園的做法是訪問登陸頁面就把cookie干掉(如果有),我個人還是覺得有cookie再訪問登陸頁面就跳轉到首頁比較好。你可以訪問博客園的登陸頁面試試(慎點,會干掉你的cookie)
另外登陸功能還調用了sina的api獲取用戶登錄城市、同一個ip連續5次輸錯密碼就30分鐘之內不讓登陸,詳細請自己查看源碼。
權限控制基本就是用戶擁有角色(可以多角色)、角色擁有菜單不同按鈕的權限(瀏覽、增加、修改、刪除等)。這樣基本做到了單用戶多角色,界面上的操作按鈕根據用戶擁有的權限顯示或者不顯示。先添加一個用戶,默認密碼123:
"已經改密"如果不勾選上,那么下次這個用戶登錄就會彈框讓他修改密碼(這個功能是跟添加用戶默認密碼是123相互呼應的)。直接用添加的用戶登錄會什么都沒有,因為此用戶沒有任何菜單權限:
左側的目錄樹是EasyUI的Tree組件,打開頁面的時候ajax取出當前用戶擁有的菜單權限然后展示出來,不同用戶看到的菜單是不一樣的。后臺操作基本就是一個連表查詢,DataTable取出來然后遍歷構建這個Tree:
/// <summary>/// 根據用戶主鍵id查詢用戶可以訪問的菜單/// </summary>public DataTable GetUserMenu(int id){ StringBuilder strSql = new StringBuilder(); strSql.Append("select distinct(m.Name) menuname,m.Id menuid,m.Icon icon,u.Id userid,u.UserId username,m.ParentId menuparentid,m.Sort menusort,m.LinkAddress linkaddress from tbUser u"); strSql.Append(" join tbUserRole ur on u.Id=ur.UserId"); strSql.Append(" join tbRoleMenuButton rmb on ur.RoleId=rmb.RoleId"); strSql.Append(" join tbMenu m on rmb.MenuId=m.Id"); strSql.Append(" where u.Id=@Id order by m.ParentId,m.Sort"); return ZGZY.Common.SqlHelper.GetDataTable(ZGZY.Common.SqlHelper.connStr, CommandType.Text, strSql.ToString(), new SqlParameter("@Id", id));}
重新登陸下管理員賬戶添加一個瀏覽角色:
給角色授權:
新聞熱點
疑難解答