Skip to content

Commit ecd3ba5

Browse files
authored
Merge pull request #84 from cnblogs/76-add-file-provider-abstraction
feat: add file provider abstraction
2 parents 408ad57 + f6afcca commit ecd3ba5

File tree

9 files changed

+338
-4
lines changed

9 files changed

+338
-4
lines changed

Cnblogs.Architecture.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Cq
6060
EndProject
6161
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse", "src\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj", "{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}"
6262
EndProject
63+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss", "src\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss\Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj", "{9C76E136-1D79-408C-A17F-FD63632B00A9}"
64+
EndProject
6365
Global
6466
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6567
Debug|Any CPU = Debug|Any CPU
@@ -91,6 +93,7 @@ Global
9193
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F} = {772497F8-2CB1-4EA6-AEB8-482C3ECD0A9D}
9294
{73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
9395
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
96+
{9C76E136-1D79-408C-A17F-FD63632B00A9} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
9497
EndGlobalSection
9598
GlobalSection(ProjectConfigurationPlatforms) = postSolution
9699
{54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -193,5 +196,9 @@ Global
193196
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
194197
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
195198
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.Build.0 = Release|Any CPU
199+
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
200+
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
201+
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
202+
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.Build.0 = Release|Any CPU
196203
EndGlobalSection
197204
EndGlobal

src/Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection/CqrsInjector.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
22
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
33
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
4-
54
using MediatR;
6-
75
using Microsoft.Extensions.DependencyInjection;
86
using Microsoft.Extensions.DependencyInjection.Extensions;
97

@@ -109,6 +107,27 @@ public CqrsInjector AddRemoteQueryCache<TRemote>(Action<CacheableRequestOptions>
109107
return this;
110108
}
111109

110+
/// <summary>
111+
/// Use default implementation of <see cref="IFileProvider"/> that accesses file system directly.
112+
/// </summary>
113+
/// <returns></returns>
114+
public CqrsInjector UseDefaultFileProvider()
115+
{
116+
return UseFileProvider<DefaultFileProvider>();
117+
}
118+
119+
/// <summary>
120+
/// Use given implementation of <see cref="IFileProvider"/>.
121+
/// </summary>
122+
/// <typeparam name="TProvider">The implementation type.</typeparam>
123+
/// <returns></returns>
124+
public CqrsInjector UseFileProvider<TProvider>()
125+
where TProvider : class, IFileProvider
126+
{
127+
Services.AddScoped<IFileProvider, TProvider>();
128+
return this;
129+
}
130+
112131
/// <summary>
113132
/// 添加自定义随机数提供器。
114133
/// </summary>
@@ -140,4 +159,4 @@ private void AddCacheBehaviorPipeline(Action<CacheableRequestOptions>? configure
140159
});
141160
}
142161
}
143-
}
162+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Stream = System.IO.Stream;
2+
3+
namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
4+
5+
/// <summary>
6+
/// Use default file provider.
7+
/// </summary>
8+
public class DefaultFileProvider : IFileProvider
9+
{
10+
/// <inheritdoc />
11+
public Task<Stream> GetFileStreamAsync(string filename)
12+
{
13+
return Task.FromResult<Stream>(File.OpenRead(filename));
14+
}
15+
16+
/// <inheritdoc />
17+
public async Task<byte[]> GetFileBytesAsync(string filename)
18+
{
19+
var file = await File.ReadAllBytesAsync(filename);
20+
return file;
21+
}
22+
23+
/// <inheritdoc />
24+
public async Task SaveFileAsync(string filename, Stream filestream)
25+
{
26+
var file = File.OpenWrite(filename);
27+
await filestream.CopyToAsync(file);
28+
await file.FlushAsync();
29+
file.Close();
30+
}
31+
32+
/// <inheritdoc />
33+
public async Task SaveFileAsync(string filename, byte[] bytes)
34+
{
35+
await File.WriteAllBytesAsync(filename, bytes);
36+
}
37+
38+
/// <inheritdoc />
39+
public Task<bool> FileExistsAsync(string filename)
40+
{
41+
var file = new FileInfo(filename);
42+
return Task.FromResult(file.Exists);
43+
}
44+
45+
/// <inheritdoc />
46+
public Task DeleteFileAsync(string filename)
47+
{
48+
var file = new FileInfo(filename);
49+
if (file.Exists)
50+
{
51+
file.Delete();
52+
}
53+
54+
return Task.CompletedTask;
55+
}
56+
57+
/// <inheritdoc />
58+
public async Task DeleteFilesAsync(IList<string> filenames)
59+
{
60+
foreach (var filename in filenames)
61+
{
62+
await DeleteFileAsync(filename);
63+
}
64+
}
65+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
2+
3+
/// <summary>
4+
/// Provides abstractions for accessing file system.
5+
/// </summary>
6+
public interface IFileProvider
7+
{
8+
/// <summary>
9+
/// Get file content by filename.
10+
/// </summary>
11+
/// <param name="filename">The filename.</param>
12+
/// <returns>File's content stream.</returns>
13+
/// <exception cref="FileNotFoundException">Throw if file with filename does not exist.</exception>
14+
Task<Stream> GetFileStreamAsync(string filename);
15+
16+
/// <summary>
17+
/// Get file content by filename.
18+
/// </summary>
19+
/// <param name="filename">The filename.</param>
20+
/// <returns>File's content in byte array.</returns>
21+
/// <exception cref="FileNotFoundException">Throw if file with filename does not exist.</exception>
22+
Task<byte[]> GetFileBytesAsync(string filename);
23+
24+
/// <summary>
25+
/// Save file to given filename.
26+
/// </summary>
27+
/// <param name="filename">The path to save file to.</param>
28+
/// <param name="filestream">The file content.</param>
29+
/// <returns></returns>
30+
Task SaveFileAsync(string filename, Stream filestream);
31+
32+
/// <summary>
33+
/// Save file to given filename.
34+
/// </summary>
35+
/// <param name="filename">The path to save file to.</param>
36+
/// <param name="bytes">The file content in byte array.</param>
37+
/// <returns></returns>
38+
Task SaveFileAsync(string filename, byte[] bytes);
39+
40+
/// <summary>
41+
/// Check if file exists.
42+
/// </summary>
43+
/// <param name="filename">The filename to check.</param>
44+
/// <returns>True if file exists.</returns>
45+
Task<bool> FileExistsAsync(string filename);
46+
47+
/// <summary>
48+
/// Delete file with certain filename.
49+
/// </summary>
50+
/// <param name="filename">The filename to delete.</param>
51+
/// <returns></returns>
52+
Task DeleteFileAsync(string filename);
53+
54+
/// <summary>
55+
/// Bulk delete files by filenames.
56+
/// </summary>
57+
/// <param name="filenames">The files to be deleted.</param>
58+
/// <returns></returns>
59+
Task DeleteFilesAsync(IList<string> filenames);
60+
}

src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="ClickHouse.Client" Version="6.5.1" />
16+
<PackageReference Include="ClickHouse.Client" Version="6.5.2" />
1717
</ItemGroup>
1818

1919
</Project>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
2+
using Cuiliang.AliyunOssSdk;
3+
using Cuiliang.AliyunOssSdk.Api;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;
7+
8+
/// <summary>
9+
/// An <see cref="IFileProvider"/> implementation using Aliyun OSS.
10+
/// </summary>
11+
public class AliyunOssFileProvider : IFileProvider
12+
{
13+
private readonly OssClient _ossClient;
14+
private readonly AliyunOssOptions _options;
15+
16+
/// <summary>
17+
/// Create a <see cref="IFileProvider"/> based on Aliyun OSS.
18+
/// </summary>
19+
/// <param name="ossClient">The underlying Aliyun OSS client.</param>
20+
/// <param name="options">The Aliyun OSS options.</param>
21+
public AliyunOssFileProvider(OssClient ossClient, IOptions<AliyunOssOptions> options)
22+
{
23+
_ossClient = ossClient;
24+
_options = options.Value;
25+
}
26+
27+
/// <inheritdoc />
28+
public async Task<Stream> GetFileStreamAsync(string filename)
29+
{
30+
var file = await _ossClient.GetObjectAsync(_options.BucketInfo, filename);
31+
if (file.IsSuccess == false)
32+
{
33+
throw NewFileNotFoundException(filename, file);
34+
}
35+
36+
return await file.SuccessResult.Content.ReadAsStreamAsync();
37+
}
38+
39+
/// <inheritdoc />
40+
public async Task<byte[]> GetFileBytesAsync(string filename)
41+
{
42+
var file = await _ossClient.GetObjectAsync(_options.BucketInfo, filename);
43+
if (file.IsSuccess == false)
44+
{
45+
throw NewFileNotFoundException(filename, file);
46+
}
47+
48+
return await file.SuccessResult.Content.ReadAsByteArrayAsync();
49+
}
50+
51+
/// <inheritdoc />
52+
public async Task SaveFileAsync(string filename, Stream filestream)
53+
{
54+
var result = await _ossClient.PutObjectAsync(_options.BucketInfo, filename, filestream);
55+
if (result.IsSuccess == false)
56+
{
57+
throw new InvalidOperationException(result.ErrorMessage, result.InnerException);
58+
}
59+
}
60+
61+
/// <inheritdoc />
62+
public async Task SaveFileAsync(string filename, byte[] bytes)
63+
{
64+
var stream = new MemoryStream(bytes);
65+
await SaveFileAsync(filename, stream);
66+
}
67+
68+
/// <inheritdoc />
69+
public async Task<bool> FileExistsAsync(string filename)
70+
{
71+
var result = await _ossClient.GetObjectMetaAsync(_options.BucketInfo, filename);
72+
return result.IsSuccess;
73+
}
74+
75+
/// <inheritdoc />
76+
public async Task DeleteFilesAsync(IList<string> filenames)
77+
{
78+
await _ossClient.DeleteMultipleObjectsAsync(_options.BucketInfo, filenames, true);
79+
}
80+
81+
/// <inheritdoc />
82+
public async Task DeleteFileAsync(string filename)
83+
{
84+
await _ossClient.DeleteObjectAsync(_options.BucketInfo, filename);
85+
}
86+
87+
private static FileNotFoundException NewFileNotFoundException<T>(string path, OssResult<T> result)
88+
{
89+
return new FileNotFoundException(result.ErrorMessage, path, result.InnerException);
90+
}
91+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Cuiliang.AliyunOssSdk.Entites;
2+
3+
namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;
4+
5+
/// <summary>
6+
/// The aliyun oss options.
7+
/// </summary>
8+
public class AliyunOssOptions
9+
{
10+
private BucketInfo? _bucketInfo;
11+
12+
/// <summary>
13+
/// OSS access key id.
14+
/// </summary>
15+
public string AccessKeyId { get; set; } = string.Empty;
16+
17+
/// <summary>
18+
/// OSS access key secret.
19+
/// </summary>
20+
public string AccessKeySecret { get; set; } = string.Empty;
21+
22+
/// <summary>
23+
/// OSS security token.
24+
/// </summary>
25+
public string SecurityToken { get; set; } = string.Empty;
26+
27+
/// <summary>
28+
/// The bucket name.
29+
/// </summary>
30+
public string BucketName { get; set; } = string.Empty;
31+
32+
/// <summary>
33+
/// The region that bucket belongs to.
34+
/// </summary>
35+
public string Region { get; set; } = OssRegions.HangZhou;
36+
37+
/// <summary>
38+
/// True if HTTPS is enabled.
39+
/// </summary>
40+
public bool UseHttps { get; set; }
41+
42+
/// <summary>
43+
/// True if OSS is used by internal resources.
44+
/// </summary>
45+
public bool UseInternal { get; set; }
46+
47+
/// <summary>
48+
/// The bucket info of OSS.
49+
/// </summary>
50+
public BucketInfo BucketInfo
51+
=> _bucketInfo ??= BucketInfo.CreateByRegion(Region, BucketName, UseHttps, UseInternal);
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<ItemGroup>
4+
<PackageReference Include="aliyun.sdk.oss" Version="0.3.7" />
5+
</ItemGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection\Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection.csproj" />
9+
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection;
2+
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;
7+
8+
/// <summary>
9+
/// Extension methods to inject Aliyun OSS provider to CQRS injector.
10+
/// </summary>
11+
public static class CqrsInjectorExtensions
12+
{
13+
/// <summary>
14+
/// Use aliyun oss as default implementation of <see cref="IFileProvider"/>.
15+
/// </summary>
16+
/// <param name="injector"></param>
17+
/// <param name="configuration"></param>
18+
/// <param name="configurationSectionName"></param>
19+
/// <returns></returns>
20+
public static CqrsInjector UseAliyunOssFileProvider(
21+
this CqrsInjector injector,
22+
IConfiguration configuration,
23+
string configurationSectionName = "ossClient")
24+
{
25+
injector.Services.AddOssClient(configuration, configurationSectionName);
26+
return injector.UseFileProvider<AliyunOssFileProvider>();
27+
}
28+
}

0 commit comments

Comments
 (0)