在上一章节中,我们处理了MVC多级目录问题,参见《二、处理MVC多级目录问题——以ABP为基础架构的一个中等规模的OA开发日志》。从这章开始,我们将进入正式的开发过程。首先,我们要完成系统的基础设置模块(在后续的功能中,需要大量使用这些基础设置信息)。和一般的OA系统不同,在律所OA系统中,用户类别管理是基础模块中非常重要、使用频率非常高的一个基础模块。虽然此功能只是很小的一个字典项设置,但是其中涉及了锁、并发处理、领域服务于应用服务的划分等繁琐问题。

UI功能页面介绍(因用户功能未完成,欠缺删除页面)

UI方面,我们使用了Metronic+EasyUI做为主要呈现方式。其中我们对EasyUI做了相应调整,已使其更加适用于Metronic风格。其中,上图所示的cnblogs.scss为本博客的UI风格(仿小米风格,未完工);color.scss为全局颜色设置;common.scss为全局公用css;easyui.custom.scss为我们对EasyUI的样式修改;metronic.scss为我们对Metronic的样式调整; site.scss为程序主css文件。我们在common.scss文件中,导入了color.scss。在site.scss文件中,导入了common.scss、metronic.scss和easyui.custom.scss文件。这样在布局页引入css文件时,我们仅需要引入单个site.min.css文件即可,而不用引入一大堆css文件。我们在布局页中,大量使用了存储于cdn上的一些css和js文件。这些大多都是一些公用类库,大家可根据需要自行下载。

ABP对CSRF/XSRF跨站攻击的处理

abp通过token验证以解决上述攻击问题。只要在模板文件(布局页)中,增加@{SetAntiForgeryCookie();},即可方便在ABP内置的ajax辅助方法中发送生成的token。但在实际使用中,我们需要做一些处理。ABP的view页面文件继承自View目录下EasyFastWebViewPageBase类(该类最终继承自AbpWebViewPage类),而SetAntiForgeryCookie()方法则是AbpWebViewPage类的内置方法。所以,要使用SetAntiForgeryCookie()方法。我们必须要所有的布局页全部继承自EasyFastWebViewPageBase类。

如图所示,在View根目录下,有EasyFastWebViewPageBase.cs文件(依据您的项目名,此处会有不同的文件名)。为了保证在Areas中的布局页也能正常的使用SetAntiForgeryCookie()方法。需要在布局页继承该类。(也可以复制一遍,放到所有Areas的View目录下,但显然,继承的方式更合理)感谢ABP架构交流群的朋友们,刚开始时作者也被这里卡了很久,是群里的朋友们最终指出了问题所在。

实体设置

我们在Core层下创建Entities目录(目录名可以随意起,这里采用的常用习惯Entity的复数形式)。然后创建BaseEntity和UserType两个类。

namespace EasyFast.Core.Entities
{
public class BaseEntity : FullAuditedEntity<long>
{
public BaseEntity()
{
OrderId = ;
Guid = Guid.NewGuid();
} /// <summary>
/// model的Guid,用于记录操作日志
/// </summary>
public Guid Guid { get; set; } /// <summary>
/// 排序Id
/// </summary>
[Range(,)]
public int OrderId { get; set; } /// <summary>
/// 行号,用于乐观并发控制
/// </summary>
[Timestamp]
public byte[] RowVersion { get; set; }
}
}
namespace EasyFast.Core.Entities
{
public class UserType : BaseEntity
{
/// <summary>
/// 人员类别名称
/// </summary>
[StringLength()]
public string Name { get; set; } /// <summary>
/// 备注信息
/// </summary>
public string Remarks { get; set; }
public ICollection<User> User { get; set; }
}
}

BaseEntity类将做为我们大多数实体的基类。该基本继承自FullAuditedEntity<long>。这样一来,BaseEntity就自动继承了 public long Id{ get; set; }这个属性。我们追加的Guid字段用于记录操作日志。这个在日后使用时再详细说明。RowVersion字段用于对EF进行并发控制。在SQLServer中,行中的数据每变动一次,RowVersion自动+1(该字段为16进制)。通过对比该字段的变化,我们即可得知在修改或是删除数据时,是否存在并发冲突。

应用层(EasyFast.Application)主要代码简析

特别提醒:本人对应用层、领域层的讲解仅仅只是本人的一点浅见,不代表DDD的最佳实践要求这么干。在本系列文章里,我们更关注解决工程问题,而不是进行理论研究。如您发现我们的设计有不合理之处,或是对ABP的使用或理解有不对之处。欢迎批评指正。

Application层一般称之为应用服务于层。在DDD设计规范里,此层专门针对页面进行服务。这个说法可能让人费解。我们举个实际的例子做参考:在OA系统中,我们要展示一个律师的信息时,既要展示User表本身的信息,也要同时展示其关联的Case表、Client表、Finance表等内容。在N层架构的做法中,我们会分别实例化User、Case、Client等对应的业务逻辑类。然后将其查询的结果存储成ViewBag发送到View页面。接着再在View页面中,将对应的ViewBag转换成model进行输出。

        public ActionResult Index()
{
long UserID = User.GetUserInfo().LawyerId;
var user = LawyerService.Find(UserID);
if (user.Status == JingShOnline.Models.Enum.LawyerStatus.Normal)
{
return RedirectToAction("Normal", "Authen");
} ViewBag.CaseList = CaseService.Where(o =>o.LawyerId== UserID)
.Include(o=>o.CaseReason)
.Include(o=>o.Practice)
.Include(o=>o.Court)
.Include(o=>o.Industry)
.Take(10).ToList(); var history = user.LawyerWorkHistory.OrderByDescending(o => o.StartDate).ToList();
var education = user.LawyerEducation.OrderByDescending(o => o.StartDate).ToList();
var academic = user.LawyerAcademic.OrderByDescending(o => o.Id).ToList();
var certificate = user.LawyerCertificate.OrderByDescending(o => o.Id).ToList();
var socialposition = user.LawyerSocialPosition.OrderByDescending(o => o.Id).ToList(); //简历完整度
var ResumeCompletion = (history.Count > 0 ? 35 : 0) + (education.Count > 0 ? 35 : 0) + (academic.Count > 0 ? 10 : 0) + (certificate.Count > 0 ? 10 : 0) + (socialposition.Count > 0 ? 10 : 0); ViewBag.WorkHistory = history;
ViewBag.Education = education;
ViewBag.Academic = academic;
ViewBag.Certificate = certificate;
ViewBag.SocialPosition = socialposition;
ViewBag.ResumeCompletion = ResumeCompletion; return View(user);
}

  参见上述代码。View页面需要多个模块的数据做集中展示。为达到目的,只好在Controller里初始化多个Service,进行多次查询,然后将查询的结果存储到ViewBag中,发送到前台。再在前台进行数据类型转换并输出。如此做法主要有两个个弊端:

  1. Controller过于重型化,不利于代码质量控制。在MVC中,Controller应只负责基础效验和Action的跳转。
  2. ViewBag是弱类型的,前台使用时,容易出错。且日后代码进行扩展或是重构时,将大大增大bug出现几率。

Application层的出现,其实就是为了解决这两个问题。在DDD设计规范中,Application针对View进行服务,View需要什么类型的数据,那么Application就返回什么类型的数据。在本例子中,Application会返回一个包含了上述所有ViewBag类型的综合model。这样就可以把大量代码转移到Application或是Core层去实现,且前台只接受一个含有具体数据的model,model是强类型的,不用考虑数据类型转换问题。

在ABP里,作者推荐为每一个应用服务单独建立目录,且每一个应用服务目录中都应包含Dto子目录。该目录用于存放ViewModel。Application层负责将从Core或是Repository中得到的Entity转化成ViewModel,发送到前台。参见上图,我们将UserType这个应用服务单独创建目录(单独将UserType视为一个应用服务其实不太合理,更合理的做法是将和User相关的所有内容统一在User这个应用服务中实现)。Dto目录里存放着UserTypeAppService中每一个方法所对应的输入输出参数。ABP推荐将Application中的服务方法所需的参数全部类型化。并分别以Input、Output结尾以做区分。比如我们的删除用户类别方法,需要两个参数:long oldId, long newId,我们仍旧把这两个参数组成一个类去传递。这样做的好处是,日后进行重构或是功能调整时,会大幅减少程序中的修改地方。降低出现bug的几率。

