什么是对象存储

在工作中,咱们常常须要将文件内容(文件或二进制流)存储在应用程序中,例如你可能要保留商品的封面图片。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.OSScd Assignment.OSSdotnet 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,欢送分割咱们