Skip to content

feat: add file provider abstraction #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cnblogs.Architecture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Cq
EndProject
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}"
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -91,6 +93,7 @@ Global
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F} = {772497F8-2CB1-4EA6-AEB8-482C3ECD0A9D}
{73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
{9C76E136-1D79-408C-A17F-FD63632B00A9} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -193,5 +196,9 @@ Global
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.Build.0 = Release|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C76E136-1D79-408C-A17F-FD63632B00A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;

using MediatR;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

Expand Down Expand Up @@ -109,6 +107,27 @@ public CqrsInjector AddRemoteQueryCache<TRemote>(Action<CacheableRequestOptions>
return this;
}

/// <summary>
/// Use default implementation of <see cref="IFileProvider"/> that accesses file system directly.
/// </summary>
/// <returns></returns>
public CqrsInjector UseDefaultFileProvider()
{
return UseFileProvider<DefaultFileProvider>();
}

/// <summary>
/// Use given implementation of <see cref="IFileProvider"/>.
/// </summary>
/// <typeparam name="TProvider">The implementation type.</typeparam>
/// <returns></returns>
public CqrsInjector UseFileProvider<TProvider>()
where TProvider : class, IFileProvider
{
Services.AddScoped<IFileProvider, TProvider>();
return this;
}

/// <summary>
/// 添加自定义随机数提供器。
/// </summary>
Expand Down Expand Up @@ -140,4 +159,4 @@ private void AddCacheBehaviorPipeline(Action<CacheableRequestOptions>? configure
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Stream = System.IO.Stream;

namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;

/// <summary>
/// Use default file provider.
/// </summary>
public class DefaultFileProvider : IFileProvider
{
/// <inheritdoc />
public Task<Stream> GetFileStreamAsync(string filename)
{
return Task.FromResult<Stream>(File.OpenRead(filename));
}

/// <inheritdoc />
public async Task<byte[]> GetFileBytesAsync(string filename)
{
var file = await File.ReadAllBytesAsync(filename);
return file;
}

/// <inheritdoc />
public async Task SaveFileAsync(string filename, Stream filestream)
{
var file = File.OpenWrite(filename);
await filestream.CopyToAsync(file);
await file.FlushAsync();
file.Close();
}

/// <inheritdoc />
public async Task SaveFileAsync(string filename, byte[] bytes)
{
await File.WriteAllBytesAsync(filename, bytes);
}

/// <inheritdoc />
public Task<bool> FileExistsAsync(string filename)
{
var file = new FileInfo(filename);
return Task.FromResult(file.Exists);
}

/// <inheritdoc />
public Task DeleteFileAsync(string filename)
{
var file = new FileInfo(filename);
if (file.Exists)
{
file.Delete();
}

return Task.CompletedTask;
}

/// <inheritdoc />
public async Task DeleteFilesAsync(IList<string> filenames)
{
foreach (var filename in filenames)
{
await DeleteFileAsync(filename);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;

/// <summary>
/// Provides abstractions for accessing file system.
/// </summary>
public interface IFileProvider
{
/// <summary>
/// Get file content by filename.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>File's content stream.</returns>
/// <exception cref="FileNotFoundException">Throw if file with filename does not exist.</exception>
Task<Stream> GetFileStreamAsync(string filename);

/// <summary>
/// Get file content by filename.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>File's content in byte array.</returns>
/// <exception cref="FileNotFoundException">Throw if file with filename does not exist.</exception>
Task<byte[]> GetFileBytesAsync(string filename);

/// <summary>
/// Save file to given filename.
/// </summary>
/// <param name="filename">The path to save file to.</param>
/// <param name="filestream">The file content.</param>
/// <returns></returns>
Task SaveFileAsync(string filename, Stream filestream);

/// <summary>
/// Save file to given filename.
/// </summary>
/// <param name="filename">The path to save file to.</param>
/// <param name="bytes">The file content in byte array.</param>
/// <returns></returns>
Task SaveFileAsync(string filename, byte[] bytes);

/// <summary>
/// Check if file exists.
/// </summary>
/// <param name="filename">The filename to check.</param>
/// <returns>True if file exists.</returns>
Task<bool> FileExistsAsync(string filename);

/// <summary>
/// Delete file with certain filename.
/// </summary>
/// <param name="filename">The filename to delete.</param>
/// <returns></returns>
Task DeleteFileAsync(string filename);

/// <summary>
/// Bulk delete files by filenames.
/// </summary>
/// <param name="filenames">The files to be deleted.</param>
/// <returns></returns>
Task DeleteFilesAsync(IList<string> filenames);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="ClickHouse.Client" Version="6.5.1" />
<PackageReference Include="ClickHouse.Client" Version="6.5.2" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
using Cuiliang.AliyunOssSdk;
using Cuiliang.AliyunOssSdk.Api;
using Microsoft.Extensions.Options;

namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;

/// <summary>
/// An <see cref="IFileProvider"/> implementation using Aliyun OSS.
/// </summary>
public class AliyunOssFileProvider : IFileProvider
{
private readonly OssClient _ossClient;
private readonly AliyunOssOptions _options;

/// <summary>
/// Create a <see cref="IFileProvider"/> based on Aliyun OSS.
/// </summary>
/// <param name="ossClient">The underlying Aliyun OSS client.</param>
/// <param name="options">The Aliyun OSS options.</param>
public AliyunOssFileProvider(OssClient ossClient, IOptions<AliyunOssOptions> options)
{
_ossClient = ossClient;
_options = options.Value;
}

/// <inheritdoc />
public async Task<Stream> GetFileStreamAsync(string filename)
{
var file = await _ossClient.GetObjectAsync(_options.BucketInfo, filename);
if (file.IsSuccess == false)
{
throw NewFileNotFoundException(filename, file);
}

return await file.SuccessResult.Content.ReadAsStreamAsync();
}

/// <inheritdoc />
public async Task<byte[]> GetFileBytesAsync(string filename)
{
var file = await _ossClient.GetObjectAsync(_options.BucketInfo, filename);
if (file.IsSuccess == false)
{
throw NewFileNotFoundException(filename, file);
}

return await file.SuccessResult.Content.ReadAsByteArrayAsync();
}

/// <inheritdoc />
public async Task SaveFileAsync(string filename, Stream filestream)
{
var result = await _ossClient.PutObjectAsync(_options.BucketInfo, filename, filestream);
if (result.IsSuccess == false)
{
throw new InvalidOperationException(result.ErrorMessage, result.InnerException);
}
}

/// <inheritdoc />
public async Task SaveFileAsync(string filename, byte[] bytes)
{
var stream = new MemoryStream(bytes);
await SaveFileAsync(filename, stream);
}

/// <inheritdoc />
public async Task<bool> FileExistsAsync(string filename)
{
var result = await _ossClient.GetObjectMetaAsync(_options.BucketInfo, filename);
return result.IsSuccess;
}

/// <inheritdoc />
public async Task DeleteFilesAsync(IList<string> filenames)
{
await _ossClient.DeleteMultipleObjectsAsync(_options.BucketInfo, filenames, true);
}

/// <inheritdoc />
public async Task DeleteFileAsync(string filename)
{
await _ossClient.DeleteObjectAsync(_options.BucketInfo, filename);
}

private static FileNotFoundException NewFileNotFoundException<T>(string path, OssResult<T> result)
{
return new FileNotFoundException(result.ErrorMessage, path, result.InnerException);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Cuiliang.AliyunOssSdk.Entites;

namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;

/// <summary>
/// The aliyun oss options.
/// </summary>
public class AliyunOssOptions
{
private BucketInfo? _bucketInfo;

/// <summary>
/// OSS access key id.
/// </summary>
public string AccessKeyId { get; set; } = string.Empty;

/// <summary>
/// OSS access key secret.
/// </summary>
public string AccessKeySecret { get; set; } = string.Empty;

/// <summary>
/// OSS security token.
/// </summary>
public string SecurityToken { get; set; } = string.Empty;

/// <summary>
/// The bucket name.
/// </summary>
public string BucketName { get; set; } = string.Empty;

/// <summary>
/// The region that bucket belongs to.
/// </summary>
public string Region { get; set; } = OssRegions.HangZhou;

/// <summary>
/// True if HTTPS is enabled.
/// </summary>
public bool UseHttps { get; set; }

/// <summary>
/// True if OSS is used by internal resources.
/// </summary>
public bool UseInternal { get; set; }

/// <summary>
/// The bucket info of OSS.
/// </summary>
public BucketInfo BucketInfo
=> _bucketInfo ??= BucketInfo.CreateByRegion(Region, BucketName, UseHttps, UseInternal);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<PackageReference Include="aliyun.sdk.oss" Version="0.3.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection\Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection.csproj" />
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions\Cnblogs.Architecture.Ddd.Infrastructure.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection;
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss;

/// <summary>
/// Extension methods to inject Aliyun OSS provider to CQRS injector.
/// </summary>
public static class CqrsInjectorExtensions
{
/// <summary>
/// Use aliyun oss as default implementation of <see cref="IFileProvider"/>.
/// </summary>
/// <param name="injector"></param>
/// <param name="configuration"></param>
/// <param name="configurationSectionName"></param>
/// <returns></returns>
public static CqrsInjector UseAliyunOssFileProvider(
this CqrsInjector injector,
IConfiguration configuration,
string configurationSectionName = "ossClient")
{
injector.Services.AddOssClient(configuration, configurationSectionName);
return injector.UseFileProvider<AliyunOssFileProvider>();
}
}