初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存
點(diǎn)擊上方藍(lán)字"小黑在哪里"關(guān)注我吧
聚合根
倉儲(chǔ)
領(lǐng)域服務(wù)
BLOB儲(chǔ)存
應(yīng)用服務(wù)
單元測(cè)試
模塊引用
前言
在前兩節(jié)中介紹了ABP模塊開發(fā)的基本步驟,試著實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的文件管理模塊;功能很簡(jiǎn)單,就是基于本地文件系統(tǒng)來完成文件的讀寫操作,數(shù)據(jù)也并沒有保存到數(shù)據(jù)庫,所以之前只簡(jiǎn)單使用了應(yīng)用服務(wù),并沒有用到領(lǐng)域?qū)印6贒DD中領(lǐng)域?qū)邮欠浅V匾囊粚?#xff0c;其中包含了實(shí)體,聚合根,領(lǐng)域服務(wù),倉儲(chǔ)等等,復(fù)雜的業(yè)務(wù)邏輯也應(yīng)該在領(lǐng)域?qū)觼韺?shí)現(xiàn)。本篇來完善一下文件管理模塊,將文件記錄保存到數(shù)據(jù)庫,并使用ABP BLOB系統(tǒng)來完成文件的存儲(chǔ)。
開始
聚合根
首先從實(shí)體模型開始,建立File實(shí)體。按照DDD的思路,這里的File應(yīng)該是一個(gè)聚合根。
\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;?}...... }配置實(shí)體
\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();}); }倉儲(chǔ)
ABP為每個(gè)聚合根或?qū)嶓w提供了 默認(rèn)的通用(泛型)倉儲(chǔ) ,其中包含了標(biāo)準(zhǔn)的CRUD操作,注入IRepository<TEntity, TKey>即可使用。通常來說默認(rèn)倉儲(chǔ)就夠用了,有特殊需求時(shí)也可以自定義倉儲(chǔ)。
定義倉儲(chǔ)接口
\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileRepository.cs:
public?interface?IFileRepository?:?IRepository<File,?Guid> {Task<File>?FindByBlobNameAsync(string?blobName); }倉儲(chǔ)實(shí)現(xiàn)
\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);} }注冊(cè)倉儲(chǔ)
\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>();});} }領(lǐng)域服務(wù)
定義領(lǐng)域服務(wù)接口
\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); }在實(shí)現(xiàn)領(lǐng)域服務(wù)之前,先來安裝一下ABP Blob系統(tǒng)核心包,因?yàn)槲乙褂胋lob來存儲(chǔ)文件,Volo.Abp.BlobStoring包是必不可少的。
BLOB儲(chǔ)存
BLOB(binary large object):大型二進(jìn)制對(duì)象;關(guān)于BLOB可以參考 BLOB 存儲(chǔ)[1] ,這里不多介紹。
安裝Volo.Abp.BlobStoring,在Domain項(xiàng)目目錄下執(zhí)行:abp add-package Volo.Abp.BlobStoring
Volo.Abp.BlobStoring是BLOB的核心包,它僅包含BLOB的一些基本抽象,想要BLOB系統(tǒng)正常工作,還需要為它配置一個(gè)提供程序;這個(gè)提供程序暫時(shí)不管,將來由模塊的具體使用者去提供。這樣的好處是模塊不依賴特定存儲(chǔ)提供程序,使用者可以隨意的指定存儲(chǔ)到阿里云,Azure,或者文件系統(tǒng)等等。。。
領(lǐng)域服務(wù)實(shí)現(xiàn)
\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);} }應(yīng)用服務(wù)
接下來修改一下應(yīng)用服務(wù),應(yīng)用服務(wù)通常沒有太多業(yè)務(wù)邏輯,其調(diào)用領(lǐng)域服務(wù)來完成業(yè)務(wù)。
應(yīng)用服務(wù)接口
\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); }應(yīng)用服務(wù)實(shí)現(xiàn)
\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控制器
最后記得將服務(wù)接口暴露出去,我這里是自己編寫Controller,你也可以使用ABP的自動(dòng)API控制器來完成,請(qǐng)參考 自動(dòng)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);} }單元測(cè)試
針對(duì)以上內(nèi)容做一個(gè)簡(jiǎn)單的測(cè)試,首先為Blob系統(tǒng)配置一個(gè)提供程序。
我這里使用最簡(jiǎn)單的文件系統(tǒng)來儲(chǔ)存,所以需要安裝Volo.Abp.BlobStoring.FileSystem。在Application.Tests項(xiàng)目目錄下執(zhí)行:abp add-package Volo.Abp.BlobStoring.FileSystem
配置默認(rèn)容器
\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);} }測(cè)試用例
\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\雜項(xiàng)\圖片\微信圖片_20200813165555.jpg")});blobName.ShouldNotBeEmpty();var?fileDto?=?await?_fileAppService.FindByBlobNameAsync(blobName);fileDto.ShouldNotBeNull();fileDto.FileName.ShouldBe("微信圖片_20200813165555.jpg");} }運(yùn)行測(cè)試
測(cè)試通過,blob也已經(jīng)存入D:\my-files:
模塊引用
下面回到主項(xiàng)目,前面的章節(jié)中已經(jīng)介紹過,模塊的引用依賴都已經(jīng)添加完成,下面就直接從數(shù)據(jù)庫遷移開始。
\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();......} }打開程序包管理器控制臺(tái),執(zhí)行以下命令:
Add-Migration "Added_FileManagement"
Update-Database
此時(shí)數(shù)據(jù)庫已經(jīng)生成了File表:
還有記得在HttpApi.Host項(xiàng)目配置你想要的blob提供程序。
最后結(jié)合前端測(cè)試一下吧:
最后
以上就是本人所理解的abp模塊開發(fā)一個(gè)相對(duì)完整的流程,還有些概念后面再做補(bǔ)充。因?yàn)檫@個(gè)例子比較簡(jiǎn)單,文中有些環(huán)節(jié)是不必要的,需要結(jié)合實(shí)際情況去取舍。代碼地址:https://github.com/xiajingren/HelloAbp
參考資料
[1]
BLOB 存儲(chǔ): https://docs.abp.io/zh-Hans/abp/latest/Blob-Storing
[2]自動(dòng)API控制器: https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers
如果本文對(duì)您有用,
不妨點(diǎn)個(gè)“在看”或者轉(zhuǎn)發(fā)朋友圈支持一下
總結(jié)
以上是生活随笔為你收集整理的初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob储存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: chrome禁止三方cookie,网站登
- 下一篇: 跟我一起学.NetCore之文件系统应用