6. 对象存储
什么是对象存储
在工作中,我们经常需要将文件内容(文件或二进制流)存储在应用程序中,例如你可能要保存商品的封面图片。Masa框架为此提供了对象存储的功能,并对功能抽象,抽象给我们带来的好处:
- 存储的无关性(不关心存储平台时阿里云OSS还是腾讯云的COS)
- 更换存储平台成本更低(仅需要更改下存储的提供者,业务侵染低)
- 支持自定义存储提供者(仅需要自行实现
IClient
)
对象存储提供程序
- 阿里云: 在阿里云OSS存储服务上存储
目前仅支持阿里云存储,后续将逐步提供更多的云存储平台支持,如果您有喜欢的其它云存储平台,欢迎提建议,或者自己实现它并为Masa框架做出贡献
快速入门
Masa.BuildingBlocks.Storage.ObjectStorage是对象存储服务的抽象包,你可以在项目中使用它来进行编写代码,最后在Program.cs
中选择一个存储提供程序使用即可
- 安装.Net 6.0
新建ASP.NET Core 空项目
Assignment.OSS
,并安装Masa.Contrib.Storage.ObjectStorage.Aliyun
1
2
3dotnet new web -o Assignment.OSS
cd Assignment.OSS
dotnet add package Masa.Contrib.Storage.ObjectStorage.Aliyun --version 0.5.0-preview.2修改
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18builder.Services.AddAliyunStorage();
// 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");修改
appsettings.json
,增加阿里云配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21{
"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
2
3
4
5
6
7
8
9app.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
2
3
4
5
6
7
8
9app.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
2
3
4
5
6
7
8
9public class DeleteRequest
{
public string Key { get; set; }
}
app.MapDelete("/delete", async (IClient client, [FromBody] DeleteRequest request) =>
{
await client.DeleteObjectAsync("storage1-test", request.Key);
});判断对象是否存在
1
2
3
4app.MapGet("/exist", async (IClient client, string key) =>
{
await client.ObjectExistsAsync("storage1-test", key);
});返回对象数据的流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16app.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();
});
});获取临时凭证(STS)
1
2
3
4app.MapGet("/GetSts", (IClient client) =>
{
client.GetSecurityToken();
});获取临时凭证(字符串类型的临时凭证)
1
2
3
4app.MapGet("/GetToken", (IClient client) =>
{
client.GetToken();
});七牛云等存储平台使用较多
IBucketNameProvider
IBucketNameProvider
是用来获取BucketName的接口,通过IBucketNameProvider
可以获取指定存储空间的BucketName,为IClientContainer
提供BucketName能力,在业务项目中不会使用到
IClientContainer
IClientContainer
对象存储容器,用来存储和读取对象的主要接口,一个应用程序下可能会存在管理多个BucketName,通过使用IClientContainer
,像管理DbContext
一样管理不同Bucket
的对象,不需要在项目中频繁指定BucketName
,在同一个应用程序中,有且只有一个默认ClientContainer,可以通过DI获取IClientContainer
来使用,例如:
上传对象(上传到默认
Bucket
)1
2
3
4
5
6
7
8
9app.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24[ ]
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
创建对象存储提供程序
以适配腾讯云存储为例:
新建类库
Masa.Contrib.Storage.ObjectStorage.Tencent
选中
Masa.Contrib.Storage.ObjectStorage.Tencent
并新建类DefaultStorageClient
,并实现IClient
由于腾讯云存储提供Sts临时凭证,所以仅需要实现
GetSecurityToken
方法即可,GetToken
方法可抛出不支持的异常,并在文档说明即可新建类
ServiceCollectionExtensions
,并提供对IServiceCollection
的扩展方法AddTencentStorage
,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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,欢迎联系我们