BLOB存储
在应用中存储文件内容并按需读取是常见需求。除了文件,您可能还需要将各类大型二进制对象(即BLOB)保存至存储系统。例如,保存用户头像图片。
BLOB本质上是字节数组。存储BLOB项目有多种选择:本地文件系统、共享数据库或Azure BLOB存储等。
ABP框架提供了处理BLOB的抽象层,并内置了多种可轻松集成的存储提供程序。这种抽象设计具有以下优势:
- 通过几行配置即可轻松集成您偏好的BLOB存储服务。
- 后续可轻松更换BLOB存储方案,而无需修改应用代码。
- 开发可复用应用模块时,无需对BLOB存储方式做任何假设。
ABP BLOB存储系统还兼容其他ABP特性,如 多租户支持。
BLOB存储提供程序
ABP已内置以下存储提供程序实现:
- 文件系统:将BLOB以标准文件形式存储于本地文件系统目录。
- 数据库:将BLOB存储于数据库中。
- Azure:使用Azure BLOB存储服务。
- 阿里云:使用阿里云存储服务。
- Minio:使用MinIO对象存储。
- AWS:使用Amazon简单存储服务。
- Google:使用Google云存储。
- Bunny:使用Bunny.net存储。
未来将持续增加更多提供程序。您可 提交请求 添加心仪的提供程序,或 自行创建 并 贡献 给 ABP 项目。
通过容器系统支持多提供程序协同工作,每个容器可配置使用不同的提供程序。
必须配置存储提供程序后BLOB存储系统才能正常工作。请参阅各提供程序文档了解配置细节。
安装
Volo.Abp.BlobStoring是定义BLOB存储服务的核心包。使用此包可接入BLOB存储系统而不依赖特定存储提供程序。
使用ABP CLI添加此包至项目:
- 若未安装,请先安装 ABP CLI。
- 在目标
.csproj文件所在目录打开命令行(终端)。 - 运行
abp add-package Volo.Abp.BlobStoring命令。
手动安装时,将Volo.Abp.BlobStoring NuGet包添加至项目,并在项目内的ABP模块类中添加[DependsOn(typeof(AbpBlobStoringModule))]。
IBlobContainer接口
IBlobContainer是存储和读取BLOB的核心接口。应用可包含多个容器,每个容器可独立配置。通过依赖注入IBlobContainer即可使用默认容器。
示例:简单保存和读取指定名称的BLOB字节数据
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IBlobContainer _blobContainer;
public MyService(IBlobContainer blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveBytesAsync(byte[] bytes)
{
await _blobContainer.SaveAsync("my-blob-1", bytes);
}
public async Task<byte[]> GetBytesAsync()
{
return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1");
}
}
}
此服务将给定字节以my-blob-1名称保存,随后读取同名BLOB的字节数据。
BLOB是命名对象,每个BLOB需具备唯一名称(任意字符串)。
IBlobContainer支持处理Stream和byte[]对象,后续章节将详细说明。
保存BLOB
SaveAsync方法用于保存新BLOB或替换现有BLOB。默认支持保存Stream对象,同时提供保存字节数组的快捷扩展方法。
SaveAsync参数说明:
- name (string):BLOB的唯一名称。
- stream (Stream) 或 bytes (byte[]):读取BLOB内容的流或字节数组。
- overrideExisting (bool):设为
true可替换已存在的BLOB内容。默认值为false,若容器中已存在同名BLOB将抛出BlobAlreadyExistsException。
读取/获取BLOB
GetAsync:仅接收BLOB名称,返回用于读取BLOB内容的Stream对象。使用后务必释放流。若未找到指定名称的BLOB则抛出异常。GetOrNullAsync:与GetAsync相反,未找到BLOB时返回null。GetAllBytesAsync:返回byte[]而非Stream。未找到BLOB时仍抛出异常。GetAllBytesOrNullAsync:与GetAllBytesAsync相反,未找到BLOB时返回null。
删除BLOB
DeleteAsync方法接收BLOB名称并删除对应数据。未找到BLOB时不抛出异常,而是返回bool指示是否实际执行了删除操作(如需关注此结果)。
其他方法
ExistsAsync方法简单检查容器中是否存在指定名称的BLOB。
关于BLOB命名
BLOB命名无硬性规则。BLOB名称仅是每容器(及每租户——参见“多租户”章节)内唯一的字符串。但不同存储提供程序可能遵循某些惯例。例如文件系统提供程序会在BLOB名称中使用目录分隔符(/)和文件扩展名(若BLOB名称为images/common/x.png,则实际保存路径为根容器文件夹内的images/common/x.png)。
类型化IBlobContainer
类型化BLOB容器系统用于在应用中创建和管理多个容器:
- 每个容器独立存储。即BLOB名称在容器内唯一,不同容器中可存在同名BLOB且互不影响。
- 每个容器可独立配置,因此可根据配置为各容器选用不同存储提供程序。
创建类型化容器需定义带有BlobContainerName特性的简单类:
using Volo.Abp.BlobStoring;
namespace AbpDemo
{
[BlobContainerName("profile-pictures")]
public class ProfilePictureContainer
{
}
}
若不使用
BlobContainerName特性,ABP将使用类的完整名称(含命名空间),但建议采用稳定的容器名称(即使重命名类也不会变化)。
创建容器类后,可注入对应容器类型的IBlobContainer<T>。
[Authorize]
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer<ProfilePictureContainer> _blobContainer;
public ProfileAppService(IBlobContainer<ProfilePictureContainer> blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveProfilePictureAsync(byte[] bytes)
{
var blobName = CurrentUser.GetId().ToString();
await _blobContainer.SaveAsync(blobName, bytes);
}
public async Task<byte[]> GetProfilePictureAsync()
{
var blobName = CurrentUser.GetId().ToString();
return await _blobContainer.GetAllBytesOrNullAsync(blobName);
}
}
IBlobContainer<T>方法与IBlobContainer完全相同。
开发可复用模块时始终使用类型化容器是最佳实践,这样最终应用可为您的容器配置提供程序而不影响其他容器。
默认容器
若不使用泛型参数直接注入IBlobContainer(如前所述),将获得默认容器。另一种注入默认容器的方式是使用IBlobContainer<DefaultContainer>,两者完全等效。
默认容器名称为default。
命名容器
类型化容器仅是命名容器的快捷方式。可通过注入使用IBlobContainerFactory按名称获取BLOB容器:
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer _blobContainer;
public ProfileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainer = blobContainerFactory.Create("profile-pictures");
}
//...
}
IBlobContainerFactory
IBlobContainerFactory是用于创建BLOB容器的服务。上文已展示示例。
示例:按名称创建容器
var blobContainer = blobContainerFactory.Create("profile-pictures");
示例:按类型创建容器
var blobContainer = blobContainerFactory.Create<ProfilePictureContainer>();
通常无需直接使用
IBlobContainerFactory,因其在注入IBlobContainer或IBlobContainer<T>时内部自动调用。
配置容器
使用容器前需进行配置。最基础的配置是选择BLOB存储提供程序(参见前文“BLOB存储提供程序”章节)。
AbpBlobStoringOptions是配置容器的选项类。可在模块的ConfigureServices方法中配置选项。
配置单个容器
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
//配置内容...
});
});
此示例配置ProfilePictureContainer。也可按容器名称配置:
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure("profile-pictures", container =>
{
//配置内容...
});
});
配置默认容器
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
//配置内容...
});
});
默认容器有特殊逻辑:若未为某容器指定配置,将回退至默认容器配置。这是为所有容器设置默认值,并在需要时为特定容器定制配置的有效方式。
配置所有容器
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
//配置内容...
});
});
此方式可一次性配置所有容器。
与配置默认容器的主要区别在于:
ConfigureAll会覆盖已为特定容器设置的专项配置。
多租户支持
若应用设置为多租户模式,BLOB存储系统可与 多租户 无缝协作。所有提供程序均将多租户作为标准功能实现。它们隔离不同租户的BLOB,确保各租户仅能访问自身BLOB。这意味着不同租户可使用相同BLOB名称。
对于多租户应用,您可能需要单独控制各容器的多租户行为。例如,为特定容器禁用多租户,使其中的BLOB对所有租户可见。这是实现租户间共享BLOB的途径。
示例:为特定容器禁用多租户
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.IsMultiTenant = false;
});
});
若应用非多租户,无需担忧,系统将按预期工作。不必配置
IsMultiTenant选项。
扩展BLOB存储系统
除创建自定义BLOB存储提供程序外,通常无需定制BLOB存储系统。但必要时可通过依赖注入替换任何服务。以下是一些未提及但可能需了解的服务:
IBlobProviderSelector用于按容器名称获取IBlobProvider实例。默认实现(DefaultBlobProviderSelector)根据配置选择提供程序。IBlobContainerConfigurationProvider用于获取指定容器名称的BlobContainerConfiguration。默认实现(DefaultBlobContainerConfigurationProvider)从上述AbpBlobStoringOptions获取配置。
BLOB存储 vs 文件管理系统
请注意BLOB存储并非文件管理系统。它是用于保存、获取和删除命名BLOB的低层系统。不提供典型文件系统中的层次化结构(如目录)。
若需创建文件夹、在文件夹间移动文件、为文件分配权限及用户间共享文件,需在BLOB存储系统之上自行实现应用逻辑。
抠丁客


