1

对象存储 - 磊_磊

 1 year ago
source link: https://www.cnblogs.com/zhenlei520/p/16470089.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

什么是对象存储

在工作中,我们经常需要将文件内容(文件或二进制流)存储在应用程序中,例如你可能要保存商品的封面图片。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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK