From 4856eb095549c78a3c1a3e4ea84b56e54abdb815 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 16 Oct 2025 11:02:13 +0800 Subject: [PATCH 1/2] Optimize BlobProvider - Build an object download link using 'PresignedGetObjectAsync' and directly download the network stream through 'HttpClient'. - ` MinioBlobProviderConfiguration ` add a ` PresignedGetExpirySeconds ` configuration items, used to configure expiration time component link, the default is 7 days. --- ...lientFactoryServiceCollectionExtensions.cs | 18 ++++++++++ .../Volo.Abp.BlobStoring.Minio.csproj | 1 + .../Minio/AbpBlobStoringMinioModule.cs | 8 +++-- .../BlobStoring/Minio/MinioBlobProvider.cs | 34 +++++++++---------- .../Minio/MinioBlobProviderConfiguration.cs | 10 ++++++ .../MinioBlobProviderConfigurationNames.cs | 1 + .../Minio/AbpBlobStoringMinioTestModule.cs | 1 + 7 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 framework/src/Volo.Abp.BlobStoring.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs new file mode 100644 index 0000000000..8838a1a7f1 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Microsoft/Extensions/DependencyInjection/MinioHttpClientFactoryServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using System.Net.Http; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class MinioHttpClientFactoryServiceCollectionExtensions +{ + private const string HttpClientName = "__MinioApiClient"; + public static IServiceCollection AddMinioHttpClient(this IServiceCollection services) + { + services.AddHttpClient(HttpClientName); + + return services; + } + + public static HttpClient CreateMinioHttpClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(HttpClientName); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj index 8a2dff731a..e0bd8f3687 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj @@ -18,6 +18,7 @@ + diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs index 31d023ecf2..b1746a0e34 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs @@ -1,9 +1,13 @@ -using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; namespace Volo.Abp.BlobStoring.Minio; [DependsOn(typeof(AbpBlobStoringModule))] public class AbpBlobStoringMinioModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMinioHttpClient(); + } } diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs index 5fa1ceb0ce..82490c177a 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs @@ -1,22 +1,27 @@ -using Minio; -using Minio.Exceptions; -using System; +using System; using System.IO; +using System.Net.Http; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Minio; using Minio.DataModel.Args; +using Minio.Exceptions; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BlobStoring.Minio; public class MinioBlobProvider : BlobProviderBase, ITransientDependency { + protected IHttpClientFactory HttpClientFactory { get; } protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; } protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } public MinioBlobProvider( + IHttpClientFactory httpClientFactory, IMinioBlobNameCalculator minioBlobNameCalculator, IBlobNormalizeNamingService blobNormalizeNamingService) { + HttpClientFactory = httpClientFactory; MinioBlobNameCalculator = minioBlobNameCalculator; BlobNormalizeNamingService = blobNormalizeNamingService; } @@ -81,21 +86,16 @@ public class MinioBlobProvider : BlobProviderBase, ITransientDependency return null; } - var memoryStream = new MemoryStream(); - await client.GetObjectAsync(new GetObjectArgs().WithBucket(containerName).WithObject(blobName).WithCallbackStream(stream => - { - if (stream != null) - { - stream.CopyTo(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - } - else - { - memoryStream = null; - } - })); + var configuration = args.Configuration.GetMinioConfiguration(); + var downloadUrl = await client.PresignedGetObjectAsync( + new PresignedGetObjectArgs() + .WithBucket(containerName) + .WithObject(blobName) + .WithExpiry(configuration.PresignedGetExpirySeconds)); + + var httpClient = HttpClientFactory.CreateMinioHttpClient(); - return memoryStream; + return await httpClient.GetStreamAsync(downloadUrl, args.CancellationToken); } protected virtual IMinioClient GetMinioClient(BlobProviderArgs args) diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs index e626a6837e..740a24f04f 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs @@ -47,6 +47,16 @@ public class MinioBlobProviderConfiguration set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.CreateBucketIfNotExists, value); } + /// + /// Default value: 7 * 24 * 3600. + /// + public int PresignedGetExpirySeconds { + get => _containerConfiguration.GetConfigurationOrDefault(MinioBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.PresignedGetExpirySeconds, value); + } + + private int _defaultExpirySeconds = 7 * 24 * 3600; + private readonly BlobContainerConfiguration _containerConfiguration; public MinioBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs index 8a8a121ef7..1c32bd8aa0 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs @@ -8,4 +8,5 @@ public static class MinioBlobProviderConfigurationNames public const string SecretKey = "Minio.SecretKey"; public const string WithSSL = "Minio.WithSSL"; public const string CreateBucketIfNotExists = "Minio.CreateBucketIfNotExists"; + public const string PresignedGetExpirySeconds = "Minio.PresignedGetExpirySeconds"; } diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs index ce24894b5c..fc6b8e5841 100644 --- a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs @@ -57,6 +57,7 @@ public class AbpBlobStoringMinioTestModule : AbpModule minio.WithSSL = false; minio.BucketName = _randomContainerName; minio.CreateBucketIfNotExists = true; + minio.PresignedGetExpirySeconds = 3600; }); }); }); From 6d8e39c80fdac6168c9d2057d82d9f9b8bc2c7ab Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 16 Oct 2025 11:27:32 +0800 Subject: [PATCH 2/2] update minio.md - Update **PresignedGetExpirySeconds ** description --- docs/en/framework/infrastructure/blob-storing/minio.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/framework/infrastructure/blob-storing/minio.md b/docs/en/framework/infrastructure/blob-storing/minio.md index 771b699278..39ef2f959c 100644 --- a/docs/en/framework/infrastructure/blob-storing/minio.md +++ b/docs/en/framework/infrastructure/blob-storing/minio.md @@ -31,6 +31,7 @@ Configure(options => minio.AccessKey = "your minio accessKey"; minio.SecretKey = "your minio secretKey"; minio.BucketName = "your minio bucketName"; + minio.PresignedGetExpirySeconds = 3600; }); }); }); @@ -53,6 +54,7 @@ Configure(options => * Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration. * **WithSSL** (bool): Default value is `false`,Chain to MinIO Client object to use https instead of http. * **CreateContainerIfNotExists** (bool): Default value is `false`, If a bucket does not exist in minio, `MinioBlobProvider` will try to create it. +* **PresignedGetExpirySeconds** (int): Default value is `7 * 24 * 3600`, The expiration time of the pre-specified get url. The is valid within the range of 1 to 604800(corresponding to 7 days). ## Minio Blob Name Calculator