日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Abp vNext 二进制大对象系统(BLOB)

發布時間:2023/12/4 windows 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Abp vNext 二进制大对象系统(BLOB) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、簡介

ABP vNext 在 v 2.9.x 版本當中添加了 BLOB 系統,主要用于存儲大型二進制文件。ABP 抽象了一套通用的 BLOB 體系,開發人員在存儲或讀取二進制文件時,可以忽略具體實現,直接使用IBlobContainer?或?IBlobContainer<T>?進行操作。官方的 BLOB Provider 實現有AzureAWSFileSystem(文件系統存儲)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> : IBlobContainerwhere 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 名稱標準化對象

IBlobNamingNormalizer(BLOB 名稱標準化對象),主要用于將一個字符串進行標準化處理,防止 Provider 無法處理這種名稱。各大 OSS 都對容器的名稱或對象的名稱有命名要求,比如必須全部小寫,不能有哪些特殊符號等等。

protected virtual (string, string) NormalizeNaming(string containerName, string blobName) {// 從當前的配置信息中獲取對應的標準化器,如果不存在任何標準化工具對象,則直接返回原始名稱。if (!Configuration.NamingNormalizers.Any()){return (containerName, blobName);}using (var scope = ServiceProvider.CreateScope()){// 獲取所有的標準化器,并依次進行名稱的標準化處理。foreach (var normalizerType in Configuration.NamingNormalizers){var normalizer = scope.ServiceProvider.GetRequiredService(normalizerType).As<IBlobNamingNormalizer>();containerName = normalizer.NormalizeContainerName(containerName);blobName = normalizer.NormalizeBlobName(blobName);}return (containerName, blobName);} }
2.2.3.2 BLOB 上下文

在 BLOB 里面,ABP 分別為每個操作都定義了一個?***Args?對象,它就是一個上下文對象,用于在整個調用周期中傳遞參數。

2.2.3.3 BLOB 配置信息

每個 BLOB 容器都會有一個?BlobContainerConfiguration?用于存儲配置信息,它主要有以下幾個重要的屬性。

public class BlobContainerConfiguration {// 當前 BLOB 容器對應的 Provider 類型。public Type ProviderType { get; set; }// 當前 BLOB 容器是否啟用了多租戶。public bool IsMultiTenant { get; set; } = true;// 當前 BLOB 容器的名稱標準化對象。public ITypeList<IBlobNamingNormalizer> NamingNormalizers { get; }// 當前 BLOB 容器的屬性。[NotNull] private readonly Dictionary<string, object> _properties;// 當嘗試獲取某些配置屬性,但是不存在時,會從這個 Configuration 拿取數據。[CanBeNull] private readonly BlobContainerConfiguration _fallbackConfiguration;public BlobContainerConfiguration(BlobContainerConfiguration fallbackConfiguration = null){NamingNormalizers = new TypeList<IBlobNamingNormalizer>();_fallbackConfiguration = fallbackConfiguration;_properties = new Dictionary<string, object>();}[CanBeNull]public T GetConfigurationOrDefault<T>(string name, T defaultValue = default){return (T) GetConfigurationOrNull(name, defaultValue);}[CanBeNull]public object GetConfigurationOrNull(string name, object defaultValue = null){return _properties.GetOrDefault(name) ??_fallbackConfiguration?.GetConfigurationOrNull(name, defaultValue) ??defaultValue;}// ... 其他代碼。 }

在后續各種 Provider 里面定義的配置項,本質上就是對?_properties?字典進行操作。

2.2.3 容器的構造與初始化

BLOB 容器并不是通過 IoC 容器直接解析構造的,而是通過?IBlobContainerFactory?工廠進行創建,與容器相關的配置對象和 BLOB Provider 也是在這個時候進行構造賦值。

public class BlobContainerFactory : IBlobContainerFactory, ITransientDependency {protected IBlobProviderSelector ProviderSelector { get; }protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }protected ICurrentTenant CurrentTenant { get; }protected ICancellationTokenProvider CancellationTokenProvider { get; }protected IServiceProvider ServiceProvider { get; }public BlobContainerFactory(IBlobContainerConfigurationProvider configurationProvider,ICurrentTenant currentTenant,ICancellationTokenProvider cancellationTokenProvider,IBlobProviderSelector providerSelector,IServiceProvider serviceProvider){ConfigurationProvider = configurationProvider;CurrentTenant = currentTenant;CancellationTokenProvider = cancellationTokenProvider;ProviderSelector = providerSelector;ServiceProvider = serviceProvider;}public virtual IBlobContainer Create(string name){// 根據容器的名稱,獲取對應的配置。var configuration = ConfigurationProvider.Get(name);// 構造一個新的容器對象。return new BlobContainer(name,configuration,// 一樣的是根據容器名稱,獲得匹配的 Provider 類型。ProviderSelector.Get(name),CurrentTenant,CancellationTokenProvider,ServiceProvider);} }

那么這個工廠方法是在什么時候調用的呢?跳轉到工廠方法的實現,發現會被一個靜態擴展方法所調用,重要的是這個方法是一個泛型方法,這樣就與開頭的類型化 BLOB 容器相對應了。

public static class BlobContainerFactoryExtensions {public static IBlobContainer Create<TContainer>(this IBlobContainerFactory blobContainerFactory){// 通過 GetContainerName 方法獲取容器的名字。return blobContainerFactory.Create(BlobContainerNameAttribute.GetContainerName<TContainer>());} }

GetContainerName()?方法也很簡單,如果容器類型沒有指定?BlobContainerNameAttribute特性,那么就會默認使用類型的?FullName?作為名稱。

public static string GetContainerName(Type type) {var nameAttribute = type.GetCustomAttribute<BlobContainerNameAttribute>();if (nameAttribute == null){return type.FullName;}return nameAttribute.GetName(type); }

最后的最后,看一下這個類型化的 BLOB 容器。

public class BlobContainer<TContainer> : IBlobContainer<TContainer>where TContainer : class {private readonly IBlobContainer _container;public BlobContainer(IBlobContainerFactory blobContainerFactory){_container = blobContainerFactory.Create<TContainer>();}// ... 其他代碼。 }

對應的是模塊初始化的工廠方法:

context.Services.AddTransient(typeof(IBlobContainer),serviceProvider => serviceProvider.GetRequiredService<IBlobContainer<DefaultContainer>>()

這里的?DefaultContainer?就指定了該特性,所以本質上一個?IBlobContainer?就是一個類型化的容器,它的泛型參數是?DefaultContainer。

[BlobContainerName(Name)] public class DefaultContainer {public const string Name = "default"; }
2.2.3.1 BLOB 的配置提供者

BLOB 容器工廠使用?IBlobContainerConfigurationProvider?來匹配對應容器的配置信息,實現比較簡單,直接注入了?AbpBlobStoringOptions?并嘗試從它的?BlobContainerConfigurations?中獲取配置對象。

public class DefaultBlobContainerConfigurationProvider : IBlobContainerConfigurationProvider, ITransientDependency {protected AbpBlobStoringOptions Options { get; }public DefaultBlobContainerConfigurationProvider(IOptions<AbpBlobStoringOptions> options){Options = options.Value;}public virtual BlobContainerConfiguration Get(string name){return Options.Containers.GetConfiguration(name);} }

這里的?BlobContainerConfigurations?對象,核心就是一個鍵值對,鍵就是 BLOB 容器的名稱,值就是容器對應的配置對象。

public class BlobContainerConfigurations {private BlobContainerConfiguration Default => GetConfiguration<DefaultContainer>();private readonly Dictionary<string, BlobContainerConfiguration> _containers;public BlobContainerConfigurations(){_containers = new Dictionary<string, BlobContainerConfiguration>{// 添加默認的 BLOB 容器。[BlobContainerNameAttribute.GetContainerName<DefaultContainer>()] = new BlobContainerConfiguration()};}// ... 其他代碼public BlobContainerConfigurations Configure([NotNull] string name,[NotNull] Action<BlobContainerConfiguration> configureAction){Check.NotNullOrWhiteSpace(name, nameof(name));Check.NotNull(configureAction, nameof(configureAction));configureAction(_containers.GetOrAdd(name,() => new BlobContainerConfiguration(Default)));return this;}public BlobContainerConfigurations ConfigureAll(Action<string, BlobContainerConfiguration> configureAction){foreach (var container in _containers){configureAction(container.Key, container.Value);}return this;}// ... 其他代碼 }

在使用過程中,我們在模塊里面調用的?Configure()?方法,就會在字典添加一個新的 Item,并為其賦值。而?ConfigureAll()?就是遍歷這個字典,為每個 BLOB 容器調用委托,以便進行配置。

2.2.3.2 BLOB 的 Provider 選擇器

在構造 BLOB 容器的時候,BLOB 容器工廠通過?IBlobProviderSelector?來選擇對應的 BLOB Provider,具體選擇哪一個是根據?BlobContainerConfiguration?里面的?ProviderType?決定的。

public virtual IBlobProvider Get([NotNull] string containerName) {Check.NotNull(containerName, nameof(containerName));// 獲得當前 BLOB 容器對應的配置信息。var configuration = ConfigurationProvider.Get(containerName);if (!BlobProviders.Any()){throw new AbpException("No BLOB Storage provider was registered! At least one provider must be registered to be able to use the Blog Storing System.");}foreach (var provider in BlobProviders){// 通過配置信息匹配對應的 Provider。if (ProxyHelper.GetUnProxiedType(provider).IsAssignableTo(configuration.ProviderType)){return provider;}}throw new AbpException($"Could not find the BLOB Storage provider with the type ({configuration.ProviderType.AssemblyQualifiedName}) configured for the container {containerName} and no default provider was set."); }

上面的?BlobProviders?其實就是直接從 IoC 解析的?IEnumerable<IBlobProvider>?對象,我還找了半天是哪個地方進行賦值的。當 ABP 框架自動之后,會自動將已經實現的 BLOB Provider 注入到 IoC 容器中,如果某個容器在使用時指定了對應的配置參數,則會匹配對應的 BLOB Provider。

2.3 Provider 的實現

2.3.1 File System

文件系統作為 BLOB 的最簡化實現,本質就是通過文件夾進行租戶隔離動作,所有操作都會將數據持久化到硬盤上。核心代碼就一個文件?FileSystemBlobProvider,在這個文件內部定義了具體的執行邏輯,我們這里大概看一下?SaveAsyn()?的實現。

public override async Task SaveAsync(BlobProviderSaveArgs args) {var filePath = FilePathCalculator.Calculate(args);if (!args.OverrideExisting && await ExistsAsync(filePath)){throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");}DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath));var fileMode = args.OverrideExisting? FileMode.Create: FileMode.CreateNew;await Policy.Handle<IOException>().WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(retryCount)).ExecuteAsync(async () =>{using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write)){await args.BlobStream.CopyToAsync(fileStream,args.CancellationToken);await fileStream.FlushAsync();}}); }

很簡單,通過?FilePathCalculator計算出來文件的具體路徑,然后結合配置參數來判斷文件是否存在,以及是否進入后續操作。通過?Polly?提供的重試機制來創建文件。

2.3.2 DataBase

數據庫 Provider 是利用數據庫的 BLOB 類型,將這些大型對象存儲到數據庫當中,不太建議這樣操作。這里不再進行詳細介紹,基本大同小異。

2.3.3 各類 OSS (騰訊云為例)

OSS 作為云廠商的標配,基本概念和操作都與 ABP 的 BLOB 相匹配,集成起來也還是比較簡單,就是將各個 OSS 的 SDK 塞進來就行。這里注意點的是,每個 BLOB Provider 都會編寫一個基于?BlobContainerConfiguration?類型的靜態方法,取名都叫做?UseXXX(),并在里面對具體的配置進行賦值。

public static class TencentCloudBlobContainerConfigurationExtensions {public static TencentCloudBlobProviderConfiguration GetTencentCloudConfiguration(this BlobContainerConfiguration containerConfiguration){return new TencentCloudBlobProviderConfiguration(containerConfiguration);}public static BlobContainerConfiguration UseTencentCloud(this BlobContainerConfiguration containerConfiguration,Action<TencentCloudBlobProviderConfiguration> tencentCloudConfigureAction){containerConfiguration.ProviderType = typeof(TencentCloudBlobProvider);containerConfiguration.NamingNormalizers.TryAdd<TencentCloudBlobNamingNormalizer>();tencentCloudConfigureAction(new TencentCloudBlobProviderConfiguration(containerConfiguration));return containerConfiguration;} }

可能會對這個?TencentCloudBlobProviderConfiguration?有一些好奇,其實就是個套娃,因為直接傳入了?BlobContainerConfiguration?對象,里面的各種屬性本質上就是對配置項的那個?Dictionary<string,object>?進行操作。

public class TencentCloudBlobProviderConfiguration {public string AppId{get => _containerConfiguration.GetConfigurationOrDefault<string>(TencentCloudBlobProviderConfigurationNames.AppId);set => _containerConfiguration.SetConfiguration(TencentCloudBlobProviderConfigurationNames.AppId, value);}public string SecretId{get => _containerConfiguration.GetConfigurationOrDefault<string>(TencentCloudBlobProviderConfigurationNames.SecretId);set => _containerConfiguration.SetConfiguration(TencentCloudBlobProviderConfigurationNames.SecretId, value);}// ... 其他代碼public TencentCloudBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration){_containerConfiguration = containerConfiguration;} }

騰訊云的 BLOB Provider 倉庫:https://github.com/EasyAbp/Abp.BlobStoring.TencentCloud

2.4 回顧

  • 開發人員可以在模塊的?ConfigureService()?階段為所有容器或者特定容器指定參數。

  • ABP vNext 框架會注入所有的 BLOB Provider,并注入默認的?IBlobContainer<DefaultContainer>?容器和其他的類型化容器實現。

  • 當需要使用 BLOB 時,開發人員注入了?IBlobContainer?或?IBlobContainer<T>。

  • BLOB 容器的工廠會根據容器的名稱匹配對應的 BLOB Provider 和配置對象。

  • BLOB Provider 根據 **Args 參數內部附帶的配置對象,讀取對應的配置信息進行自定義的操作。

  • 三、總結

    小型項目直接集成 FileSystem 即可,中大型項目可以使用各種 OSS Provider,BLOB 系統可以簡化開發人員對于大量二進制文件的管理操作。最近工作相當雜亂繁忙,下半年希望有時間繼續學習更新吧。

    相關文章:

    • 我和ABP vNext 的故事

    • 基于 abp vNext 和 .NET Core 開發博客項目 - 終結篇之發布項目

    • [Abp vNext 源碼分析] - 19. 多租戶

    • abp vnext2.0核心組件之DDD組件之實體結構源碼解析

    • 基于Abp VNext框架設計 - Masstransit分布式消息

    • ABP vNext中使用開源日志面板 LogDashboard

    總結

    以上是生活随笔為你收集整理的Abp vNext 二进制大对象系统(BLOB)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。