diff --git a/Cnblogs.Architecture.sln b/Cnblogs.Architecture.sln index bff5342..2826a7b 100644 --- a/Cnblogs.Architecture.sln +++ b/Cnblogs.Architecture.sln @@ -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 @@ -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 @@ -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 diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection/CqrsInjector.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection/CqrsInjector.cs index 0339bd5..5b0272a 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection/CqrsInjector.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.DependencyInjection/CqrsInjector.cs @@ -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; @@ -109,6 +107,27 @@ public CqrsInjector AddRemoteQueryCache(Action return this; } + /// + /// Use default implementation of that accesses file system directly. + /// + /// + public CqrsInjector UseDefaultFileProvider() + { + return UseFileProvider(); + } + + /// + /// Use given implementation of . + /// + /// The implementation type. + /// + public CqrsInjector UseFileProvider() + where TProvider : class, IFileProvider + { + Services.AddScoped(); + return this; + } + /// /// 添加自定义随机数提供器。 /// @@ -140,4 +159,4 @@ private void AddCacheBehaviorPipeline(Action? configure }); } } -} \ No newline at end of file +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/DefaultFileProvider.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/DefaultFileProvider.cs new file mode 100644 index 0000000..e66fcbc --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/DefaultFileProvider.cs @@ -0,0 +1,65 @@ +using Stream = System.IO.Stream; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions; + +/// +/// Use default file provider. +/// +public class DefaultFileProvider : IFileProvider +{ + /// + public Task GetFileStreamAsync(string filename) + { + return Task.FromResult(File.OpenRead(filename)); + } + + /// + public async Task GetFileBytesAsync(string filename) + { + var file = await File.ReadAllBytesAsync(filename); + return file; + } + + /// + public async Task SaveFileAsync(string filename, Stream filestream) + { + var file = File.OpenWrite(filename); + await filestream.CopyToAsync(file); + await file.FlushAsync(); + file.Close(); + } + + /// + public async Task SaveFileAsync(string filename, byte[] bytes) + { + await File.WriteAllBytesAsync(filename, bytes); + } + + /// + public Task FileExistsAsync(string filename) + { + var file = new FileInfo(filename); + return Task.FromResult(file.Exists); + } + + /// + public Task DeleteFileAsync(string filename) + { + var file = new FileInfo(filename); + if (file.Exists) + { + file.Delete(); + } + + return Task.CompletedTask; + } + + /// + public async Task DeleteFilesAsync(IList filenames) + { + foreach (var filename in filenames) + { + await DeleteFileAsync(filename); + } + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/IFileProvider.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/IFileProvider.cs new file mode 100644 index 0000000..fde8db5 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Abstractions/IFileProvider.cs @@ -0,0 +1,60 @@ +namespace Cnblogs.Architecture.Ddd.Infrastructure.Abstractions; + +/// +/// Provides abstractions for accessing file system. +/// +public interface IFileProvider +{ + /// + /// Get file content by filename. + /// + /// The filename. + /// File's content stream. + /// Throw if file with filename does not exist. + Task GetFileStreamAsync(string filename); + + /// + /// Get file content by filename. + /// + /// The filename. + /// File's content in byte array. + /// Throw if file with filename does not exist. + Task GetFileBytesAsync(string filename); + + /// + /// Save file to given filename. + /// + /// The path to save file to. + /// The file content. + /// + Task SaveFileAsync(string filename, Stream filestream); + + /// + /// Save file to given filename. + /// + /// The path to save file to. + /// The file content in byte array. + /// + Task SaveFileAsync(string filename, byte[] bytes); + + /// + /// Check if file exists. + /// + /// The filename to check. + /// True if file exists. + Task FileExistsAsync(string filename); + + /// + /// Delete file with certain filename. + /// + /// The filename to delete. + /// + Task DeleteFileAsync(string filename); + + /// + /// Bulk delete files by filenames. + /// + /// The files to be deleted. + /// + Task DeleteFilesAsync(IList filenames); +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj index b7fc25a..d98192f 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssFileProvider.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssFileProvider.cs new file mode 100644 index 0000000..ce37985 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssFileProvider.cs @@ -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; + +/// +/// An implementation using Aliyun OSS. +/// +public class AliyunOssFileProvider : IFileProvider +{ + private readonly OssClient _ossClient; + private readonly AliyunOssOptions _options; + + /// + /// Create a based on Aliyun OSS. + /// + /// The underlying Aliyun OSS client. + /// The Aliyun OSS options. + public AliyunOssFileProvider(OssClient ossClient, IOptions options) + { + _ossClient = ossClient; + _options = options.Value; + } + + /// + public async Task 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(); + } + + /// + public async Task 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(); + } + + /// + 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); + } + } + + /// + public async Task SaveFileAsync(string filename, byte[] bytes) + { + var stream = new MemoryStream(bytes); + await SaveFileAsync(filename, stream); + } + + /// + public async Task FileExistsAsync(string filename) + { + var result = await _ossClient.GetObjectMetaAsync(_options.BucketInfo, filename); + return result.IsSuccess; + } + + /// + public async Task DeleteFilesAsync(IList filenames) + { + await _ossClient.DeleteMultipleObjectsAsync(_options.BucketInfo, filenames, true); + } + + /// + public async Task DeleteFileAsync(string filename) + { + await _ossClient.DeleteObjectAsync(_options.BucketInfo, filename); + } + + private static FileNotFoundException NewFileNotFoundException(string path, OssResult result) + { + return new FileNotFoundException(result.ErrorMessage, path, result.InnerException); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssOptions.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssOptions.cs new file mode 100644 index 0000000..4c5ad7f --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/AliyunOssOptions.cs @@ -0,0 +1,52 @@ +using Cuiliang.AliyunOssSdk.Entites; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss; + +/// +/// The aliyun oss options. +/// +public class AliyunOssOptions +{ + private BucketInfo? _bucketInfo; + + /// + /// OSS access key id. + /// + public string AccessKeyId { get; set; } = string.Empty; + + /// + /// OSS access key secret. + /// + public string AccessKeySecret { get; set; } = string.Empty; + + /// + /// OSS security token. + /// + public string SecurityToken { get; set; } = string.Empty; + + /// + /// The bucket name. + /// + public string BucketName { get; set; } = string.Empty; + + /// + /// The region that bucket belongs to. + /// + public string Region { get; set; } = OssRegions.HangZhou; + + /// + /// True if HTTPS is enabled. + /// + public bool UseHttps { get; set; } + + /// + /// True if OSS is used by internal resources. + /// + public bool UseInternal { get; set; } + + /// + /// The bucket info of OSS. + /// + public BucketInfo BucketInfo + => _bucketInfo ??= BucketInfo.CreateByRegion(Region, BucketName, UseHttps, UseInternal); +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj new file mode 100644 index 0000000..93f0ce2 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/CqrsInjectorExtensions.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/CqrsInjectorExtensions.cs new file mode 100644 index 0000000..593a29a --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.FileProviders.AliyunOss/CqrsInjectorExtensions.cs @@ -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; + +/// +/// Extension methods to inject Aliyun OSS provider to CQRS injector. +/// +public static class CqrsInjectorExtensions +{ + /// + /// Use aliyun oss as default implementation of . + /// + /// + /// + /// + /// + public static CqrsInjector UseAliyunOssFileProvider( + this CqrsInjector injector, + IConfiguration configuration, + string configurationSectionName = "ossClient") + { + injector.Services.AddOssClient(configuration, configurationSectionName); + return injector.UseFileProvider(); + } +}