什麼是物件儲存
在工作中,我們經常需要將檔案內容(檔案或二進位制流)儲存在應用程式中,例如你可能要儲存商品的封面圖片。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
https://github.com/zhenlei520/MasaFramework.Practice
開源地址
MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks
MASA.Contrib:https://github.com/masastack/MASA.Contrib
MASA.Utils:https://github.com/masastack/MASA.Utils
MASA.EShop:https://github.com/masalabs/MASA.EShop
MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor
如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯絡我們