您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
基于DDDLite的权限管理OpenAuth.net
 
作者:李玉宝 来源:博客园 发布于 2016-1-19
   次浏览      
 

在园子里面,搜索一下“权限管理”至少能得到上千条的有效记录。记得刚开始工作的时候,写个通用的权限系统一直是自己的一个梦想。中间因为工作忙(其实就是懒!)等原因,被无限期搁置了。最近想想,自己写东西时,很多都是偏理论方面的,常常找不到合适的例子来论证自己的观点。于是用业余时间来写点东西。

园子中的权限管理系统有以下几种:

1.写的好的,界面NB的,但不开源,毕竟人家辛辛苦苦的劳动成果;

2.写的好的,也公开源码,但不公开数据库设计和一些流程设计,你得看着源码去猜字段去猜流程;

3.不定期讲源码和放截图的,丫的就是不放出项目的,这种同1,就是没事换个马甲来水点广告;

4.入门级的,开放源码的,但那源码实在是不想多看两眼;

什么也不说了,开干!文字太多了,来个动态图缓一缓:

需求

首先,做个东西必须要把需求搞清楚。园子里面的权限管理需求分析的比较合理的,应该是萧秦的我的权限系统设计实现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;  
2.using OpenAuth.Domain.Interface;
3.using System;
4.using System.Collections.Generic;
5.using System.Linq;
6.
7.namespace OpenAuth.App
8.{
9. public class OrgManagerApp
10. {
11. private IOrgRepository _repository;
12.
13. public OrgManagerApp(IOrgRepository repository)
14. {
15. _repository = repository;
16. }
17.
18. public IList<Org> GetAll()
19. {
20. return _repository.LoadOrgs().ToList();
21. }
22.
23. /// <summary>
24. /// 部门的直接子部门
25. /// <para>TODO:可以根据用户的喜好决定选择LoadAllChildren或LoadDirectChildren</para>
26. /// </summary>
27. public IList<Org> LoadDirectChildren(int orgId)
28. {
29. return _repository.Find(u => u.ParentId == orgId).ToList();
30. }
31.
32. /// <summary>
33. /// 得到部门的所有子部门
34. /// <para>如果orgId为0,表示取得所有部门</para>
35. /// </summary>
36. public IList<Org> LoadAllChildren(int orgId)
37. {
38. string cascadeId = "0.";
39. if (orgId != 0)
40. {
41. var org = _repository.FindSingle(u => u.Id == orgId);
42. if (org == null)
43. throw new Exception("未能找到指定对象信息");
44. cascadeId = org.CascadeId;
45. }
46.
47. return _repository.Find(u => u.CascadeId.Contains(cascadeId) && u.Id != orgId).ToList();
48. }
49.
50. /// <summary>
51. /// 添加部门
52. /// </summary>
53. public int AddOrUpdate(Org org)
54. {
55. if (org.Id == 0)
56. {
57. ChangeModuleCascade(org);
58. _repository.Add(org);
59. }
60. else
61. {
62. _repository.Update(org);
63. }
64.
65. return org.Id;
66. }
67.
68. /// <summary>
69. /// 删除指定ID的部门及其所有子部门
70. /// </summary>
71. public void DelOrg(int id)
72. {
73. var delOrg = _repository.FindSingle(u => u.Id == id);
74. if (delOrg == null) return;
75.
76. _repository.Delete(u => u.CascadeId.Contains(delOrg.CascadeId));
77. }
78.
79. #region 私有方法
80.
81. //修改对象的级联ID,生成类似XXX.XXX.X.XX
82. private void ChangeModuleCascade(Org org)
83. {
84. string cascadeId;
85. int currentCascadeId = 1; //当前结点的级联节点最后一位
86. var sameLevels = _repository.Find(o => o.ParentId == org.ParentId && o.Id != org.Id);
87. foreach (var obj in sameLevels)
88. {
89. int objCascadeId = int.Parse(obj.CascadeId.Split('.').Last());
90. if (currentCascadeId <= objCascadeId) currentCascadeId = objCascadeId + 1;
91. }
92.
93. if (org.ParentId != 0)
94. {
95. var parentOrg = _repository.FindSingle(o => o.Id == org.ParentId);
96. if (parentOrg != null)
97. {
98. cascadeId = parentOrg.CascadeId + "." + currentCascadeId;
99. org.ParentName = parentOrg.Name;
100. }
101. else
102. {
103. throw new Exception("未能找到该组织的父节点信息");
104. }
105. }
106. else
107. {
108. cascadeId = "0." + currentCascadeId;
109. org.ParentName = "根节点";
110. }
111.
112. org.CascadeId = cascadeId;
113. }
114.
115. #endregion 私有方法
116. }
117.}

