• 对象存储


    什么是对象存储

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

  • 相关阅读:
    springboot+ssm基于vue.js的客户关系Crm管理系统
    M编程备忘录之C++——多态
    Springboot通过谷歌Kaptcha 组件,生成图形验证码
    Spring framework Day22:Aware接口
    【开题报告】如何借助chatgpt完成毕业论文开题报告
    Robot Framework 自动化测试详解
    Hadoop3教程(三十):(生产调优篇)纠删码
    QTN25-80、QTN25-90、QTN25-100双轴气缸
    分布式ID生成服务的技术原理和项目实战
    四、【基础】组件实例三大核心属性之一 state
  • 原文地址:https://www.cnblogs.com/zhenlei520/p/16470089.html