From 0b6482d75772f8f2aa4cd7b0e6c130dd974c4276 Mon Sep 17 00:00:00 2001 From: root <8907060@qq.com> Date: Wed, 10 Jan 2024 17:50:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E6=9C=8D=E5=8A=A1=E7=94=9F=E6=88=90=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E5=AE=9E=E7=8E=B0=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/version.props | 2 +- .../GenerateTempDownloadUrlArgs.cs | 23 +++++++++++ .../IFileStore.cs | 20 +++++++-- .../AliyunFileStore.cs | 41 ++++++++++++++++--- src/Util.FileStorage.Minio/MinioFileStore.cs | 31 +++++++++++--- .../FileNameProcessorFactory.cs | 16 ++------ src/Util.FileStorage/Local/LocalFileStore.cs | 18 ++++++++ .../UserTimeFileNameProcessor.cs | 34 ++++----------- src/Util.FileStorage/Usings.cs | 1 + .../Tests/AliyunFileStoreTest.cs | 13 +++--- .../Tests/MinioFileStoreTest.cs | 10 +---- .../Tests/UserTimeFileNameProcessorTest.cs | 8 ++-- 12 files changed, 143 insertions(+), 74 deletions(-) create mode 100644 src/Util.FileStorage.Abstractions/GenerateTempDownloadUrlArgs.cs diff --git a/build/version.props b/build/version.props index 931dbe196..66ebed593 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 7 1 - 126 + 127 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/src/Util.FileStorage.Abstractions/GenerateTempDownloadUrlArgs.cs b/src/Util.FileStorage.Abstractions/GenerateTempDownloadUrlArgs.cs new file mode 100644 index 000000000..af8772113 --- /dev/null +++ b/src/Util.FileStorage.Abstractions/GenerateTempDownloadUrlArgs.cs @@ -0,0 +1,23 @@ +namespace Util.FileStorage; + +/// +/// 生成临时下载Url方法参数 +/// +public class GenerateTempDownloadUrlArgs : FileStorageArgs { + /// + /// 初始化生成临时下载Url方法参数 + /// + /// 文件名 + public GenerateTempDownloadUrlArgs( string fileName ) : base( fileName ) { + } + + /// + /// 下载地址过期时间,单位:秒 + /// + public int? Expiration { get; set; } + + /// + /// 响应内容类型 + /// + public string ResponseContentType { get; set; } +} \ No newline at end of file diff --git a/src/Util.FileStorage.Abstractions/IFileStore.cs b/src/Util.FileStorage.Abstractions/IFileStore.cs index a13f3a95d..0e4c17ec5 100644 --- a/src/Util.FileStorage.Abstractions/IFileStore.cs +++ b/src/Util.FileStorage.Abstractions/IFileStore.cs @@ -75,26 +75,38 @@ public interface IFileStore : ILocalFileStore { /// 取消令牌 Task DeleteFileAsync( DeleteFileArgs args, CancellationToken cancellationToken = default ); /// - /// 生成客户端直接下载Url + /// 生成客户端下载Url /// /// 文件名 /// 取消令牌 Task GenerateDownloadUrlAsync( string fileName, CancellationToken cancellationToken = default ); /// - /// 生成客户端直接下载Url + /// 生成客户端下载Url /// /// 参数 /// 取消令牌 Task GenerateDownloadUrlAsync( GenerateDownloadUrlArgs args, CancellationToken cancellationToken = default ); /// - /// 生成客户端直接上传Url + /// 生成客户端临时下载Url + /// + /// 文件名 + /// 取消令牌 + Task GenerateTempDownloadUrlAsync( string fileName, CancellationToken cancellationToken = default ); + /// + /// 生成客户端临时下载Url + /// + /// 参数 + /// 取消令牌 + Task GenerateTempDownloadUrlAsync( GenerateTempDownloadUrlArgs args, CancellationToken cancellationToken = default ); + /// + /// 生成客户端直传Url /// /// 文件名 /// 文件名处理策略 /// 取消令牌 Task GenerateUploadUrlAsync( string fileName, string policy = null, CancellationToken cancellationToken = default ); /// - /// 生成客户端直接上传Url + /// 生成客户端直传Url /// /// 参数 /// 取消令牌 diff --git a/src/Util.FileStorage.Aliyun/AliyunFileStore.cs b/src/Util.FileStorage.Aliyun/AliyunFileStore.cs index d274abafe..ac48e1d83 100644 --- a/src/Util.FileStorage.Aliyun/AliyunFileStore.cs +++ b/src/Util.FileStorage.Aliyun/AliyunFileStore.cs @@ -72,7 +72,7 @@ protected virtual async Task InitConfig() { /// /// 获取阿里云对象存储客户端 /// - protected virtual async Task GetClient() { + public virtual async Task GetClient() { if( _client != null ) return _client; await InitConfig(); @@ -408,20 +408,49 @@ protected async Task ProcessBucketName( FileStorageArgs args ) { } /// - public async Task GenerateDownloadUrlAsync( GenerateDownloadUrlArgs args, CancellationToken cancellationToken = default ) { + public virtual async Task GenerateDownloadUrlAsync( GenerateDownloadUrlArgs args, CancellationToken cancellationToken = default ) { args.CheckNull( nameof( args ) ); var processedFileName = ProcessFileName( args ); var processedBucketName = await ProcessBucketName( args ); - return await GenerateDownloadUrlAsync( processedFileName, processedBucketName, args.ResponseContentType, cancellationToken ); + var endpoint = await GetEndPoint( processedBucketName.Name ); + return Util.Helpers.Common.JoinPath( endpoint, processedFileName.Name ); } /// - /// 生成下载Url + /// 获取端点 /// - protected async Task GenerateDownloadUrlAsync( ProcessedName fileName, ProcessedName bucketName, string responseContentType, CancellationToken cancellationToken = default ) { + protected virtual async Task GetEndPoint(string bucketName ) { + await InitConfig(); + var endpoint = _config.Endpoint.RemoveStart( "http://" ).RemoveStart( "https://" ); + return $"https://{bucketName}.{endpoint}"; + } + + #endregion + + #region GenerateTempDownloadUrlAsync + + /// + public async Task GenerateTempDownloadUrlAsync( string fileName, CancellationToken cancellationToken = default ) { + var args = new GenerateTempDownloadUrlArgs( fileName ); + return await GenerateTempDownloadUrlAsync( args, cancellationToken ); + } + + /// + public virtual async Task GenerateTempDownloadUrlAsync( GenerateTempDownloadUrlArgs args, CancellationToken cancellationToken = default ) { + args.CheckNull( nameof( args ) ); + var processedFileName = ProcessFileName( args ); + var processedBucketName = await ProcessBucketName( args ); + var expiration = args.Expiration ?? _config.DownloadUrlExpiration; + return await GenerateTempDownloadUrlAsync( processedFileName, processedBucketName, expiration, cancellationToken ); + } + + /// + /// 生成临时下载Url + /// + protected virtual async Task GenerateTempDownloadUrlAsync( ProcessedName fileName, ProcessedName bucketName,int expiration, CancellationToken cancellationToken = default ) { var client = await GetClient(); var request = new GeneratePresignedUriRequest( bucketName.Name, fileName.Name, SignHttpMethod.Get ) { - Expiration = DateTime.Now.AddSeconds( _config.UploadUrlExpiration ) + Expiration = DateTime.Now.AddSeconds( expiration ) }; var result = client.GeneratePresignedUri( request ); return result.AbsoluteUri; diff --git a/src/Util.FileStorage.Minio/MinioFileStore.cs b/src/Util.FileStorage.Minio/MinioFileStore.cs index 7c6760d86..ce4299ef0 100644 --- a/src/Util.FileStorage.Minio/MinioFileStore.cs +++ b/src/Util.FileStorage.Minio/MinioFileStore.cs @@ -463,24 +463,45 @@ private async Task CreateCopySourceObjectArgs( FileStorage } /// - public async Task GenerateDownloadUrlAsync( GenerateDownloadUrlArgs args, CancellationToken cancellationToken = default ) { + public virtual async Task GenerateDownloadUrlAsync( GenerateDownloadUrlArgs args, CancellationToken cancellationToken = default ) { args.CheckNull( nameof( args ) ); var processedFileName = ProcessFileName( args ); var processedBucketName = await ProcessBucketName( args ); - return await GenerateDownloadUrlAsync( processedFileName, processedBucketName, args.ResponseContentType,cancellationToken ); + await InitConfig(); + var schema = _config.UseSSL ? "https" : "http"; + return Util.Helpers.Common.JoinPath( $"{schema}://{GetEndpoint()}", processedBucketName.Name, processedFileName.Name ); + } + + #endregion + + #region GenerateTempDownloadUrlAsync + + /// + public async Task GenerateTempDownloadUrlAsync( string fileName, CancellationToken cancellationToken = default ) { + var args = new GenerateTempDownloadUrlArgs( fileName ); + return await GenerateTempDownloadUrlAsync( args, cancellationToken ); + } + + /// + public async Task GenerateTempDownloadUrlAsync( GenerateTempDownloadUrlArgs args, CancellationToken cancellationToken = default ) { + args.CheckNull( nameof( args ) ); + var processedFileName = ProcessFileName( args ); + var processedBucketName = await ProcessBucketName( args ); + var expiration = args.Expiration ?? _config.DownloadUrlExpiration; + return await GenerateTempDownloadUrlAsync( processedFileName, processedBucketName, args.ResponseContentType, expiration ); } /// - /// 生成下载Url + /// 生成临时下载Url /// - protected async Task GenerateDownloadUrlAsync( ProcessedName fileName, ProcessedName bucketName, string responseContentType, CancellationToken cancellationToken = default ) { + protected async Task GenerateTempDownloadUrlAsync( ProcessedName fileName, ProcessedName bucketName, string responseContentType, int expiration ) { var client = await GetClient(); responseContentType ??= "application/octet-stream"; var headers = new Dictionary { { "response-content-type", responseContentType } }; var args = new PresignedGetObjectArgs() .WithBucket( bucketName.Name ) .WithObject( fileName.Name ) - .WithExpiry( _config.DownloadUrlExpiration ) + .WithExpiry( expiration ) .WithHeaders( headers ); return await client.PresignedGetObjectAsync( args ); } diff --git a/src/Util.FileStorage/FileNameProcessorFactory.cs b/src/Util.FileStorage/FileNameProcessorFactory.cs index cd9f1816d..c74fdc734 100644 --- a/src/Util.FileStorage/FileNameProcessorFactory.cs +++ b/src/Util.FileStorage/FileNameProcessorFactory.cs @@ -1,6 +1,4 @@ -using Util.Sessions; - -namespace Util.FileStorage; +namespace Util.FileStorage; /// /// 文件名处理器工厂 @@ -10,27 +8,21 @@ public class FileNameProcessorFactory : IFileNameProcessorFactory { /// 用户会话 /// private readonly ISession _session; - /// - /// 文件名过滤器 - /// - private readonly IFileNameFilter _filter; /// /// 初始化文件名处理器工厂 /// /// 用户会话 - /// 文件名过滤器 - public FileNameProcessorFactory( ISession session, IFileNameFilter filter ) { + public FileNameProcessorFactory( ISession session ) { _session = session ?? NullSession.Instance; - _filter = filter; } /// public IFileNameProcessor CreateProcessor( string policy ) { if ( policy.IsEmpty() ) return new FileNameProcessor(); - if ( policy.ToLowerInvariant() == UserTimeFileNameProcessor.Policy ) - return new UserTimeFileNameProcessor( _session, _filter ); + if ( policy.ToUpperInvariant() == UserTimeFileNameProcessor.Policy ) + return new UserTimeFileNameProcessor( _session ); throw new NotImplementedException( $"文件名处理策略 {policy} 未实现." ); } } \ No newline at end of file diff --git a/src/Util.FileStorage/Local/LocalFileStore.cs b/src/Util.FileStorage/Local/LocalFileStore.cs index 894d099ef..26e85f04b 100644 --- a/src/Util.FileStorage/Local/LocalFileStore.cs +++ b/src/Util.FileStorage/Local/LocalFileStore.cs @@ -274,6 +274,24 @@ protected virtual async Task GetPhysicalPath( ProcessedName fileName ) { #endregion + #region GenerateTempDownloadUrlAsync + + /// + public async Task GenerateTempDownloadUrlAsync( string fileName, CancellationToken cancellationToken = default ) { + var args = new GenerateTempDownloadUrlArgs( fileName ); + return await GenerateTempDownloadUrlAsync( args, cancellationToken ); + } + + /// + public virtual Task GenerateTempDownloadUrlAsync( GenerateTempDownloadUrlArgs args, CancellationToken cancellationToken = default ) { + args.CheckNull( nameof( args ) ); + var processedFileName = ProcessFileName( args ); + var url = Util.Helpers.Common.JoinPath( Util.Helpers.Web.Host, processedFileName.Name ); + return Task.FromResult( url ); + } + + #endregion + #region GenerateUploadUrlAsync /// diff --git a/src/Util.FileStorage/UserTimeFileNameProcessor.cs b/src/Util.FileStorage/UserTimeFileNameProcessor.cs index d88cb7cf8..ab50f1703 100644 --- a/src/Util.FileStorage/UserTimeFileNameProcessor.cs +++ b/src/Util.FileStorage/UserTimeFileNameProcessor.cs @@ -1,5 +1,4 @@ using Util.Helpers; -using Util.Sessions; namespace Util.FileStorage; @@ -10,46 +9,27 @@ public class UserTimeFileNameProcessor : IFileNameProcessor { /// /// 策略名称 /// - public const string Policy = "usertime"; + public const string Policy = "USERTIME"; /// /// 用户会话 /// private readonly ISession _session; - /// - /// 文件名过滤器 - /// - private readonly IFileNameFilter _filter; /// /// 初始化基于用户标识和时间的文件名处理器 /// /// 用户会话 - /// 文件名过滤器 - public UserTimeFileNameProcessor( ISession session, IFileNameFilter filter ) { + public UserTimeFileNameProcessor( ISession session ) { _session = session ?? NullSession.Instance; - _filter = filter; } /// public ProcessedName Process( string fileName ) { - fileName = _filter?.Filter( fileName ); - var result = $"{GetUserId()}{GetTime()}{fileName}"; + if ( fileName.IsEmpty() ) + return new ProcessedName( null ); + var extension = Path.GetExtension( fileName ); + var name = $"{Id.Create()}{extension}"; + var result = Util.Helpers.Common.JoinPath( _session.UserId, $"{Time.Now:yyyy-MM-dd}", name ); return new ProcessedName( result, fileName ); } - - /// - /// 获取用户标识 - /// - private string GetUserId() { - if ( _session.UserId.IsEmpty() ) - return null; - return $"{_session.UserId}/"; - } - - /// - /// 获取时间 - /// - private string GetTime() { - return $"{Time.Now:yyyy-MM-dd-HH-mm-ss-fff}/"; - } } \ No newline at end of file diff --git a/src/Util.FileStorage/Usings.cs b/src/Util.FileStorage/Usings.cs index d87a5e5d0..0e6b7d400 100644 --- a/src/Util.FileStorage/Usings.cs +++ b/src/Util.FileStorage/Usings.cs @@ -12,3 +12,4 @@ global using FileSignatures.Formats; global using Util.Dependency; global using Util.Http; +global using Util.Sessions; diff --git a/test/Util.FileStorage.Aliyun.Tests.Integration/Tests/AliyunFileStoreTest.cs b/test/Util.FileStorage.Aliyun.Tests.Integration/Tests/AliyunFileStoreTest.cs index 510fd8cca..969ac3860 100644 --- a/test/Util.FileStorage.Aliyun.Tests.Integration/Tests/AliyunFileStoreTest.cs +++ b/test/Util.FileStorage.Aliyun.Tests.Integration/Tests/AliyunFileStoreTest.cs @@ -344,15 +344,12 @@ public async Task TestDeleteFileAsync() { /// [Fact] public async Task TestGenerateDownloadUrlAsync() { - //保存文件 - var path = Common.GetPhysicalPath( "~/Resources/a.png" ); - var fileInfo = new FileInfo( path ); - var result = await _fileStore.SaveFileAsync( fileInfo ); - - //生成url - var url = await _fileStore.GenerateDownloadUrlAsync( result.FileName ); + var args = new GenerateDownloadUrlArgs("a.jpg") { + BucketName = "test" + }; + var url = await _fileStore.GenerateDownloadUrlAsync( args ); _testOutputHelper.WriteLine( url ); - Assert.StartsWith( "http", url ); + Assert.StartsWith( "https://test.oss-cn-beijing.aliyuncs.com/a.jpg", url ); } #endregion diff --git a/test/Util.FileStorage.Minio.Tests.Integration/Tests/MinioFileStoreTest.cs b/test/Util.FileStorage.Minio.Tests.Integration/Tests/MinioFileStoreTest.cs index 7a6c5f5b5..69b78d1bb 100644 --- a/test/Util.FileStorage.Minio.Tests.Integration/Tests/MinioFileStoreTest.cs +++ b/test/Util.FileStorage.Minio.Tests.Integration/Tests/MinioFileStoreTest.cs @@ -387,15 +387,9 @@ public async Task TestDeleteFileAsync() { /// [Fact] public async Task TestGenerateDownloadUrlAsync() { - //保存文件 - var path = Common.GetPhysicalPath( "~/Resources/b.jpg" ); - var fileInfo = new FileInfo( path ); - var result = await _fileStore.SaveFileAsync( fileInfo ); - - //生成url - var url = await _fileStore.GenerateDownloadUrlAsync( "a" ); + var url = await _fileStore.GenerateDownloadUrlAsync( "a.jpg" ); _testOutputHelper.WriteLine( url ); - Assert.StartsWith( "http", url ); + Assert.Equal( "http://127.0.0.1:9000/util-filestorage-minio-test/a.jpg", url ); } #endregion diff --git a/test/Util.FileStorage.Minio.Tests.Integration/Tests/UserTimeFileNameProcessorTest.cs b/test/Util.FileStorage.Minio.Tests.Integration/Tests/UserTimeFileNameProcessorTest.cs index 5da7dfeec..e32f016e3 100644 --- a/test/Util.FileStorage.Minio.Tests.Integration/Tests/UserTimeFileNameProcessorTest.cs +++ b/test/Util.FileStorage.Minio.Tests.Integration/Tests/UserTimeFileNameProcessorTest.cs @@ -13,7 +13,8 @@ public class UserTimeFileNameProcessorTest : IDisposable { /// 测试初始化 /// public UserTimeFileNameProcessorTest() { - Time.SetTime( new DateTime( 2012, 12, 12, 12, 12, 12, 123 ) ); + Time.SetTime( new DateTime( 2012, 12, 12 ) ); + Id.SetId( "id" ); } /// @@ -21,6 +22,7 @@ public UserTimeFileNameProcessorTest() { /// public void Dispose() { Time.Reset(); + Id.Reset(); } /// @@ -28,8 +30,8 @@ public void Dispose() { /// [Fact] public void TestProcess_1() { - var processor = new UserTimeFileNameProcessor( new TestSession(),new FileNameFilter() ); + var processor = new UserTimeFileNameProcessor( new TestSession() ); var result = processor.Process( "a.jpg" ); - Assert.Equal( $"{TestSession.TestUserId}/2012-12-12-12-12-12-123/a.jpg", result.Name ); + Assert.Equal( $"{TestSession.TestUserId}/2012-12-12/id.jpg", result.Name ); } } \ No newline at end of file