乐趣区

关于后端:6-对象存储

什么是对象存储

在工作中,咱们常常须要将文件内容(文件或二进制流)存储在应用程序中,例如你可能要保留商品的封面图片。Masa 框架为此提供了对象存储的性能,并对性能形象,形象给咱们带来的益处:

  • 存储的无关性(不关怀存储平台时阿里云 OSS 还是腾讯云的 COS)
  • 更换存储平台老本更低(仅须要更改下存储的提供者,业务侵染低)
  • 反对自定义存储提供者(仅须要自行实现IClient

对象存储提供程序

  • 阿里云: 在阿里云 OSS 存储服务上存储

目前仅反对阿里云存储,后续将逐渐提供更多的云存储平台反对,如果您有喜爱的其它云存储平台,欢送提倡议,或者本人实现它并为 Masa 框架做出奉献

如何制作自定义存储程序?

疾速入门

Masa.BuildingBlocks.Storage.ObjectStorage 是对象存储服务的形象包,你能够在我的项目中应用它来进行编写代码,最初在 Program.cs 中抉择一个存储提供程序应用即可

  • 装置.Net 6.0
  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…

开源地址

MASA.BuildingBlocks:https://github.com/masastack/…

MASA.Contrib:https://github.com/masastack/…

MASA.Utils:https://github.com/masastack/…

MASA.EShop:https://github.com/masalabs/M…

MASA.Blazor:https://github.com/BlazorComp…

如果你对咱们的 MASA Framework 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们

退出移动版