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

在上一章节中,我们处理了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 = 999;
            Guid = Guid.NewGuid();
        }

        /// <summary>
        /// model的Guid,用于记录操作日志
        /// </summary>
        public Guid Guid { get; set; }

        /// <summary>
        /// 排序Id
        /// </summary>
        [Range(1,999)]
        public int OrderId { get; set; }

        /// <summary>
        /// 行号,用于乐观并发控制
        /// </summary>
        [Timestamp]
        public byte[] RowVersion { get; set; }
    }
}
namespace EasyFast.Core.Entities
{
    public class UserType : BaseEntity
    {
        /// <summary>
        /// 人员类别名称
        /// </summary>
        [StringLength(50)]
        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 - 1) * 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());
        }
    }
}

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

时间: 2024-12-22 19:39:18

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

Nginx功能模块以及进程管理

1.      功能 1.1.           功能描述 使用缓存加速反向代理,简单负载均衡和容错: 使用缓存机制加速远程FastCGI服务器的访问: 模块化结构: 基本的HTTP功能: 邮件代理服务器功能: 架构可扩展:非阻塞.时间驱动.一个master多个worker.高度模块化: 主要扮演角色为反向代理.CDN缓存服务 1.2.           基本模块 内核模块.事件驱动模块.邮件模块.服务模块 相关配置参数举例: 内核模块参数env\error_log\master_proce

2017-11-13Linux基础知识(11)用户、组和权限管理

在之前的章节中讲述了bash的基础特性以及IO重定向以和管道等其它的功能程序,之后介绍了一些其相关的命令,例如:tr和tee命令等,之后我们介绍了Here document,在这一章中,我们开始讲述用户.组及权限管理中的一部分,我们首先来说用户和组. 一.用户 Linux操作系统是一款多用户Multi-Users及多任务Multi-tasks的操作系统,也就是说,可以多个用户同时登录该系统执行各自的任务,彼此用户与用户之间互不干扰,这也是当时为了解决主机资源所提供的一种有效手段,那时候主机都在中

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

本文源码:GitHub·点这里 || GitEE·点这里 一.锁概念简介 1.基础描述 锁机制核心功能是用来协调多个会话中多线程并发访问相同资源时,资源的占用问题.锁机制是一个非常大的模块,贯彻MySQL的几大核心难点模块:索引,锁机制,事务.这里是基于MySQL5.6演示的几种典型场景,对面MySQL这几块问题时,有分析流程和思路是比较关键的.在MySQL中常见这些锁概念:共享读锁.排它写锁 ; 表锁.行锁.间隙锁. 2.存储引擎和锁 MyISAM引擎:基于读写两种模式,支持表级锁 ; Inn

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

回<[开源]EnterpriseFrameWork框架系列文章索引> 从本章开始进入框架的第二块内容“EnterpriseFrameWork框架的基础功能”,包括:权限管理.字典数据管理.报表管理和消息管理四块,这些功能又包括两个版本,Web版和Winform版也就是说有两套界面: 既然开始讲基础功能,顺便说一下EnterpriseFrameWork框架的适用范围,前面也有提到过就是此框架适合中小团队这是一方面,还一方面就是此框架适合行业应用系统软件的开发,你用它做一个“超市库存管理系统”.“

mysql之锁、存储引擎和用户账户管理

一.锁(自动加锁和释放锁) 二.存储引擎 三.用户账户管理---用户授权 原文地址:https://www.cnblogs.com/yuxiangyang/p/11079236.html

python爬虫主要就是五个模块:爬虫启动入口模块,URL管理器存放已经爬虫的URL和待爬虫URL列表,html下载器,html解析器,html输出器 同时可以掌握到urllib2的使用、bs4(BeautifulSoup)页面解析器、re正则表达式、urlparse、python基础知识回顾(set集合操作)等相关内容。

本次python爬虫百步百科,里面详细分析了爬虫的步骤,对每一步代码都有详细的注释说明,可通过本案例掌握python爬虫的特点: 1.爬虫调度入口(crawler_main.py) # coding:utf-8from com.wenhy.crawler_baidu_baike import url_manager, html_downloader, html_parser, html_outputer print "爬虫百度百科调度入口" # 创建爬虫类class SpiderMai

系统管理模块_岗位管理_实现CRUD功能的具体步骤并设计Role实体

系统管理模块_岗位管理_实现CRUD功能的具体步骤并设计Role实体 1,设计实体/表 设计实体 --> JavaBean --> hbm.xml --> 建表 设计Role实体 1 public class Role { 2 private Long id; 3 private String name; 4 private String description; 5 public Long getId() { 6 return id; 7 } 8 public void setId(L

2017-11-17Linux基础知识(12)用户和组的管理命令

在上一章中我们讲述了通配符(bash globing)以及IO重定向及管道,以及介绍了用户管理的基本概念,主要讲述了其用户类别和组类别及管理用户和组的数据库文件,在用户类别当中分为管理员和普通用户这两个大类,而普通用户又分为系统用户和登录用户这个两类.之后在组类别当中介绍了其管理组和普通用户组还有一个组类别是基本组和附加组,最后一个组类别为似有组和公共组,那么接下来我们讲述Linux用户和组的管理命令. 一.安全上下文 我们都知道,所有的进程都是使用发起者的身份来运行,那么对于操作系统来讲,所谓

三周第二次课(12月26) 3.4 usermod命令 3.5 用户密码管理 3.6 mkpasswd命令

三周第二次课(12月26) 3.4 usermod命令3.5 用户密码管理3.6 mkpasswd命令 usermod命令: 用户和工作组管理: usermod命令用于修改用户的基本信息. usermod命令不允许你改变正在线上的使用者帐号名称. 当usermod命令用来改变user id, 必须确认这名user没在电脑上执行任何程序. 你需手动更改使用者的crontab档. 也需手动更改使用者的at工作档. 采用NIS server须在server上更动相关的NIS设定. 语法: usermo