领域驱动设计实战—基于DDDLite的权限管理OpenAuth.net
在园子里面,搜索一下“权限管理”至少能得到上千条的有效记录。记得刚开始工作的时候,写个通用的权限系统一直是自己的一个梦想。中间因为工作忙(其实就是懒!)等原因,被无限期搁置了。最近想想,自己写东西时,很多都是偏理论方面的,常常找不到合适的例子来论证自己的观点。于是用业余时间来写点东西。
园子中的权限管理系统有以下几种:
- 写的好的,界面NB的,但不开源,毕竟人家辛辛苦苦的劳动成果;
- 写的好的,也公开源码,但不公开数据库设计和一些流程设计,你得看着源码去猜字段去猜流程;
- 不定期讲源码和放截图的,丫的就是不放出项目的,这种同1,就是没事换个马甲来水点广告;
- 入门级的,开放源码的,但那源码实在是不想多看两眼;
什么也不说了,开干!文字太多了,来个动态图缓一缓:
需求
首先,做个东西必须要把需求搞清楚。园子里面的权限管理需求分析的比较合理的,应该是萧秦的我的权限系统设计实现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 != )
{
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 == )
{
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 = ; //当前结点的级联节点最后一位
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 + ;
} if (org.ParentId != )
{
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 私有方法
}
}
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的一些看法,对重构的一些看法,等等等等,来讲述自己的程序人生。
本博客其他精彩内容:http://www.cnblogs.com/yubaolee/p/Catalogue.html
领域驱动设计实战—基于DDDLite的权限管理OpenAuth.net的更多相关文章
- 基于DDDLite的权限管理OpenAuth.net 1.0版正式发布
距离上一篇OpenAuth.net的文章已经有5个多月了,在这段时间里项目得到了很多朋友的认可,开源中国上面的Star数接近300,于是坚定了我做下去的信心.最近稍微清闲点,正式推出1.0版,并在阿里 ...
- [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店
一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...
- [.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结
一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计 ...
- [.NET领域驱动设计实战系列]专题一:前期准备之EF CodeFirst
一.前言 从去年已经接触领域驱动设计(Domain-Driven Design)了,当时就想自己搭建一个DDD框架,所以当时看了很多DDD方面的书,例如领域驱动模式与实战,领域驱动设计:软件核心复杂性 ...
- NET 领域驱动设计实战系列总结
NET 领域驱动设计实战系列总结 一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核 ...
- [.NET领域驱动设计实战系列]专题十:DDD扩展内容:全面剖析CQRS模式实现
一.引言 前面介绍的所有专题都是基于经典的领域驱动实现的,然而,领域驱动除了经典的实现外,还可以基于CQRS模式来进行实现.本专题将全面剖析如何基于CQRS模式(Command Query Respo ...
- Java开发架构篇:领域驱动设计架构基于SpringCloud搭建微服务
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 微服务不是泥球小单体,而是具备更加清晰职责边界的完整一体的业务功能服务.领域驱动 ...
- [.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己的订单状态看到店家已经发货.从上面的业务逻辑可以看出,当用户 ...
- [.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一 ...
随机推荐
- ABP文档 - Mvc 控制器
文档目录 本节内容: 简介 AbpController基类 本地化 其它 过滤 异常处理和结果包装 审计日志 验证 授权 工作单元 反伪造 模型绑定器 简介 ABP通过nuget包Abp.Web.Mv ...
- requests的content与text导致lxml的解析问题
title: requests的content与text导致lxml的解析问题 date: 2015-04-29 22:49:31 categories: 经验 tags: [Python,lxml, ...
- shell变量
定义变量 定义变量时,变量名不加美元符号($),如: variableName="value" 注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样.同时,变量名 ...
- Canvas坐标系转换
默认坐标系与当前坐标系 canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿垂直方向向下延伸.左上角坐标为x=0,y=0的点称作原点.在默认坐标系中,每一个点的坐标都是直接 ...
- Bootstrap 模态框(Modal)插件
页面效果: html+js: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- 开源一个跨平台运行的服务插件 - TaskCore.MainForm
本次将要很大家分享的是一个跨平台运行的服务插件 - TaskCore.MainForm,此框架是使用.netcore来写的,现在netcore已经支持很多系统平台运行了,所以将以前的Task.Main ...
- npm package.json属性详解
概述 本文档是自己看官方文档的理解+翻译,内容是package.json配置里边的属性含义.package.json必须是一个严格的json文件,而不仅仅是js里边的一个对象.其中很多属性可以通过np ...
- [.NET] C# 知识回顾 - Event 事件
C# 知识回顾 - Event 事件 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6060297.html 序 昨天,通过<C# 知识回顾 - ...
- 微服务与Docker介绍
什么是微服务 微服务应用的一个最大的优点是,它们往往比传统的应用程序更有效地利用计算资源.这是因为它们通过扩展组件来处理功能瓶颈问题.这样一来,开发人员只需要为额外的组件部署计算资源,而不需要部署一个 ...
- 深入理解Spring MVC
如何让一个普通类成为Controller? 方案一:实现接口Controller 解析:handleRequest(request,response) 方案二:继承AbstractController ...