2、教科书级的分层思想,哪怕苛刻的你阅读的是大神级经典大作(如:《企业应用架构模式》《重构与模式》《ASP.NET设计模式》等),你也可以参考本项目。不信?有图为证,Resharper自动生成的项目引用关系,毫无PS痕迹!

记得以前弦子哥写过一篇园子里面搭构架对比的文章(.Net项目分层与文件夹结构大全(最佳架子奖,吐槽奖,阴沟翻船奖揭晓)),本想也建他10几个项目,想一想还是算了,折腾读的人也折腾我自己。毕竟我的项目还没有分布式的需求,就算有,也得遵循分布式设计的最高准则------能不用分布式就不要用!

所以精简到6个项目,个个都是精华!

所有项目都依赖于领域层,而领域层不关心任何数据库实现或界面UI实现;

通过依赖注入真正实现了上层与数据库分离,虽然数据库访问采用了EF的方式,但WEB层对此毫不知情!

3、经过N次优化的数据库结构设计。本来数据库核心表中有很多多对多的关系(用户与机构/用户与角色/角色与模块等等),如下:

代码写到一半的时候,觉得何苦呢,为什么以前设计权限的人都喜欢这么设计?去你的,看我的:

瞬间少了很多,代码风格也可以统一起来,多美好的事情啊。你会问:所有多对多关系放在一张表,性能怎么办?什么?性能?没有千万级数据,别和我提性能。如果你的系统几十万数据时都会很卡,还是去恶补一下数据库基础吧!

界面

人要脸树要皮,没图没真相!

源码及说明

项目地址:https://git.oschina.net/yubaolee/OpenAuth.Net

源码中包含所有的程序代码,数据库PowerDesigner设计图,CodeSmith生成模板,数据库初始脚本。请下载源码后,先用Nuget还原引用的第三方包,再修改一下web.config里面的连接字符串。

当前代码已经实现核心功能如下:

模块/用户/部门/角色的分级管理;

为用户分配角色或直接为用户分配模块;

根据模块URL地址与MVC的Controller适配授权;

页面菜单按钮分配;

内部已经集成log4net,只需要简单的 LogHelper.Log("日志内容") 即可;

最近开发功能展望:

菜单授权处理;

数据权限处理;

用户分级授权功能;

短短一文怎么能表达完一个项目的功能与思想!在后续的博客中,我会结合自己对企业开发的一些看法,对DDD的一些看法,对重构的一些看法,等等等等,来讲述自己的程序人生。

   
次浏览       
相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS

面向应用的架构设计实践
单元测试+重构+设计模式
软件架构师—高级实践
软件架构设计方法、案例与实践
嵌入式软件架构设计—高级实践
SOA体系结构实践

锐安科技 软件架构设计方法
成都 嵌入式软件架构设计
上海汽车 嵌入式软件架构设计
北京 软件架构设计
上海 软件架构设计案例与实践
北京 架构设计方法案例与实践
深圳 架构设计方法案例与实践
嵌入式软件架构设计—高级实践
更多...