最近自己在做一套权限系统,进展还不错,在这里我要感谢两个人对我权限系统UI上的帮助:第一个是@微软高级php工程师在博客园看到这位牛人怎么扣界面的,非常膜拜啊。原文地址:
大湿教我写.net通用权限框架(1)之菜单导航篇 但是看了个一知半解,幸好没多久又出现了一位牛人@wolfy,经过他这两篇文章:
看过《大湿教我写.net通用权限框架(1)之菜单导航篇》之后发生的事
看过《大湿教我写.net通用权限框架(1)之菜单导航篇》之后发生的事(续)——主界面
跟着大师们的步伐,我从登陆界面到主界面,不厌其烦的F12,发现并不像微软高级php工程师说得那么简单,有些东西抓过来并不真的就可以用,布局会乱掉,JS也会有很多地方报错。
所以我对像我一样的菜鸟同行建议,人家方法指出来了,我们也还是要提高一下自身的修为,要学习一下html、div+css布局、javascript这些东西,不然层次差太远,人家说稍微高深一点的东西我们就不懂了。
当然自己懂得多一点也可以发现一些牛人们犯的错误,牛人毕竟不是圣人。
大半套UI做下来我发现自己顶多只有4分之1的时间是在写有用的代码,其它时间基本是在学一些基本的前端知识,当我现在再回过头去看前面提到的那几篇博客时收获要多很多。
特别是微软高级php工程师的动态拼接html,用的真是太好了。我把那些东西用到自己的开发中发现解决了很多以前解决不掉的问题。
class="postTitle">
费话少说晒一下我的成果,这个主界面就是完全仿的微软高级php工程师的(当然微软高级php工程师看到了别跟我急,这是练练手,肯定不敢拿了商用,这点我懂的)
wolfy已经在他的文章里讲了如何实现,有兴趣的可以参照博文《大湿教我写.net通用权限框架(1)之菜单导航篇》 我这里就不多费笔墨了。
我主要说一下带出菜单项后如何对页面的权限进行控制
菜单权限表:
CREATE TABLE [dbo].[BPMS_UserMenu]( [UserMenuId] [varchar](50) NOT NULL, [UserId] [varchar](50) NULL, [MenuId] [varchar](50) NULL, [CreateDate] [datetime] NULL, [CreateUserId] [varchar](50) NULL, [CreateUserName] [varchar](50) NULL, CONSTRAINT [PK_BPMS_USERMENU] PRIMARY KEY NONCLUSTERED ( [UserMenuId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户菜单关系主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'UserMenuId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'UserId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜单主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'MenuId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'发生时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'CreateDate' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建用户主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'CreateUserId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建用户' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu', @level2type=N'COLUMN',@level2name=N'CreateUserName' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户菜单关系' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenu' GO ALTER TABLE [dbo].[BPMS_UserMenu] ADD CONSTRAINT [DF_BPMS_UserMenu_CreateDate] DEFAULT (getdate()) FOR [CreateDate] GO
这里很简单,就是把用户表跟菜单表的中间表,用户用哪些菜单的权限都保存在这个表里面
按钮权限表:
CREATE TABLE [dbo].[BPMS_UserMenuButton]( [UserMenuButtonId] [varchar](50) NOT NULL, [UserId] [varchar](50) NULL, [MenuId] [varchar](50) NULL, [ButtonId] [varchar](50) NULL, [CreateDate] [datetime] NULL, [CreateUserId] [varchar](50) NULL, [CreateUserName] [varchar](50) NULL, CONSTRAINT [PK_BPMS_USERMENUBUTTON] PRIMARY KEY NONCLUSTERED ( [UserMenuButtonId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户菜单按钮关系主 键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'UserMenuButtonId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'UserId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜单主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'MenuId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'按钮主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'ButtonId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'发生时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'CreateDate' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建用户主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'CreateUserId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建用户' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton', @level2type=N'COLUMN',@level2name=N'CreateUserName' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户菜单按钮关系' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'BPMS_UserMenuButton' GO ALTER TABLE [dbo].[BPMS_UserMenuButton] ADD CONSTRAINT [DF_BPMS_UserMenuButton_CreateDate] DEFAULT (getdate()) FOR [CreateDate] GO
现在我处理权限就是通过这种中间表的形式来处理的,给用户分配角色,然后再给角色分配权限。
权限表中记录一下角色ID和要进行权限控制对象ID的对应关系。进行权限验证的时候直接跟这个表来匹配。
这样的话,按钮权限、数据权限等等,都可以控制得到。当然肯定还会有其它更好的方法,以后再慢慢来完善。
我的权限控制是用了缓存,用户登陆的时候根据用户ID取得用户的所有角色。
然后取用户角色权限的合集加载到缓存中,进行权限判断的时候直接跟缓存匹配。
取角色菜单SQL语句
SELECT M.MenuId, M.Code , M.FullName , M.Img , M.Category, M.Description, M.SortCode, M.ParentId, UM.MenuId AS IsExist FROM BPMS_SysMenu M LEFT JOIN BPMS_UserMenu UM ON M.MenuId = UM.MenuId AND UserId = @UserId ORDER BY M.SortCode
下面这段代码是把权限读入缓存
using DotNet.Kernel; using DotNet.Utilities; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace BPMS.Service { /// <summary> /// 使用服务器缓存 - 存储权限 /// </summary> public class StorePermission { private readonly BPMS_PermissionDAL dal = new BPMS_PermissionDAL(); private static StorePermission item; public static StorePermission Instance { get { if (item == null) { item = new StorePermission(); } return item; } } /// <summary> /// 将【模块权限】保存在服务器缓存中,提高性能。这样就不用每次去数据库读 /// </summary> /// <param name="UserId">用户主键</param> /// <param name="list"></param> /// <returns></returns> public void SetModulePermission(string UserId, IList list) { CacheHelper.Insert("Module" + UserId, list); } /// <summary> /// 将【操作按钮权限】保存在服务器缓存中,提高性能。这样就不用每次去数据库读 /// </summary> /// <param name="UserId">用户主键</param> /// <param name="list"></param> /// <returns></returns> public void SetButtonPermission(string UserId, IList list) { CacheHelper.Insert("Button" + UserId, list); } /// <summary> /// 获取【模块权限】在服务器缓存中,提高性能。这样就不用每次去数据库读 /// </summary> /// <param name="UserId">用户主键</param> /// <param name="list"></param> /// <returns></returns> public object GetModulePermission(string UserId) { string strKey = "Module" + UserId; if (!CacheHelper.IsExist(strKey)) {this.SetModulePermission(UserId, dal.GetModulePermission(UserId)); } return CacheHelper.GetCache(strKey); } /// <summary> /// 获取【操作按钮权限】在服务器缓存中,提高性能。这样就不用每次去数据库读 /// </summary> /// <param name="UserId">用户主键</param> /// <param name="list"></param> /// <returns></returns> public object GetButtonPermission(string UserId) { string strKey = "Button" + UserId; if (!CacheHelper.IsExist(strKey)) { this.SetButtonPermission(UserId, dal.GetButtonPermission(UserId)); } return CacheHelper.GetCache(strKey); } } }
登陆成功后进入主界面,这里做了两步操作:
1、调用jajax获取权限,将权限存入缓存。
2、根据角色对应的菜单数据动态拼接html,构造出一个个的<a href,点击过后就可以在右侧的框架页中打开。
3、这里只会加载出用户合法分配的菜单项,如果手动输入页面地址的话也会经过验证。有效防止非法访问。
载入权限及动态拼接菜单代码:
var AccordionMenuJson = ""; function GetAccordionMenu() { var index = 0; var html = ""; getAjax("Frame.ashx", "action=LoadFirstMenu", function (data) { AccordionMenuJson = eval("(" + data + ")"); $.each(AccordionMenuJson, function (i) { if (AccordionMenuJson[i].ParentId == '9f8ce93a-fc2d-4914-a59c-a6b49494108f') { if (index == 0) { html += "<li><a style=\"border-top: 0px solid #ccc;\"><img src=\"/Themes/Images/32/" + AccordionMenuJson[i].Img + "\">" + AccordionMenuJson[i].FullName + "</a>"; } else { html += "<li><a><img src=\"/Themes/Images/32/" + AccordionMenuJson[i].Img + "\">" + AccordionMenuJson[i].FullName + "</a>"; } html += GetSubmenu(AccordionMenuJson[i].MenuId); html += "</li>"; index++; } }); }) $(".accordion").append(html); }
URL权限验证:
这段代码最好写在页面基类里面,每个页面都去继承它,任何URL申请都会拿去跟缓存中的权限去匹配一下。
非法请求就直接被拒绝。
#region URL权限验证,加强安全验证防止未授权匿名不合法的请求 /// <summary> /// URL权限验证,加强安全验证防止未授权匿名不合法的请求 /// </summary> public void IsUrlPermission() { bool IsOK = false; //获取当前访问页面地址 string requestPath = RequestHelper.GetScriptName; string[] filterUrl = { "/Frame/HomeIndex.aspx", "/RMBase/SysUser/UpdateUserPwd.aspx" };//过滤特别页面 for (int i = 0; i < filterUrl.Length; i++) { if (requestPath == filterUrl[i]) { IsOK = true; break; } } if (!IsOK) { string UserId = RequestSession.GetSessionUser().UserId; IList list = (IList)StorePermission.Instance.GetModulePermission(UserId); IList itemNode = IListHelper.IListToList<BPMS_ModulePermission>(list).FindAll(t => t.NavigateUrl == requestPath); if (itemNode.Count == 0) { StringBuilder strHTML = new StringBuilder(); strHTML.Append("<div><script type=\"text/javascript\">alert('很抱歉!您的权限不足,访问被拒绝!')</script>"); strHTML.Append("</div>"); HttpContext.Current.Response.Write(strHTML.ToString()); HttpContext.Current.Response.End(); } } } #endregion
做到这里我还没有做前台控制权限的界面,手动从后台去掉当前用户的几个菜单权限,果然显示的菜单就少了:
这里只将实现权限控制的主要代码贴出来分享一下。说得可能不是很好,大家可以去微软高级php工程师的文章《大湿教我写.net通用权限框架(1)之菜单导航篇》中有提供相关链接,希望自己动手也尝试一下。
好东西确实值得好好去学习,以前我也是个单纯的伸手党,东西拿到手没有好好研究。遇到问题也不知道怎么解决,自已多动动手真的很好,驾驭的感觉真不错。
在这里我非常感谢微软高级php工程师和吉日嘎啦,自己第一次做权限框架,很多思想都是参考他们的,如果有想自己造轮子的可以好好地去参考一下他们的博客。这里就不再提供源码下载了,涉及作者的版权问题。需要的还请购买正版。呵呵。