AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理、职称管理、部门管理、角色管理、角色权限管理等模块。
AppBox v2.0中权限管理中涉及三个概念:模块、用户、角色
1. 权限是定义在模块中,而模块相当于一个分组,比如用户管理就是一个模块。用户分组模块可以包含的多个页面,比如用户列表页面、新增用户页面、修改用户页面、用户详细信息查看页面、修改用户密码页面等;
2. 角色拥有对权限的控制,可以设置一个角色拥有哪些权限;
3. 一个用户可以有多个角色,用户最终的权限来自己所属角色的权限集合。
下面看一下在AppBox v2.0中设置角色权限的页面:
通过上面的描述可以看出,“模块”在整个权限设计中并不重要,仅仅相当于权限的一个分组。
“模块”的引入使得系统看起来更加复杂,比如判断一个用户对某个页面是否有浏览权限?
明显角色模块权限表的设计会比较复杂,因为每个模块的权限个数不同,可以需要特殊的结构。
在AppBox v2.0中,我们是通过JSON结构保持除浏览权限之外的所有权限。来看下数据库表X_RoleModule的初始化脚本:
SET IDENTITY_INSERT [dbo].[X_RoleModule] ON INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (311, 1, N'CoreUser', 1, N'{"New":true,"Edit":true,"Delete":true,"ChangePassword":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (312, 1, N'CoreRoleUser', 1, N'{"New":true,"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (313, 1, N'CoreRoleModule', 1, N'{"Edit":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (314, 1, N'CoreRole', 1, N'{"New":true,"Edit":true,"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (315, 1, N'CorePassword', 1, N'{"Edit":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (316, 1, N'CoreOnlineUser', 1, N'') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (317, 1, N'CoreMenu', 1, N'{"New":true,"Edit":true,"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (318, 1, N'CoreLog', 1, N'{"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (319, 1, N'CoreJobTitle', 1, N'{"New":true,"Edit":true,"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (320, 1, N'CoreDept', 1, N'{"New":true,"Edit":true,"Delete":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (321, 1, N'CoreConf', 1, N'{"Edit":true}') INSERT [dbo].[X_RoleModule] ([Id], [RoleId], [ModuleName], [CanRead], [Others]) VALUES (322, 1, N'AppFile', 1, N'')
这种复杂性不仅在数据库设计阶段,在代码编写阶段也不大好处理,需要大量的代码维护权限列表的读写操作。
下面的代码是更新角色对应的模块权限列表的后台代码:
FineUI.CheckBoxField canReadField = Grid2.FindColumn("CanRead") as FineUI.CheckBoxField; XRoleModuleCollection roleModules = new XRoleModuleCollection(); foreach (GridRow row in Grid2.Rows) { int rowIndex = row.RowIndex; object[] dataKeys = Grid2.DataKeys[rowIndex]; // 当前行对应的模块名称 //int moduleId = Convert.ToInt32(dataKeys[0]); string moduleName = dataKeys[1].ToString(); bool canRead = canReadField.GetCheckedState(rowIndex); AspNet.CheckBoxList ddlOthers = (AspNet.CheckBoxList)Grid2.Rows[rowIndex].FindControl("ddlOthers"); JObject otherPowerObj = new JObject(); foreach (AspNet.ListItem item in ddlOthers.Items) { if (item.Selected) { otherPowerObj.Add(item.Value, true); } } if (canRead || otherPowerObj.Count > 0) { XRoleModule roleModule = new XRoleModule(); roleModule.RoleId = roleId; roleModule.ModuleName = moduleName; roleModule.CanRead = canRead; if (otherPowerObj.Count > 0) { roleModule.Others = otherPowerObj.ToString(Newtonsoft.Json.Formatting.None); } else { roleModule.Others = ""; } roleModules.Add(roleModule); } } roleModules.BatchSave();
在这段代码中,我们不仅需要更新浏览权限,还需要将其他权限生成JSON字符串,并插入数据库。
此外由于我们还要维护菜单列表,在菜单和模块的关系上用户也产生了疑问,来看下编辑菜单的页面截图。
用户需要进一步地了解如下概念:
1. 一个模块可以对应多个页面;
2. 一个页面可以是菜单项,也可以不是菜单项;
3. 需要指定一个菜单项所属的模块,以便根据权限定义生成左侧菜单项。
我不清楚这个概念之前是否有人提过,不过这是独立思考的结果,因此我就给他起了个响亮的名字 - “扁平化的权限设计”。
之所以是扁平化,是因为我们舍弃了“模块”的页面,所以的权限(在其他系统中可能称之为功能点)都可以单独定义,而每个页面只需要在这个很大的权限集合中选中自己需要的权限子集(通常,在实践中这个过程是相反的:也即是每个页面定义自己需要的权限集合,所有页面的权限集合形成了整个站点的权限集合)。
扁平化的权限设计示意图:
数据库设计简单,自然代码就简单了。前面足足 40 多行的保存权限的代码,现在不用 20 行就实现了:
// 当前角色新的权限列表 List<int> newPowerIDs = new List<int>(); for (int i = 0; i < Grid2.Rows.Count; i++) { AspNet.CheckBoxList ddlPowers = (AspNet.CheckBoxList)Grid2.Rows[i].FindControl("ddlPowers"); foreach (AspNet.ListItem item in ddlPowers.Items) { if (item.Selected) { newPowerIDs.Add(Convert.ToInt32(item.Value)); } } } Role role = DB.Roles.Include(r => r.Powers).Where(r => r.ID == roleId).FirstOrDefault(); ReplaceEntities<Power>(role.Powers, newPowerIDs.ToArray()); DB.SaveChanges();
为了避免权限集合过于分散,我们还为每个权限定义了 GroupName (分组属性),从而在前台展示时更美观,更简洁。
权限表的模型类:
public class Power { [Key] public int ID { get; set; } [Required, StringLength(50)] public string Name { get; set; } [StringLength(50)] public string GroupName { get; set; } [StringLength(200)] public string Title { get; set; } [StringLength(500)] public string Remark { get; set; } public virtual ICollection<Role> Roles { get; set; } }
保存角色权限的页面截图:
虽然界面和之前的差不多,但内部实现已经简化了很多。
菜单项编辑时,只需要指定菜单项对应的浏览权限即可(如果不指定浏览权限,则默认这个菜单项不参与权限控制),如下图所示。
1. AppBox v2.0 是免费软件,免费提供下载:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3788
2. AppBox v3.0 是捐赠软件,你可以通过捐赠作者来获取AppBox v3.0的全部源代码(http://fineui.com/donate/)。
返回《AppBox升级进行时》目录