初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存

点击上方蓝字"小黑在哪里"关注我吧

  • 聚合根

  • 仓储

  • 领域服务

    • BLOB储存

  • 应用服务

  • 单元测试

  • 模块引用

前言

在前两节中介绍了ABP模块开发的基本步骤,试着实现了一个简单的文件管理模块;功能很简单,就是基于本地文件系统来完成文件的读写操作,数据也并没有保存到数据库,所以之前只简单使用了应用服务,并没有用到领域层。而在DDD中领域层是非常重要的一层,其中包含了实体,聚合根,领域服务,仓储等等,复杂的业务逻辑也应该在领域层来实现。本篇来完善一下文件管理模块,将文件记录保存到数据库,并使用ABP BLOB系统来完成文件的存储。

开始

聚合根

首先从实体模型开始,建立File实体。按照DDD的思路,这里的File应该是一个聚合根

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\File.cs:

public class File : FullAuditedAggregateRoot<Guid>, IMultiTenant
{public virtual Guid? TenantId { get; protected set; }[NotNull]public virtual string FileName { get; protected set; }[NotNull]public virtual string BlobName { get; protected set; }public virtual long ByteSize { get; protected set; }protected File() { }public File(Guid id, Guid? tenantId, [NotNull] string fileName, [NotNull] string blobName, long byteSize) : base(id){TenantId = tenantId;FileName = Check.NotNullOrWhiteSpace(fileName, nameof(fileName));BlobName = Check.NotNullOrWhiteSpace(blobName, nameof(blobName));ByteSize = byteSize;}
}

在DbContext中添加DbSet

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\IFileManagementDbContext.cs:

public interface IFileManagementDbContext : IEfCoreDbContext
{DbSet<File> Files { get; }
}

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContext.cs:

public class FileManagementDbContext : AbpDbContext<FileManagementDbContext>, IFileManagementDbContext
{public DbSet<File> Files { get; set; }......
}

配置实体

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContextModelCreatingExtensions.cs:

public static void ConfigureFileManagement(this ModelBuilder builder,Action<FileManagementModelBuilderConfigurationOptions> optionsAction = null)
{......builder.Entity<File>(b =>{//Configure table & schema nameb.ToTable(options.TablePrefix + "Files", options.Schema);b.ConfigureByConvention();//Propertiesb.Property(q => q.FileName).IsRequired().HasMaxLength(FileConsts.MaxFileNameLength);b.Property(q => q.BlobName).IsRequired().HasMaxLength(FileConsts.MaxBlobNameLength);b.Property(q => q.ByteSize).IsRequired();});
}

仓储

ABP为每个聚合根或实体提供了 默认的通用(泛型)仓储 ,其中包含了标准的CRUD操作,注入IRepository<TEntity, TKey>即可使用。通常来说默认仓储就够用了,有特殊需求时也可以自定义仓储。

定义仓储接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileRepository.cs:

public interface IFileRepository : IRepository<File, Guid>
{Task<File> FindByBlobNameAsync(string blobName);
}

仓储实现

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\Files\EfCoreFileRepository.cs:

public class EfCoreFileRepository : EfCoreRepository<IFileManagementDbContext, File, Guid>, IFileRepository
{public EfCoreFileRepository(IDbContextProvider<IFileManagementDbContext> dbContextProvider) : base(dbContextProvider){}public async Task<File> FindByBlobNameAsync(string blobName){Check.NotNullOrWhiteSpace(blobName, nameof(blobName));return await DbSet.FirstOrDefaultAsync(p => p.BlobName == blobName);}
}

注册仓储

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementEntityFrameworkCoreModule.cs:

public class FileManagementEntityFrameworkCoreModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){context.Services.AddAbpDbContext<FileManagementDbContext>(options =>{options.AddRepository<File, EfCoreFileRepository>();});}
}

领域服务

定义领域服务接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileManager.cs:

public interface IFileManager : IDomainService
{Task<File> FindByBlobNameAsync(string blobName);Task<File> CreateAsync(string fileName, byte[] bytes);Task<byte[]> GetBlobAsync(string blobName);
}