namespace EasyFast.Application.UserType
{
public interface IUserTypeAppService : IApplicationService
{
UserTypeInput Find(long id);
long Add(UserTypeInput model);
long Update(UserTypeInput model);
EasyUIDataGrid<UserTypeDataGridDto> GetDataGrid(UserTypeSearch search);
/// <summary>
/// 检测传入的全部用户类别是否含有用户,用于判断直接删除or转移用户后再删除
/// </summary>
/// <param name="ids">long[] Model.UserType.Id</param>
/// <returns>true:含有用户 false:不含用户</returns>
bool CheckIsHaveUser(long[] ids);
void Delete(DeleteInput model);
}
}
namespace EasyFast.Application.UserType
{
public class UserTypeAppService : EasyFastAppServiceBase, IUserTypeAppService
{
private readonly IRepository<Core.Entities.UserType, long> _userTypeRepository;
private readonly IUserTypeService _userTypeService; public UserTypeAppService(IRepository<Core.Entities.UserType, long> userTypeRepository, IUserTypeService userTypeService)
{
_userTypeRepository = userTypeRepository;
_userTypeService = userTypeService;
} public UserTypeInput Find(long id)
{
var data = _userTypeRepository.FirstOrDefault(id);
return Mapper.Map<UserTypeInput>(data);
} public long Add(UserTypeInput model)
{
var data = Mapper.Map<Core.Entities.UserType>(model);
return _userTypeService.Add(data);
} public long Update(UserTypeInput model)
{
var data = Mapper.Map<Core.Entities.UserType>(model);
return _userTypeService.Update(data);
} public EasyUIDataGrid<UserTypeDataGridDto> GetDataGrid(UserTypeSearch search)
{
var data = _userTypeRepository.GetAll()
.Where(o => o.Name.Contains(search.Name), !string.IsNullOrEmpty(search.Name));
var total = data.Count();
var list = Mapper.Map<List<UserTypeDataGridDto>>(data);
var rows = list.OrderBy(String.Format("{0} {1}", search.Sort, search.Order))
.Skip((search.Page - ) * search.Rows).Take(search.Rows).ToList();
return new EasyUIDataGrid<UserTypeDataGridDto> { total = total, rows = rows };
} public bool CheckIsHaveUser(long[] ids)
{
return _userTypeRepository.GetAllIncluding(o => o.User).Where(o => ids.Contains(o.Id)).Any(o => o.User != null);
} public void Delete(DeleteInput model)
{
_userTypeService.Delete(model.OldId, model.NewId);
}
}
}

ABP要求给所有应用服务提取接口,并且接口要继承自IApplicationService。只有继承了这个接口,ABP才会自动实现依赖注入。在UserTypeAppService类中,我们自动注入了UserTypeService这个领域服务和UserTypeRepository这个仓储。除了使用构造参数的注入方式外,您也可以使用属性注入,但构造参数注入显得更高大上一点。在作者理解,简单功能,应用服务直接调用仓储接口实现。复杂功能(尤指业务逻辑代码)在领域服务中实现(Core中的Service),然后应用服务调用领域服务的处理结果,返回给用户。其中,部分功能通过系统默认的仓储接口无法实现的,就自定义仓储然后根据情况,选择应用服务或是领域服务调用并返回。

在我们的设计实现中,新增或是修改人员类别时,要保证不重名,我们没有采用数据库唯一性约束,而是通过代码实现的,先重名检测,再进行增、改这部分代码属于业务逻辑。所以我们将这些代码放在了领域服务层去实现,应用服务本身不处理这些,其通过调用领域服务中对应的方法并返回合适的结果,供前台使用。

因ViewModel(或者称为Dto,一个意思,两种常见名称)只在Web和Application中使用,所以将AutoMapper相关的映射代码防止在Application层中最合适不过。我们可以新建一个AutoMapperConfig类,并在其中配置好映射关系后,直接在EasyFastApplicationModule.cs文件中调用即可。不用再web项目中的Global.asax中再次调用,ABP会自动在应用程序初始化时加载我们的配置文件。

namespace EasyFast.Application.AutoMapper
{
public static class AutoMapperConfig
{
public static void Bind(IMapperConfigurationExpression opt)
{
#region UserType
opt.CreateMap<Core.Entities.UserType, UserTypeInput>();
opt.CreateMap<UserTypeInput, Core.Entities.UserType>()
.ForMember(d => d.User, s => s.Ignore());
opt.CreateMap<Core.Entities.UserType, UserTypeDataGridDto>()
.ForMember(d => d.UserCount, s => s.MapFrom(o => o.User.Count));
#endregion //Mapper.AssertConfigurationIsValid();//验证所有的映射配置是否都正常
} public static void Config()
{
Mapper.Initialize(Bind);
}
}
}
namespace EasyFast.Application
{
[DependsOn(typeof(EasyFastCoreModule), typeof(AbpAutoMapperModule))]
public class EasyFastApplicationModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper =>
{
AutoMapperConfig.Bind(mapper);
//Add your custom AutoMapper mappings here...
//mapper.CreateMap<,>()
});
} public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
}

章节过长,未完待续。另征求意见:如此叙述,是否过于繁琐?如大家普遍认为啰嗦,后续我将省略大部分代码解释及配图说明。只保留关键代码说明及设计思路说明。

