初识ABPvNext(11)聚合根仓储领域服务、Blob存储

  Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章。

  目录

  前言开始聚合根仓储领域服务BLOB存储应用服务单元测试模块引用最后

  前言

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

  开始聚合根

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

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

  public class File : FullAuditedAggregateRoot, 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 Files { get; }

  }

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

  public class FileManagementDbContext : AbpDbContext, IFileManagementDbContext

  {

  public DbSet Files { get; set; }

  ......

  }

  配置实体

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

  public static void ConfigureFileManagement(

  this ModelBuilder builder,

  Action optionsAction=null)

  {

  ......

  builder.Entity(b=>

  {

  //Configure table & schema name

  b.ToTable(options.TablePrefix + "Files", options.Schema);

  b.ConfigureByConvention();

  //Properties

  bperty(q=> q.FileName).IsRequired().HasMaxLength(FileConsts.MaxFileNameLength);

  bperty(q=> q.BlobName).IsRequired().HasMaxLength(FileConsts.MaxBlobNameLength);

  bperty(q=> q.ByteSize).IsRequired();

  });

  }

  仓储

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

  定义仓储接口

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

  public interface IFileRepository : IRepository

  {

  Task FindByBlobNameAsync(string blobName);

  }

  仓储实现

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

  public class EfCoreFileRepository : EfCoreRepository, IFileRepository

  {

  public EfCoreFileRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider)

  {

  }

  public async Task 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(options=>

  {

  options.AddRepository();

  });

  }

  }

  领域服务

  定义领域服务接口

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

  public interface IFileManager : IDomainService

  {

  Task FindByBlobNameAsync(string blobName);

  Task CreateAsync(string fileName, byte[] bytes);

  Task GetBlobAsync(string blobName);

  }

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

  BLOB存储

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

  安装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 FindByBlobNameAsync(string blobName)

  {

  Check.NotNullOrWhiteSpace(blobName, nameof(blobName));

  return await FileRepository.FindByBlobNameAsync(blobName);

  }

  public virtual async Task 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 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 FindByBlobNameAsync(string blobName);

  Task 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 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 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

  {

  new ValidationResult("Bytes can not be null or empty!", new[] {"Bytes"})

  });

  }

  var allowedMaxFileSize=await SettingProvider.GetAsync(FileManagementSettings.AllowedMaxFileSize);//kb

  var 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控制器

  \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 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 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 est\Xhznl.FileManagement.Application.Tests\FileManagementApplicationTestModule.cs:

  [DependsOn(

  typeof(FileManagementApplicationModule),

  typeof(FileManagementDomainTestModule),

  typeof(AbpBlobStoringFileSystemModule)

  )]

  public class FileManagementApplicationTestModule : AbpModule

  {

  public override void ConfigureServices(ServiceConfigurationContext context)

  {

  Configure(options=>

  {

  options.Containers.ConfigureDefault(container=>

  {

  container.UseFileSystem(fileSystem=>

  {

  fileSystem.BasePath="D:\\my-files";

  });

  });

  });

  base.ConfigureServices(context);

  }

  }

  测试用例

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

  public class FileAppService_Tests : FileManagementApplicationTestBase

  {

  private readonly IFileAppService _fileAppService;

  public FileAppService_Tests()

  {

  _fileAppService=GetRequiredService();

  }

  [Fact]

  public async Task Create_FindByBlobName_Test()

  {

  var blobName=await _fileAppService.CreateAsync(new FileDto()

  {

  FileName="微信图片_20220813165555.jpg",

  Bytes=await System.File.ReadAllBytesAsync(@"D:\WorkSpace\WorkFiles\杂项\图片\微信图片_20220813165555.jpg")

  });

  blobName.ShouldNotBeEmpty();

  var fileDto=await _fileAppService.FindByBlobNameAsync(blobName);

  fileDto.ShouldNotBeNull();

  fileDto.FileName.ShouldBe("微信图片_20220813165555.jpg");

  }

  }

  运行测试

  

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

  

  模块引用

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

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

  public class HelloAbpMigrationsDbContext : AbpDbContext

  {

  public HelloAbpMigrationsDbContext(DbContextOptions options)

  : base(options)

  {

  }

  protected override void OnModelCreating(ModelBuilder builder)

  {

  ......

  builder.ConfigureFileManagement();

  ......

  }

  }

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

  Add-Migration "Added_FileManagement"

  Update-Database

  

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

  

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

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

  

  

  最后

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值