物件儲存

磊_磊發表於2022-07-12

什麼是物件儲存

在工作中,我們經常需要將檔案內容(檔案或二進位制流)儲存在應用程式中,例如你可能要儲存商品的封面圖片。Masa框架為此提供了物件儲存的功能,並對功能抽象,抽象給我們帶來的好處:

  • 儲存的無關性(不關心儲存平臺時阿里雲OSS還是騰訊雲的COS)
  • 更換儲存平臺成本更低(僅需要更改下儲存的提供者,業務侵染低)
  • 支援自定義儲存提供者(僅需要自行實現IClient

物件儲存提供程式

目前僅支援阿里雲端儲存,後續將逐步提供更多的雲端儲存平臺支援,如果您有喜歡的其它雲端儲存平臺,歡迎提建議,或者自己實現它併為Masa框架做出貢獻

如何製作自定義儲存程式?

快速入門

Masa.BuildingBlocks.Storage.ObjectStorage是物件儲存服務的抽象包,你可以在專案中使用它來進行編寫程式碼,最後在Program.cs中選擇一個儲存提供程式使用即可

  1. 新建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
    
  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
    
  3. 修改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時需要指定
          }
        }
      }
    }
    
  4. 新增上傳檔案服務

    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下的物件,也可用於判斷物件是否存在,獲取臨時憑證等。

  1. 上傳物件

    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,型別為檔案上傳

  2. 刪除物件

    public class DeleteRequest
    {
        public string Key { get; set; }
    }
    
    app.MapDelete("/delete", async (IClient client, [FromBody] DeleteRequest request) =>
    {
        await client.DeleteObjectAsync("storage1-test", request.Key);
    });
    
  3. 判斷物件是否存在

    app.MapGet("/exist", async (IClient client, string key) =>
    {
        await client.ObjectExistsAsync("storage1-test", key);
    });
    
  4. 返回物件資料的流

    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();
        });
    });
    
  5. 獲取臨時憑證(STS)

    app.MapGet("/GetSts", (IClient client) =>
    {
        client.GetSecurityToken();
    });
    

    阿里雲騰訊雲端儲存等平臺使用STS來獲取臨時憑證

  6. 獲取臨時憑證(字串型別的臨時憑證)

    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

建立物件儲存提供程式

以適配騰訊雲端儲存為例:

  1. 新建類庫Masa.Contrib.Storage.ObjectStorage.Tencent

  2. 選中Masa.Contrib.Storage.ObjectStorage.Tencent並新建類DefaultStorageClient,並實現IClient

  3. 由於騰訊雲端儲存提供Sts臨時憑證,所以僅需要實現GetSecurityToken方法即可,GetToken方法可丟擲不支援的異常,並在文件說明即可

  4. 新建類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,歡迎聯絡我們

16373211753064.png

相關文章