在实现领域服务之前,先来安装一下ABP Blob系统核心包,因为我要使用blob来存储文件,Volo.Abp.BlobStoring包是必不可少的。

BLOB储存

BLOB(binary large object):大型二进制对象;关于BLOB可以参考 BLOB 存储[1] ,这里不多介绍。

安装Volo.Abp.BlobStoring,在Domain项目目录下执行:abp add-package Volo.Abp.BlobStoring

Volo.Abp.BlobStoring是BLOB的核心包,它仅包含BLOB的一些基本抽象,想要BLOB系统正常工作,还需要为它配置一个提供程序;这个提供程序暂时不管,将来由模块的具体使用者去提供。这样的好处是模块不依赖特定存储提供程序,使用者可以随意的指定存储到阿里云,Azure,或者文件系统等等。。。

领域服务实现

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\FileManager.cs:

public class FileManager : DomainService, IFileManager
{protected IFileRepository FileRepository { get; }protected IBlobContainer BlobContainer { get; }public FileManager(IFileRepository fileRepository, IBlobContainer blobContainer){FileRepository = fileRepository;BlobContainer = blobContainer;}public virtual async Task<File> FindByBlobNameAsync(string blobName){Check.NotNullOrWhiteSpace(blobName, nameof(blobName));return await FileRepository.FindByBlobNameAsync(blobName);}public virtual async Task<File> CreateAsync(string fileName, byte[] bytes){Check.NotNullOrWhiteSpace(fileName, nameof(fileName));var blobName = Guid.NewGuid().ToString("N");var file = await FileRepository.InsertAsync(new File(GuidGenerator.Create(), CurrentTenant.Id, fileName, blobName, bytes.Length));await BlobContainer.SaveAsync(blobName, bytes);return file;}public virtual async Task<byte[]> GetBlobAsync(string blobName){Check.NotNullOrWhiteSpace(blobName, nameof(blobName));return await BlobContainer.GetAllBytesAsync(blobName);}
}

应用服务

接下来修改一下应用服务,应用服务通常没有太多业务逻辑,其调用领域服务来完成业务。

应用服务接口

\modules\file-management\src\Xhznl.FileManagement.Application.Contracts\Files\IFileAppService.cs:

public interface IFileAppService : IApplicationService
{Task<FileDto> FindByBlobNameAsync(string blobName);Task<string> CreateAsync(FileDto input);
}

应用服务实现

\modules\file-management\src\Xhznl.FileManagement.Application\Files\FileAppService.cs:

