在园子里面,搜索一下“权限管理”至少能得到上千条的有效记录。记得刚开始工作的时候,写个通用的权限系统一直是自己的一个梦想。中间因为工作忙(其实就是懒!)等原因,被无限期搁置了。最近想想,自己写东西时,很多都是偏理论方面的,常常找不到合适的例子来论证自己的观点。于是用业余时间来写点东西。
园子中的权限管理系统有以下几种:
什么也不说了,开干!文字太多了,来个动态图缓一缓:
需求首先,做个东西必须要把需求搞清楚。园子里面的权限管理需求分析的比较合理的,应该是萧秦的我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一) ,具体总结如下:
1、权限资源
a.菜单权限 经理和业务员登陆系统拥有的功能菜单是不一样的
b.按钮权限 经理能够审批,而业务员不可以
c.数据权限 A业务员看不到B业务员的单据
d.字段权限 某些人查询客户信息时看不到客户的手机号或其它字段
2、用户,应用系统的具体操作者,我这里设计用户是不能直接分配权限的,必须要分配一个角色,角色中再分配权限,如果某个用户权限比较特殊,可以为他专门建一个角色来应用解决,因为如果用户也可以分配权限系统就会复杂很多。【我采用的还是可以直接给用户分配菜单/按钮,毕竟我们的人员就喜欢搞些特殊待遇】
3、角色,为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,以上所有的权限资源都可以分配给角色,角色和用户N:N的关系。
4、机构,树形的公司部门结构,国内公司用的比较多,它实际上就是一个用户组,机构和用户设计成N:N的关系,也就是说有时候一个用户可以从属于两个部门,这种情况在我们客户需求中的确都出现过。
设计本来想用DDD(也就是把CQRS/AES等一堆的东西全用上,如果你想学习完整的DDD框架,可以参考我的另一个项目BestQ&A --开源中国推荐项目/集CQRS AES等DDD高级特性于一体的问答系统)实现这个项目,思考再三还是被自己否定了。毕竟自己也在学习真正的领域驱动设计,思想上不是很成熟。再者,我相信对于普通的经典DDD架构(好高大上的说,悄悄地告诉你其实就是分层分的格调不一样!),我是有绝对的信心可以把控的。
与其他权限管理相同的地方使用了万恶的EF+MVC结构,当然,我没恶俗到用EasyUI,为了体现个性,选择了酷炫的基于bootstrap的B-JUI前端(炫不炫,你说了算)。相同的东西总是无趣,你可以无视,请把注意力放在下面。
与其他权限管理不同的地方1、项目采用经典DDD架构(用沃恩.弗农大神的话,其实这是DDD-Lite)思想进行开发,简洁而不简单,实用至上,并且所写每一行代码都经过深思熟虑,采用Autofac对项目进行解耦,符合S.O.L.I.D规则!来秀一下内在美:
using OpenAuth.Domain; using OpenAuth.Domain.Interface; using System; using System.Collections.Generic; using System.Linq; namespace OpenAuth.App { public class OrgManagerApp { private IOrgRepository _repository; public OrgManagerApp(IOrgRepository repository) { _repository = repository; } public IList<Org> GetAll() { return _repository.LoadOrgs().ToList(); } /// <summary> /// 部门的直接子部门 /// <para>TODO:可以根据用户的喜好决定选择LoadAllChildren或LoadDirectChildren</para> /// </summary> public IList<Org> LoadDirectChildren(int orgId) { return _repository.Find(u => u.ParentId == orgId).ToList(); } /// <summary> /// 得到部门的所有子部门 /// <para>如果orgId为0,表示取得所有部门</para> /// </summary> public IList<Org> LoadAllChildren(int orgId) { string cascadeId = "0."; if (orgId != 0) { var org = _repository.FindSingle(u => u.Id == orgId); if (org == null) throw new Exception("未能找到指定对象信息"); cascadeId = org.CascadeId; } return _repository.Find(u => u.CascadeId.Contains(cascadeId) && u.Id != orgId).ToList(); } /// <summary> /// 添加部门 /// </summary> public int AddOrUpdate(Org org) { if (org.Id == 0) { ChangeModuleCascade(org); _repository.Add(org); } else { _repository.Update(org); } return org.Id; } /// <summary> /// 删除指定ID的部门及其所有子部门 /// </summary> public void DelOrg(int id) { var delOrg = _repository.FindSingle(u => u.Id == id); if (delOrg == null) return; _repository.Delete(u => u.CascadeId.Contains(delOrg.CascadeId)); } #region 私有方法 //修改对象的级联ID,生成类似XXX.XXX.X.XX private void ChangeModuleCascade(Org org) { string cascadeId; int currentCascadeId = 1; //当前结点的级联节点最后一位 var sameLevels = _repository.Find(o => o.ParentId == org.ParentId && o.Id != org.Id); foreach (var obj in sameLevels) { int objCascadeId = int.Parse(obj.CascadeId.Split('.').Last()); if (currentCascadeId <= objCascadeId) currentCascadeId = objCascadeId + 1; } if (org.ParentId != 0) { var parentOrg = _repository.FindSingle(o => o.Id == org.ParentId); if (parentOrg != null) { cascadeId = parentOrg.CascadeId + "." + currentCascadeId; org.ParentName = parentOrg.Name; } else { throw new Exception("未能找到该组织的父节点信息"); } } else { cascadeId = "0." + currentCascadeId; org.ParentName = "根节点"; } org.CascadeId = cascadeId; } #endregion 私有方法 } }