三、基础功能模块,用户类别管理——锁、EF并发处理、领域服务、应用服务的划分的更多相关文章

  1. Linux基础3(用户/组管理,rpm,yum,源码安装软件)

    用户管理 与用户相关的配置文件 /etc/passwd /etc/shadow /etc/skel /etc/defalut/useradd /etc/login.defs useradd userm ...

  2. Nginx学习笔记1-Nginx功能模块以及进程管理

    1.         功能 1.1.           功能描述 使用缓存加速反向代理,简单负载均衡和容错: 使用缓存机制加速远程FastCGI服务器的访问: 模块化结构: 基本的HTTP功能: 邮 ...

  3. 从业务流程角度:分析TMS系统各个功能模块

    TMS的主要功能是协调承运商.运营商.货主三种角色人员分工合作共同完成运输任务,并实现对运输任务的跟踪管理.本文将按照业务流程顺序对TMS系统各个功能模块进行分析说明. 一.业务描述 新零售的兴起及& ...

  4. CTO也糊涂的常用术语:功能模块、业务架构、用户需求、文档……

    功能模块.业务架构.需求分析.用户需求.系统分析.功能设计.详细设计.文档.业务.技术--很多被随口使用的名词,其实是含糊甚至错误的. 到底含糊在哪里,错误在哪里,不仅仅是新手软件开发人员糊涂,许多入 ...

  5. 六、EnterpriseFrameWork框架基础功能之权限管理

    回<[开源]EnterpriseFrameWork框架系列文章索引> 从本章开始进入框架的第二块内容“EnterpriseFrameWork框架的基础功能”,包括:权限管理.字典数据管理. ...

  6. MySQL基础篇(06):事务管理,锁机制案例详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.锁概念简介 1.基础描述 锁机制核心功能是用来协调多个会话中多线程并发访问相同资源时,资源的占用问题.锁机制是一个非常大的模块,贯彻MyS ...

  7. RDIFramework.NET V3.3 Web版新增报表管理功能模块-重量级实用功能

    功能描述 在RDIFramework.NET V3.3 Web版本新增了全新的报表管理功能模块,非常实用的功能,重量级推荐.主要用于对日常常用的报表做定制展示.可以自动发布到模块(就可授权给指定资源访 ...

  8. Django基础篇--用户权限管理和组管理

    Django作为一个成熟的python后台开发框架,为开发者提供了很多内置的功能,开发者只需要做一些配置就可以完成原生操作中比较复杂的代码编写.这些内置功能中其中一个比较强大的功能就是后台用户管理类. ...

  9. Keepalived详解(三):Keepalived基础功能应用实例【转】

    Keepalived基础功能应用实例: 1.Keepalived基础HA功能演示: 在默认情况下,Keepalived可以实现对系统死机.网络异常及Keepalived本身进行监控,也就是说当系统出现 ...

随机推荐

  1. 希尔排序(c++)

    希尔排序(Shell Sort)是插入排序的一种.也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法.该方法因DL.Shell于1959年提出而得名. 希尔排序是把记 ...

  2. bzoj2748[HAOI2012]音量调节(背包问题的方案)

    Description 一个吉他手准备参加一场演出.他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都要改变一次音量.在演出开始之前,他已经做好了一个列表,里面写着在每首歌开始之前他想要改 ...

  3. iOS UIControl 详解

    UIControl是UIView的子类,当然也是UIResponder的子类.UIControl是诸如UIButton,UISwitch,UItextField等控件的父类,它本身包含了一些属性和方法 ...

  4. 关于IOS免证书真机安装的过程和问题

    由于本人是边工作边转的IOS,所以一直都没怎么使用过免证书安装过程,通常都是公司申请的99美元的账号直接开发.但是前两天有个朋友需要在展会上用的Ipad上安装内网应用,申请一个苹果账号还要审核前后加起 ...

  5. 独立成分分析(ICA)在fMRI数据处理时timecourse的理解

    来源: http://blog.sciencenet.cn/blog-479412-434990.html   在处理fMRI数据时,使用空间ICA的方法.将一个四维的fMRI数据分解为空间patte ...

  6. js/jquery判断浏览器的方法小结

    在网站前端开发中,浏览器兼容性是前端开发框架要解决的第一个问题,要解决兼容性问题就得首先准确判断出浏览器的类型及其版本,而判断浏览器的版本一般只能通过分析浏览器的userAgent才能知道.今天我们把 ...

  7. 微软架构师解读Windows Server 2008 R2新特性

    目前众多企业都开始为自己寻找一个更加适合自身发展的服务器操作平台.微软的Windows Server 2008 R2就是可以为大家解决服务器平台问题.微软最新的服务器平台Windows Server ...

  8. Java中HashSet,HashMap和HashTable的区别

    HashMap.HashSet.HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析: 在分析之前,先将其区别列于下面 1:HashSet底层采用的 ...

  9. 【转】$_POST 与 php://input的区别分析

    $data = file_get_contents("php://input"); php://input 是个可以访问请求的原始数据的只读流. POST 请求的情况下,最好使用 ...

  10. GET请求参数为中文时乱码分析

    问题描述 近期做任务时,跟后端联调时遇到一个问题,前端发送get请求,当参数值有中文时,请求失败,请求参数变为乱码.(ps:一般当参数有中文时,很少使用get请求,而是使用post请求来传输数据,请求 ...