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

  • 相关阅读:
    计算器
    安卓第四周作业
    安卓第三次作业
    补10.21
    补:10.21
    增删改查
    android-數據庫
    安卓 -登陆界面
    android 第Ⅱ次作业
    第一个java代码
  • 原文地址:https://www.cnblogs.com/xhznl/p/13723906.html
Copyright © 2011-2022 走看看