public class FileAppService : FileManagementAppService, IFileAppService
{protected IFileManager FileManager { get; }public FileAppService(IFileManager fileManager){FileManager = fileManager;}public virtual async Task<FileDto> FindByBlobNameAsync(string blobName){Check.NotNullOrWhiteSpace(blobName, nameof(blobName));var file = await FileManager.FindByBlobNameAsync(blobName);var bytes = await FileManager.GetBlobAsync(blobName);return new FileDto{Bytes = bytes,FileName = file.FileName};}[Authorize]public virtual async Task<string> CreateAsync(FileDto input){await CheckFile(input);var file = await FileManager.CreateAsync(input.FileName, input.Bytes);return file.BlobName;}protected virtual async Task CheckFile(FileDto input){if (input.Bytes.IsNullOrEmpty()){throw new AbpValidationException("Bytes can not be null or empty!",new List<ValidationResult>{new ValidationResult("Bytes can not be null or empty!", new[] {"Bytes"})});}var allowedMaxFileSize = await SettingProvider.GetAsync<int>(FileManagementSettings.AllowedMaxFileSize);//kbvar allowedUploadFormats = (await SettingProvider.GetOrNullAsync(FileManagementSettings.AllowedUploadFormats))?.Split(",", StringSplitOptions.RemoveEmptyEntries);if (input.Bytes.Length > allowedMaxFileSize * 1024){throw new UserFriendlyException(L["FileManagement.ExceedsTheMaximumSize", allowedMaxFileSize]);}if (allowedUploadFormats == null || !allowedUploadFormats.Contains(Path.GetExtension(input.FileName))){throw new UserFriendlyException(L["FileManagement.NotValidFormat"]);}}
}

API控制器

最后记得将服务接口暴露出去,我这里是自己编写Controller,你也可以使用ABP的自动API控制器来完成,请参考 自动API控制器[2]

\modules\file-management\src\Xhznl.FileManagement.HttpApi\Files\FileController.cs:

[RemoteService]
[Route("api/file-management/files")]
public class FileController : FileManagementController
{protected IFileAppService FileAppService { get; }public FileController(IFileAppService fileAppService){FileAppService = fileAppService;}[HttpGet][Route("{blobName}")]public virtual async Task<FileResult> GetAsync(string blobName){var fileDto = await FileAppService.FindByBlobNameAsync(blobName);return File(fileDto.Bytes, MimeTypes.GetByExtension(Path.GetExtension(fileDto.FileName)));}[HttpPost][Route("upload")][Authorize]public virtual async Task<JsonResult> CreateAsync(IFormFile file){if (file == null){throw new UserFriendlyException("No file found!");}var bytes = await file.GetAllBytesAsync();var result = await FileAppService.CreateAsync(new FileDto(){Bytes = bytes,FileName = file.FileName});return Json(result);}
}

单元测试

针对以上内容做一个简单的测试,首先为Blob系统配置一个提供程序。

我这里使用最简单的文件系统来储存,所以需要安装Volo.Abp.BlobStoring.FileSystem。在Application.Tests项目目录下执行:abp add-package Volo.Abp.BlobStoring.FileSystem

配置默认容器

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\FileManagementApplicationTestModule.cs:

[DependsOn(typeof(FileManagementApplicationModule),typeof(FileManagementDomainTestModule),typeof(AbpBlobStoringFileSystemModule))]
public class FileManagementApplicationTestModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){Configure<AbpBlobStoringOptions>(options =>{options.Containers.ConfigureDefault(container =>{container.UseFileSystem(fileSystem =>{fileSystem.BasePath = "D:\\my-files";});});});base.ConfigureServices(context);}
}

测试用例

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\Files\FileAppService_Tests.cs:

public class FileAppService_Tests : FileManagementApplicationTestBase
{private readonly IFileAppService _fileAppService;public FileAppService_Tests(){_fileAppService = GetRequiredService<IFileAppService>();}[Fact]public async Task Create_FindByBlobName_Test(){var blobName = await _fileAppService.CreateAsync(new FileDto(){FileName = "微信图片_20200813165555.jpg",Bytes = await System.IO.File.ReadAllBytesAsync(@"D:\WorkSpace\WorkFiles\杂项\图片\微信图片_20200813165555.jpg")});blobName.ShouldNotBeEmpty();var fileDto = await _fileAppService.FindByBlobNameAsync(blobName);fileDto.ShouldNotBeNull();fileDto.FileName.ShouldBe("微信图片_20200813165555.jpg");}
}

运行测试

测试通过,blob也已经存入D:\my-files:

模块引用

下面回到主项目,前面的章节中已经介绍过,模块的引用依赖都已经添加完成,下面就直接从数据库迁移开始。

\src\Xhznl.HelloAbp.EntityFrameworkCore.DbMigrations\EntityFrameworkCore\HelloAbpMigrationsDbContext.cs:

public class HelloAbpMigrationsDbContext : AbpDbContext<HelloAbpMigrationsDbContext>
{public HelloAbpMigrationsDbContext(DbContextOptions<HelloAbpMigrationsDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder builder){......builder.ConfigureFileManagement();......}
}

打开程序包管理器控制台,执行以下命令:

Add-Migration "Added_FileManagement"

Update-Database

此时数据库已经生成了File表:

还有记得在HttpApi.Host项目配置你想要的blob提供程序。

最后结合前端测试一下吧:

最后

以上就是本人所理解的abp模块开发一个相对完整的流程,还有些概念后面再做补充。因为这个例子比较简单,文中有些环节是不必要的,需要结合实际情况去取舍。代码地址:https://github.com/xiajingren/HelloAbp

