ABP vNext 在 v 2.9.x 版本当中添加了 BLOB 系统,主要用于存储大型二进制文件。ABP 抽象了一套通用的 BLOB 体系,开发人员在存储或读取二进制文件时,可以忽略具体实现,直接使用 IBlobContainer 或 IBlobContainer<T> 进行操作。官方的 BLOB Provider 实现有 Azure、AWS、FileSystem(文件系统存储)、Database(数据库存储)、阿里云 OSS,你也可以自己继承 BlobProviderBase 来实现其他的 Provider。
BLOB 常用于各类二进制文件存储和管理,基本就是对云服务的 OSS 进行了抽象,在使用当中也会有 Bucket 和 Object Key 的概念,在 BLOB 里面对应的就是 ContainerName 和 BlobName。
关于 BLOB 的官方使用指南,可以参考 https://docs.abp.io/en/abp/latest/Blob-Storing,本文的阅读前提是建立在你已经阅读过该指南,并有一定的使用经验。
二、源码分析 2.1 模块分析看一个 ABP 的库项目,首先从他的 Module 入手,对应的 BLOB 核心库的 Module 就是 AbpBlobStoringModule 类,在其内部,只进行了两个操作,注入了 IBlobContainer 与 IBlobContainer<> 的实现。
public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient( typeof(IBlobContainer<>), typeof(BlobContainer<>) ); context.Services.AddTransient( typeof(IBlobContainer), serviceProvider => serviceProvider .GetRequiredService<IBlobContainer<DefaultContainer>>() ); }从上述代码可以看出来,IBlobContainer 的默认实现还是基于 BlobContainer<T> 的。那么为啥会有个泛型的 Container,从简介中可以看到 OSS 里面对应的 Bucket 其实就是一个 IBlobContainer。假如你会针对某云的多个 Bucket 进行操作,那么就需要类型化的 BlobContainer 了。
在这里可以看到,IBlobContainer 的实现是一个工厂方法,这一点在后面会进行解释。
2.2 BLOB 容器 2.2.1 容器的定义每个容器就是一个 OSS 的 Bucket,开发人员在对 BLOB 进行操作时,会注入 IBlobContainer/IBlobContainer<T>,通过接口提供的 5 种方法进行操作,这五个方法分别是 保存对象、删除对象、判断对象是否存在、获取对象、获取对象(不存在返回 NULL)。
public interface IBlobContainer { // 保存对象 Task SaveAsync( string name, Stream stream, bool overrideExisting = false, CancellationToken cancellationToken = default ); // 删除对象 Task<bool> DeleteAsync( string name, CancellationToken cancellationToken = default ); // 判断对象是否存在 Task<bool> ExistsAsync( string name, CancellationToken cancellationToken = default ); // 获取对象 Task<Stream> GetAsync( string name, CancellationToken cancellationToken = default ); // 获取对象(不存在返回 NULL) Task<Stream> GetOrNullAsync( string name, CancellationToken cancellationToken = default ); //TODO: Create shortcut extension methods: GetAsArraryAsync, GetAsStringAsync(encoding) (and null versions) }泛型的 BLOB 容器也是集成自该接口,内部没有任何特殊的方法。
public interface IBlobContainer<TContainer> : IBlobContainer where TContainer: class { } 2.2.2 容器的实现容器的两种实现都存放在 BlobContainer.cs 文件当中,标注容器实现内部都会有一个 ContainerName,用于标识不同的容器,并且和其他的组件作为 关联键 进行绑定。每个容器都会关联 BlobContainerConfiguration、IBlobProvider 两个组件,它们分别提供了容器的配置信息和容器的具体实现 Provider,在容器构造的时候根据 ContainerName 分别进行初始化。
public class BlobContainer : IBlobContainer { protected string ContainerName { get; } protected BlobContainerConfiguration Configuration { get; } protected IBlobProvider Provider { get; } protected ICurrentTenant CurrentTenant { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } protected IServiceProvider ServiceProvider { get; } // ... 其他代码。 }可以看到这里还注入了 ICurrentTenant,注入该对象的主要作用是用来处理多租户的情况,如果当前容器启用了多租户,那么会手动 Change()。下面以 SaveAsync() 方法为例。
public virtual async Task SaveAsync( string name, Stream stream, bool overrideExisting = false, CancellationToken cancellationToken = default) { // 变更当前租户信息,当启用了多租户时,会使用当前租户进行变更。 using (CurrentTenant.Change(GetTenantIdOrNull())) { // 根据 ContainerName 取得对应的标准化容器名称和对象名称。 var (normalizedContainerName, normalizedBlobName) = NormalizeNaming(ContainerName, name); // 使用 ContainerName 匹配的 Provider 存储对象数据。 await Provider.SaveAsync( new BlobProviderSaveArgs( normalizedContainerName, Configuration, normalizedBlobName, stream, overrideExisting, CancellationTokenProvider.FallbackToProvider(cancellationToken) ) ); } }这里有两个地方需要单独分析,第一个是 NormalizeNaming() 的作用,第二个是 BlobProviderSaveArgs 对象。
2.2.3.1 名称标准化对象