写点什么

6. 对象存储

  • 2022 年 7 月 12 日
  • 本文字数:4250 字

    阅读完需:约 14 分钟

什么是对象存储

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


  1. 修改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
复制代码


  1. 修改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时需要指定         }       }     }   }
复制代码


  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());   });
复制代码

进阶

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,类型为文件上传


  1. 删除对象


   public class DeleteRequest   {       public string Key { get; set; }   }      app.MapDelete("/delete", async (IClient client, [FromBody] DeleteRequest request) =>   {       await client.DeleteObjectAsync("storage1-test", request.Key);   });
复制代码


  1. 判断对象是否存在


   app.MapGet("/exist", async (IClient client, string key) =>   {       await client.ObjectExistsAsync("storage1-test", key);   });
复制代码


  1. 返回对象数据的流


   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();       });   });
复制代码


  1. 获取临时凭证(STS)


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


阿里云腾讯云存储等平台使用 STS 来获取临时凭证


  1. 获取临时凭证(字符串类型的临时凭证)


   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,欢迎联系我们



发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2021.10.26 加入

还未添加个人简介

评论

发布
暂无评论
6. 对象存储_C#_MASA技术团队_InfoQ写作社区