参考资料

[1]

BLOB 存储: https://docs.abp.io/zh-Hans/abp/latest/Blob-Storing

[2]

自动API控制器: https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers

如果本文对您有用,

不妨点个“”或者转发朋友圈支持一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode367. 有效的完全平方数

一:题目 二:上码 class Solution { public:/**完全平方数:若一个数能表示成某个整数的平方的形式&#xff0c;则称这个数为完全平方数思路:1.我们将num先折半,因为它是某个整数的平方&#xff0c;而这个数的范围肯定不会超过num的一半2.那么这就相当于在[left,num/2]中查找某个…

跟我一起学.NetCore之文件系统应用及核心浅析

前言在开发过程中&#xff0c;肯定避免不了读取文件操作&#xff0c;比如读取配置文件、上传和下载文件、Web中html、js、css、图片等静态资源的访问&#xff1b;在配置文件读取章节中有说到&#xff0c;针对不同配置源数据读取由对应的IConfigurationProvider进行读取&#xf…

深度学习入门笔记(1)——导论部分

此笔记来源于 Sebastian Raschka 的 Introduction to Deep Learning 系列课程。 首先介绍的是传统的编程范式&#xff0c;假设我们想实现垃圾邮件识别的功能&#xff0c;传统的方法就是由程序员来找出垃圾邮件的规则并对其进行编程&#xff0c;得到一个垃圾邮件识别的程序。 机…

深度学习入门笔记(2)—— 感知器

最经典的神经元模型&#xff0c;从左到右依次是&#xff1a;输入、权重、加权和、阈值、输出。加权和又叫做 Net Input&#xff0c;符号为 z&#xff0c;当 z 的值大于阈值时输出 1&#xff0c;小于阈值时输出 0。 实现与门和或门&#xff0c;权重为 1&#xff0c;阈值分别为 1…

创建一个对象时,在一个类当中 静态代码块 和普通代码块构造方法 的顺序?

一:前言须知 普通代码块&#xff0c;在创建对象实例的时候&#xff0c;会被调用&#xff0c;每创建一次&#xff0c;就调用一次静态代码块&#xff0c;在类加载的时候执行&#xff0c;并且只会执行一次类加载的时机: 创建对象实例的时候&#xff08;new&#xff09;创建子类实…

ASP.NET Core 基于声明的访问控制到底是什么鬼?

从ASP.NET 4.x到ASP.NET Core&#xff0c;内置身份验证已从基于角色的访问控制(RBAC)转变为基于声明的访问控制(CBAC)。我们常用的HttpContext.User属性ASP.NET 4.0时代是IPrincipal类型&#xff0c;ASP.NETCore现在强化为ClaimsPrincipal类型。本文就一起来看看这难缠的、晦涩…

回溯的问题合集(Leetcode题解-Python语言)

78. 子集 class Solution:def subsets(self, nums: List[int]) -> List[List[int]]:ans []cur []def dfs(i):if i len(nums):ans.append(cur.copy())return# 包括 nums[i]cur.append(nums[i])dfs(i1)# 不包括 nums[i]cur.pop()dfs(i1)dfs(0)return ans要找出所有子集&a…

一个对象的创建流程

一:流程 加载Person类的信息,(也就是加载Person.class文件 只加载一次) 这个就是类加载的几个过程加载 ,将.class文件转化成二进制流加载到JVM的内存的方法区中&#xff0c;并在堆中生成一个Class对象验证准备解析初始化 该实例堆当中开辟空间 每个类的实例都会记得自己是由哪…

RabbitMq如何确保消息不丢失

上篇写了掌握Rabbitmq几个重要概念&#xff0c;从一条消息说起&#xff0c;这篇来总结关于消息丢失让人头痛的事情。网络故障、服务器重启、硬盘损坏等都会导致消息的丢失。消息从生产到消费主要结果以下几个阶段如下图。①生产阶段&#xff0c;生产者创建消息&#xff0c;经过…

LEETCODE PATTERNS Neetcode 刷题记录(Leetcode题解-Python语言)

LEETCODE PATTERNS 官网在这个链接&#xff0c;Neetcode 官网在这个链接 If input array is sorted then 遇到有序数组用二分或双指针 Binary searchTwo pointers If asked for all permutations/subsets then 求排列或子集用回溯 Backtracking If given a tree then 遇到树就用…

蓝桥杯-单词分析

一:题目 题目描述 小蓝正在学习一门神奇的语言&#xff0c;这门语言中的单词都是由小写英文字母组 成&#xff0c;有些单词很长&#xff0c;远远超过正常英文单词的长度。小蓝学了很长时间也记不住一些单词&#xff0c;他准备不再完全记忆这些单词&#xff0c;而是根据单词中哪…

.NET Core 使用 Consul 服务注册发现

Consul是一个用来实现分布式系统服务发现与配置的开源工具。它内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案&#xff0c;不再需要依赖其他工具&#xff0c;使用起来也较为简单。Consul官网&#xff1a;https://www.consul.io开源地…

蓝桥杯-成绩统计

一:题目 题目描述 小蓝给学生们组织了一场考试&#xff0c;卷面总分为 100 分&#xff0c;每个学生的得分都是一个 0 到 100 的整数。 如果得分至少是 60 分&#xff0c;则称为及格。如果得分至少为 85 分&#xff0c;则称为优秀。 请计算及格率和优秀率&#xff0c;用百分数…

Mosh 的 MySQL 课程编程练习题目与答案

这篇文章是我观看 Mosh 的 MySQL 完整版课程进行题目练习的记录&#xff0c;视频的话去 B 站搜索就能找到&#xff0c;数据库文件的话可以从这里下载。 目录第二章2- SELECT 子句3- WHERE 子句4- 逻辑运算符5- IN 运算符6- BETWEEN 运算符7- LIKE 运算符8- REGEXP 运算符&#…

《ASP.NET Core 真机拆解》 送书活动结果公布

截至2020.09.26 本次送书活动 送福利 | 送书5本 ASP.NET Core 真机拆解 。下面把Top 5的留言截图给大家回顾一下。特别鸣谢作者罗志超提供的图书。以下5位同学将获赠书籍一本&#xff1a;傲慢的上校静阿花阿定傲慢与偏见以上同学请在2020年9月30日24&#xff1a;00之前加小二微…

跟我一起学.NetCore之静态文件处理的那些事

前言如今前后端分离开发模式如火如荼&#xff0c;开发职责更加分明&#xff08;当然前后端一起搞的模式也没有完全褪去&#xff09;&#xff1b;而对于每个公司产品实施来说&#xff0c;部署模式会稍有差别&#xff0c;有的会单独将前端文件部署为一个站点&#xff0c;有的会将…

深度学习入门笔记(3)——用梯度下降进行参数更新

首先是对感知器的简单回顾&#xff0c;假设现有的训练集为 D&#xff0c;共有 n 个训练数据&#xff0c;每个数据都有 m 个输入特征和一个输出标签。一个 epoch 就是遍历一次整个训练集&#xff0c;对于每一个训练数据&#xff0c;都计算其预测、计算误差、更新参数。 在一个 e…

ASP.NET Core Blazor Webassembly 之 路由

web最精妙的设计就是通过url把多个页面串联起来&#xff0c;并且可以互相跳转。我们开发系统的时候总是需要使用路由来实现页面间的跳转。传统的web开发主要是使用a标签或者是服务端redirect来跳转。那今天来看看Blazor是如何进行路由的。使用page指定组件的路由path我们可以在…

手撕单例模式(详解)

一:设计模式概述 1:设计模式的概念 软件设计模式&#xff08;Software Design Pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题&#xff0…

微软发布.NET 5.0 RC1,未来将只有一个.NET

本周早些时候&#xff0c;微软发布了.NET 5.0 RC1&#xff0c;这是.NET 5 在11 月正式发布之前的第一个“go-live”版本。.NET 5 是.NET 生态系统的统一平台&#xff0c;将所有组件打包成一个跨平台包。新版本包含了.NET Core 3 的很多改进&#xff0c;包括新的语言版本(C# 9 和…