zoukankan      html  css  js  c++  java
  • 初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob存储

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

    前言

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

    开始

    聚合根

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

    modulesfile-managementsrcXhznl.FileManagement.DomainFilesFile.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

    modulesfile-managementsrcXhznl.FileManagement.EntityFrameworkCoreEntityFrameworkCoreIFileManagementDbContext.cs:

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

    modulesfile-managementsrcXhznl.FileManagement.EntityFrameworkCoreEntityFrameworkCoreFileManagementDbContext.cs:

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

    配置实体

    modulesfile-managementsrcXhznl.FileManagement.EntityFrameworkCoreEntityFrameworkCoreFileManagementDbContextModelCreatingExtensions.cs:

    public static void ConfigureFileManagement(
        this ModelBuilder builder,
        Action<FileManagementModelBuilderConfigurationOptions> optionsAction = null)
    {
        ......
    
        builder.Entity<File>(b =>
        {
            //Configure table & schema name
            b.ToTable(options.TablePrefix + "Files", options.Schema);
    
            b.ConfigureByConvention();
    
            //Properties
            b.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>即可使用。通常来说默认仓储就够用了,有特殊需求时也可以自定义仓储。

    定义仓储接口

    modulesfile-managementsrcXhznl.FileManagement.DomainFilesIFileRepository.cs:

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

    仓储实现

    modulesfile-managementsrcXhznl.FileManagement.EntityFrameworkCoreFilesEfCoreFileRepository.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);
        }
    }
    

    注册仓储

    modulesfile-managementsrcXhznl.FileManagement.EntityFrameworkCoreEntityFrameworkCoreFileManagementEntityFrameworkCoreModule.cs:

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

    领域服务

    定义领域服务接口

    modulesfile-managementsrcXhznl.FileManagement.DomainFilesIFileManager.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 存储 ,这里不多介绍。

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

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

    领域服务实现

    modulesfile-managementsrcXhznl.FileManagement.DomainFilesFileManager.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);
        }
    }
    

    应用服务

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

    应用服务接口

    modulesfile-managementsrcXhznl.FileManagement.Application.ContractsFilesIFileAppService.cs:

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

    应用服务实现

    modulesfile-managementsrcXhznl.FileManagement.ApplicationFilesFileAppService.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);//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控制器

    modulesfile-managementsrcXhznl.FileManagement.HttpApiFilesFileController.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

    配置默认容器

    modulesfile-management estXhznl.FileManagement.Application.TestsFileManagementApplicationTestModule.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);
        }
    }
    

    测试用例

    modulesfile-management estXhznl.FileManagement.Application.TestsFilesFileAppService_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:WorkSpaceWorkFiles杂项图片微信图片_20200813165555.jpg")
            });
            blobName.ShouldNotBeEmpty();
    
            var fileDto = await _fileAppService.FindByBlobNameAsync(blobName);
            fileDto.ShouldNotBeNull();
            fileDto.FileName.ShouldBe("微信图片_20200813165555.jpg");
        }
    }
    

    运行测试

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

    模块引用

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

    srcXhznl.HelloAbp.EntityFrameworkCore.DbMigrationsEntityFrameworkCoreHelloAbpMigrationsDbContext.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

  • 相关阅读:
    03_ if 练习 _ little2big
    uva 11275 3D Triangles
    uva 12296 Pieces and Discs
    uvalive 3218 Find the Border
    uvalive 2797 Monster Trap
    uvalive 4992 Jungle Outpost
    uva 2218 Triathlon
    uvalive 3890 Most Distant Point from the Sea
    uvalive 4728 Squares
    uva 10256 The Great Divide
  • 原文地址:https://www.cnblogs.com/xhznl/p/13723906.html
Copyright © 2011-2022 走看看