對象存儲

什麼是對象存儲

在工作中,我們經常需要將文件內容(文件或二進位流)存儲在應用程式中,例如你可能要保存商品的封面圖片。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

//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,歡迎聯繫我們

16373211753064.png