對象存儲
- 2022 年 7 月 12 日
- 筆記
- .NET, MASA Framework
什麼是對象存儲
在工作中,我們經常需要將文件內容(文件或二進位流)存儲在應用程式中,例如你可能要保存商品的封面圖片。Masa框架為此提供了對象存儲的功能,並對功能抽象,抽象給我們帶來的好處:
- 存儲的無關性(不關心存儲平台時阿里雲OSS還是騰訊雲的COS)
- 更換存儲平台成本更低(僅需要更改下存儲的提供者,業務侵染低)
- 支援自定義存儲提供者(僅需要自行實現
IClient
)
對象存儲提供程式
- 阿里雲: 在阿里雲OSS存儲服務上存儲
目前僅支援阿里雲存儲,後續將逐步提供更多的雲存儲平台支援,如果您有喜歡的其它雲存儲平台,歡迎提建議,或者自己實現它並為Masa框架做出貢獻
快速入門
Masa.BuildingBlocks.Storage.ObjectStorage是對象存儲服務的抽象包,你可以在項目中使用它來進行編寫程式碼,最後在Program.cs
中選擇一個存儲提供程式使用即可
- 安裝.Net 6.0
-
新建ASP.NET Core 空項目
Assignment.OSS
,並安裝Masa.Contrib.Storage.ObjectStorage.Aliyun
dotnet new web -o Assignment.OSS cd Assignment.OSS dotnet add package Masa.Contrib.Storage.ObjectStorage.Aliyun --version 0.5.0-preview.2
-
修改
Program.cs
builder.Services.AddAliyunStorage(); #region 或者通過程式碼指定傳入阿里雲存儲配置資訊使用,無需使用配置文件 // builder.Services.AddAliyunStorage(new AliyunStorageOptions() // { // AccessKeyId = "Replace-With-Your-AccessKeyId", // AccessKeySecret = "Replace-With-Your-AccessKeySecret", // Endpoint = "Replace-With-Your-Endpoint", // RoleArn = "Replace-With-Your-RoleArn", // RoleSessionName = "Replace-With-Your-RoleSessionName", // Sts = new AliyunStsOptions() // { // RegionId = "Replace-With-Your-Sts-RegionId", // DurationSeconds = 3600, // EarlyExpires = 10 // } // }, "storage1-test"); #endregion
-
修改
appsettings.json
,增加阿里雲配置{ "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", "Sts": { "RegionId": "Replace-With-Your-Sts-RegionId", "DurationSeconds": 3600, "EarlyExpires": 10 }, "Storage": { "Endpoint": "Replace-With-Your-Endpoint", "RoleArn": "Replace-With-Your-RoleArn", "RoleSessionName": "Replace-With-Your-RoleSessionName", "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials", "Policy": "", "BucketNames" : { "DefaultBucketName" : "storage1-test"//默認BucketName,非必填項,僅在使用IClientContainer時需要指定 } } } }
-
新增上傳文件服務
app.MapPost("/upload", async (HttpRequest request, IClient client) => { var form = await request.ReadFormAsync(); var formFile = form.Files["file"]; if (formFile == null) throw new FileNotFoundException("Can't upload empty file"); await client.PutObjectAsync("storage1-test", formFile.FileName, formFile.OpenReadStream()); });
進階
IClient
IClient
是用來存儲和讀取對象的主要介面,可以在項目的任意地方通過DI獲取到IClient
來上傳、下載或刪除指定BucketName
下的對象,也可用於判斷對象是否存在,獲取臨時憑證等。
-
上傳對象
app.MapPost("/upload", async (HttpRequest request, IClient client) => { var form = await request.ReadFormAsync(); var formFile = form.Files["file"]; if (formFile == null) throw new FileNotFoundException("Can't upload empty file"); await client.PutObjectAsync("storage1-test", formFile.FileName, formFile.OpenReadStream()); });
Form表單提交,key為file,類型為文件上傳
-
刪除對象
public class DeleteRequest { public string Key { get; set; } } app.MapDelete("/delete", async (IClient client, [FromBody] DeleteRequest request) => { await client.DeleteObjectAsync("storage1-test", request.Key); });
-
判斷對象是否存在
app.MapGet("/exist", async (IClient client, string key) => { await client.ObjectExistsAsync("storage1-test", key); });
-
返回對象數據的流
app.MapGet("/download", async (IClient client, string key, string path) => { await client.GetObjectAsync("storage1-test", key, stream => { //下載文件到指定路徑 using var requestStream = stream; byte[] buf = new byte[1024]; var fs = File.Open(path, FileMode.OpenOrCreate); int len; while ((len = requestStream.Read(buf, 0, 1024)) != 0) { fs.Write(buf, 0, len); } fs.Close(); }); });
-
獲取臨時憑證(STS)
app.MapGet("/GetSts", (IClient client) => { client.GetSecurityToken(); });
-
獲取臨時憑證(字元串類型的臨時憑證)
app.MapGet("/GetToken", (IClient client) => { client.GetToken(); });
七牛雲等存儲平台使用較多
IBucketNameProvider
IBucketNameProvider
是用來獲取BucketName的介面,通過IBucketNameProvider
可以獲取指定存儲空間的BucketName,為IClientContainer
提供BucketName能力,在業務項目中不會使用到
IClientContainer
IClientContainer
對象存儲容器,用來存儲和讀取對象的主要介面,一個應用程式下可能會存在管理多個BucketName,通過使用IClientContainer
,像管理DbContext
一樣管理不同Bucket
的對象,不需要在項目中頻繁指定BucketName
,在同一個應用程式中,有且只有一個默認ClientContainer,可以通過DI獲取IClientContainer
來使用,例如:
-
上傳對象(上傳到默認
Bucket
)app.MapPost("/upload", async (HttpRequest request, IClientContainer clientContainer) => { var form = await request.ReadFormAsync(); var formFile = form.Files["file"]; if (formFile == null) throw new FileNotFoundException("Can't upload empty file"); await clientContainer.PutObjectAsync(formFile.FileName, formFile.OpenReadStream()); });
-
上傳到指定
Bucket
[BucketName("picture")] public class PictureContainer { } builder.Services.Configure<StorageOptions>(option => { option.BucketNames = new BucketNames(new List<KeyValuePair<string, string>>() { new("DefaultBucketName", "storage1-test"),//默認BucketName new("picture", "storage1-picture")//指定別名為picture的BucketName為storage1-picture }); }); app.MapPost("/upload", async (HttpRequest request, IClientContainer<PictureContainer> clientContainer) => { var form = await request.ReadFormAsync(); var formFile = form.Files["file"]; if (formFile == null) throw new FileNotFoundException("Can't upload empty file"); await clientContainer.PutObjectAsync(formFile.FileName, formFile.OpenReadStream()); });
IClientFactory
IClientFactory
對象存儲提供者工廠,通過指定BucketName
,創建指定的IClientContainer
創建對象存儲提供程式
以適配騰訊雲存儲為例:
-
新建類庫
Masa.Contrib.Storage.ObjectStorage.Tencent
-
選中
Masa.Contrib.Storage.ObjectStorage.Tencent
並新建類DefaultStorageClient
,並實現IClient
-
由於騰訊雲存儲提供Sts臨時憑證,所以僅需要實現
GetSecurityToken
方法即可,GetToken
方法可拋出不支援的異常,並在文檔說明即可 -
新建類
ServiceCollectionExtensions
,並提供對IServiceCollection
的擴展方法AddTencentStorage
,例如:public static IServiceCollection AddTencentStorage( this IServiceCollection services, TencentStorageOptions options, string? defaultBucketName = null) { //todo: 添加騰訊雲存儲的客戶端 if (defaultBucketName != null) { services.Configure<StorageOptions>(option => { option.BucketNames = new BucketNames(new List<KeyValuePair<string, string>>() { new(BucketNames.DEFAULT_BUCKET_NAME, defaultBucketName) }); }); services.TryAddSingleton<IClientContainer>(serviceProvider => new DefaultClientContainer(serviceProvider.GetRequiredService<IClient>(), defaultBucketName)); } services.TryAddSingleton<IClientFactory, DefaultClientFactory>(); services.TryAddSingleton<ICredentialProvider, DefaultCredentialProvider>(); services.TryAddSingleton<IClient, DefaultStorageClient>(); return services; }
總結
目前對象存儲暫時並未支援多租戶、多環境,後續根據情況逐步完善增加多租戶、多環境支援,以適配不同的租戶、不同的環境下的對象存儲到指定的Bucket
中
本章源碼
Assignment06
//github.com/zhenlei520/MasaFramework.Practice
開源地址
MASA.BuildingBlocks://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib://github.com/masastack/MASA.Contrib
MASA.Utils://github.com/masastack/MASA.Utils
MASA.EShop://github.com/masalabs/MASA.EShop
MASA.Blazor://github.com/BlazorComponent/MASA.Blazor
如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯繫我們