From 5550ce36f7ef8ffd7d362d46a1952a659aa87455 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 21 May 2026 18:27:45 +0800 Subject: [PATCH 1/4] feat: Enhance blob management module - Add the interface for generating download links - Alibaba Cloud Object Storage adds configuration for the expiration time of pre-signed links - Tencent Cloud Object Storage adds configuration for the expiration time of pre-signed links - Add generation of download url feature - Add the setting item for the expiration time of the generated download url - Add the interface for parsing file ContentType - Integrate the MimeCheck module to parse the Content-Type of the file --- .../LINGYUN.MicroService.SingleProject.slnx | 2 + .../TencentBlobProviderConfiguration.cs | 10 +++ .../TencentBlobProviderConfigurationNames.cs | 4 + .../Tencent/TencentCloudBlobProvider.cs | 2 +- .../LINGYUN.Abp.BlobStoring.Aliyun.csproj | 3 +- .../Aliyun/AbpBlobStoringAliyunModule.cs | 5 ++ .../BlobStoring/Aliyun/AliyunBlobProvider.cs | 12 ++- .../Aliyun/AliyunBlobProviderConfiguration.cs | 10 +++ .../AliyunBlobProviderConfigurationNames.cs | 4 + ...lientFactoryServiceCollectionExtensions.cs | 18 ++++ .../Aliyun/AliyunBlobProvider.cs | 58 +++++++++++-- .../Dtos/BlobDownloadByKeyInput.cs | 12 +++ .../Abp/BlobManagement/IBlobAppService.cs | 4 + ...bManagementPermissionDefinitionProvider.cs | 2 + .../BlobManagementPermissionNames.cs | 2 + .../Abp/BlobManagement/BlobAppService.cs | 16 ---- .../Abp/BlobManagement/BlobAppServiceBase.cs | 52 ++++++++--- .../BlobWithoutContainerAppService.cs | 18 ---- .../Integration/BlobIntegrationService.cs | 13 +-- .../BlobManagement/PublicBlobAppService.cs | 14 +-- ...BlobManagementContentTypeResolveOptions.cs | 15 ++++ .../BlobContentTypeResolveContext.cs | 33 +++++++ ...BlobManagementFeatureDefinitionProvider.cs | 7 +- .../Features/BlobManagementFeatureNames.cs | 4 + .../IBlobContentTypeResolveContributor.cs | 10 +++ .../Localization/Resources/en.json | 5 ++ .../Localization/Resources/zh-Hans.json | 5 ++ ...BlobManagementSettingDefinitionProvider.cs | 11 +++ .../Settings/BlobManagementSettingNames.cs | 7 +- .../BlobManagement/BlobContentTypeResolver.cs | 71 +++++++++++++++ .../BlobDownloadKeyCacheItem.cs | 24 +++++ .../LINGYUN/Abp/BlobManagement/BlobManager.cs | 87 ++++++++++++++++++- .../IBlobContentTypeResolver.cs | 9 ++ .../Abp/BlobManagement/IBlobProvider.cs | 10 ++- .../FileSystem/FileSystemBlobProvider.cs | 13 ++- .../AbpBlobManagementHttpApiModule.cs | 14 +++ .../Abp/BlobManagement/BlobController.cs | 14 +++ ...ensionBlobContentTypeResolveContributor.cs | 24 +++++ .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 +++++++ ...INGYUN.Abp.BlobManagement.MimeCheck.csproj | 25 ++++++ .../AbpBlobManagementMimeCheckModule.cs | 15 ++++ ...eCheckBlobContentTypeResolveContributor.cs | 33 +++++++ .../BlobManagement/Minio/MinioBlobProvider.cs | 57 ++++++++---- .../BlobManagementSettingAppService.cs | 5 ++ .../Tencent/TencentBlobProvider.cs | 49 ++++++++--- ...LY.MicroService.Applications.Single.csproj | 1 + .../MicroServiceApplicationsSingleModule.cs | 3 + 48 files changed, 737 insertions(+), 108 deletions(-) create mode 100644 aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs create mode 100644 aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.slnx b/aspnet-core/LINGYUN.MicroService.SingleProject.slnx index 7f687aba2..5e401511a 100644 --- a/aspnet-core/LINGYUN.MicroService.SingleProject.slnx +++ b/aspnet-core/LINGYUN.MicroService.SingleProject.slnx @@ -11,6 +11,7 @@ + @@ -211,6 +212,7 @@ + diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs index 5ebd4b5ba..3e6e85705 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs @@ -53,6 +53,16 @@ public class TencentBlobProviderConfiguration } } + /// + /// Default value: 600. + /// + public int PresignedGetExpirySeconds { + get => _containerConfiguration.GetConfigurationOrDefault(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds); + set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, value); + } + + private int _defaultExpirySeconds = 600; + private readonly BlobContainerConfiguration _containerConfiguration; public TencentBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs index 50b15be12..804c5813e 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs @@ -22,4 +22,8 @@ public static class TencentBlobProviderConfigurationNames /// 创建命名空间时防盗链列表 /// public const string CreateBucketReferer = "Tencent:OSS:CreateBucketReferer"; + /// + /// 生成预签名Uri的过期时间(s) + /// + public const string PresignedGetExpirySeconds = "Tencent:OSS:PresignedGetExpirySeconds"; } diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs index 65429bdfa..11b6e09fc 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs +++ b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs @@ -78,7 +78,7 @@ public class TencentCloudBlobProvider : BlobProviderBase, ITransientDependency key = blobName, //对象键 httpMethod = "GET", //HTTP 请求方法 isHttps = true, //生成 HTTPS 请求 URL - signDurationSecond = 600, //请求签名时间为600s + signDurationSecond = configuration.PresignedGetExpirySeconds, //请求签名时间为600s headers = null, //签名中需要校验的 header queryParameters = null //签名中需要校验的 URL 中请求参数 }; diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj index faad574d2..59822d893 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj @@ -4,7 +4,7 @@ - netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0 LINGYUN.Abp.BlobStoring.Aliyun LINGYUN.Abp.BlobStoring.Aliyun false @@ -16,6 +16,7 @@ + diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs index 5a52f79a1..cb88a938c 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Aliyun; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.BlobStoring; using Volo.Abp.Modularity; @@ -9,6 +10,10 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun; typeof(AbpAliyunModule))] public class AbpBlobStoringAliyunModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAliyunHttpClient(); + } // 需要时引用配置 //public override void ConfigureServices(ServiceConfigurationContext context) //{ diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs index 63c09c373..0064ce2cc 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs @@ -1,8 +1,10 @@ using Aliyun.OSS; using LINGYUN.Abp.Aliyun.Features; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; +using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.BlobStoring; using Volo.Abp.DependencyInjection; @@ -14,13 +16,16 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun; public class AliyunBlobProvider : BlobProviderBase, ITransientDependency { protected IOssClientFactory OssClientFactory { get; } + protected IHttpClientFactory HttpClientFactory { get; } protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; } public AliyunBlobProvider( IOssClientFactory ossClientFactory, + IHttpClientFactory httpClientFactory, IAliyunBlobNameCalculator aliyunBlobNameCalculator) { OssClientFactory = ossClientFactory; + HttpClientFactory = httpClientFactory; AliyunBlobNameCalculator = aliyunBlobNameCalculator; } @@ -55,8 +60,11 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency return null; } - var ossObject = ossClient.GetObject(GetBucketName(args), blobName); - return ossObject.Content; + var downloadUri = ossClient.GeneratePresignedUri(GetBucketName(args), blobName); + + var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + + return await httpClient.GetStreamAsync(downloadUri, args.CancellationToken); } public override async Task SaveAsync(BlobProviderSaveArgs args) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs index 901053d0c..d7efa95a4 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs @@ -40,6 +40,16 @@ public class AliyunBlobProviderConfiguration } } + /// + /// Default value: 7 * 24 * 3600. + /// + public int PresignedGetExpirySeconds { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, value); + } + + private int _defaultExpirySeconds = 7 * 24 * 3600; + private readonly BlobContainerConfiguration _containerConfiguration; public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs index 13e0c3c9c..e3e0c9221 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs @@ -18,4 +18,8 @@ public static class AliyunBlobProviderConfigurationNames /// 创建命名空间时防盗链列表 /// public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer"; + /// + /// 生成预签名Uri的过期时间(s) + /// + public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds"; } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs new file mode 100644 index 000000000..b28410930 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using System.Net.Http; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class AliyunHttpClientFactoryServiceCollectionExtensions +{ + private const string HttpClientName = "__AliyunBlobStoringApiClient"; + public static IServiceCollection AddAliyunHttpClient(this IServiceCollection services) + { + services.AddHttpClient(HttpClientName); + + return services; + } + + public static HttpClient CreateAliyunHttpClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(HttpClientName); + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs index 50157e116..912320b7a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs @@ -7,8 +7,9 @@ using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Volo.Abp; +using Volo.Abp.BlobStoring; using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; namespace LINGYUN.Abp.BlobManagement.Aliyun; @@ -17,24 +18,30 @@ public class AliyunBlobProvider : IBlobProvider public const string ProviderName = "Aliyun"; public string Name => ProviderName; + protected IClock Clock { get; } protected IConfiguration Configuration { get; } protected ICurrentTenant CurrentTenant { get; } protected IOssClientFactory OssClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; } protected AliyunBlobNamingNormalizer BlobNamingNormalizer { get; } + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } public AliyunBlobProvider( + IClock clock, IConfiguration configuration, ICurrentTenant currentTenant, IOssClientFactory ossClientFactory, IHttpClientFactory httpClientFactory, - AliyunBlobNamingNormalizer blobNamingNormalizer) + AliyunBlobNamingNormalizer blobNamingNormalizer, + IBlobContainerConfigurationProvider configurationProvider) { + Clock = clock; Configuration = configuration; CurrentTenant = currentTenant; OssClientFactory = ossClientFactory; HttpClientFactory = httpClientFactory; BlobNamingNormalizer = blobNamingNormalizer; + ConfigurationProvider = configurationProvider; } public async virtual Task CreateContainerAsync( @@ -90,6 +97,29 @@ public class AliyunBlobProvider : IBlobProvider string containerName, string blobName, CancellationToken cancellationToken = default) + { + var configuration = GetBlobConfiguration(); + + var downloadUrl = await GenerateDownloadUrlAsync( + containerName, + blobName, + TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), + cancellationToken); + if (downloadUrl.IsNullOrWhiteSpace()) + { + return null; + } + + var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + + return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + } + + public async virtual Task GenerateDownloadUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); @@ -100,11 +130,7 @@ public class AliyunBlobProvider : IBlobProvider return null; } - var downloadUrl = client.GeneratePresignedUri(bucket, objectName); - - var httpClient = HttpClientFactory.CreateAliyunHttpClient(); - - return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + return client.GeneratePresignedUri(bucket, objectName, Clock.Now.Add(expiration)).ToString(); } public virtual Task CreateFolderAsync( @@ -120,7 +146,8 @@ public class AliyunBlobProvider : IBlobProvider public async virtual Task UploadBlobAsync( string containerName, string blobName, - Stream content, + Stream content, + string? contentType = null, CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); @@ -129,7 +156,20 @@ public class AliyunBlobProvider : IBlobProvider CreateBucketIfNotExists(client, bucket); - client.PutObject(bucket, objectName, content); + var objectMetadata = new ObjectMetadata(); + if (!contentType.IsNullOrWhiteSpace()) + { + objectMetadata.ContentType = contentType; + } + + client.PutObject(bucket, objectName, content, objectMetadata); + } + + protected virtual AliyunBlobProviderConfiguration GetBlobConfiguration() + { + var configuration = ConfigurationProvider.Get(); + var blobConfiguration = configuration.GetAliyunConfiguration(); + return blobConfiguration; } protected async virtual Task CreateClientAsync() diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs new file mode 100644 index 000000000..fb5da0020 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.BlobManagement.Dtos; + +public class BlobDownloadByKeyInput +{ + public Guid? TenantId { get; set; } + + [Required] + public string Key { get; set; } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs index 3ea16c27a..2d4679569 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs @@ -17,6 +17,10 @@ public interface IBlobAppService : IApplicationService Task DeleteAsync(Guid id); + Task GenerateDownloadUrlAsync(Guid id); + + Task DownloadAsync(BlobDownloadByKeyInput input); + Task GetContentAsync(Guid id); Task GetContentByNameAsync(BlobDownloadByNameInput input); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs index afacc3089..91bbbc778 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs @@ -27,6 +27,8 @@ public class BlobManagementPermissionDefinitionProvider : PermissionDefinitionPr blob.AddChild(BlobManagementPermissionNames.Blob.Delete, L("Permission:Delete")); blob.AddChild(BlobManagementPermissionNames.Blob.Download, L("Permission:Download")) .RequireFeatures(BlobManagementFeatureNames.Blob.DownloadFile); + blob.AddChild(BlobManagementPermissionNames.Blob.GenerateDownloadUrl, L("Permission:GenerateDownloadUrl")) + .RequireFeatures(BlobManagementFeatureNames.Blob.GenerateDownloadUrl); context.AddResourcePermission( name: BlobManagementPermissionNames.Blob.Resources.Delete, diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs index 56bca0d0e..c062e8bda 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs @@ -23,6 +23,8 @@ public static class BlobManagementPermissionNames public const string Download = Default + ".Download"; + public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl"; + public const string ManagePermissions = Default + ".ManagePermissions"; public static class Resources diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs index 8dab178a9..689395ef4 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs @@ -1,7 +1,6 @@ using LINGYUN.Abp.BlobManagement.Dtos; using LINGYUN.Abp.BlobManagement.Features; using LINGYUN.Abp.BlobManagement.Permissions; -using LINGYUN.Abp.Features.LimitValidation; using System.Threading.Tasks; using Volo.Abp.Content; using Volo.Abp.Features; @@ -23,11 +22,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService GetListPolicyName = BlobManagementPermissionNames.Blob.Default; } - [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.UploadLimit, - BlobManagementFeatureNames.Blob.UploadInterval, - LimitPolicy.Month)] public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateDto input) { await CheckCreatePolicyAsync(); @@ -35,11 +29,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService await base.CreateChunkFileAsync(input.ContainerId.ToString(), input); } - [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.UploadLimit, - BlobManagementFeatureNames.Blob.UploadInterval, - LimitPolicy.Month)] public async virtual Task CreateFileAsync(BlobFileCreateDto input) { await CheckCreatePolicyAsync(); @@ -58,11 +47,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService return await base.CreateFolderAsync(blobContainer, input); } - [RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.DownloadLimit, - BlobManagementFeatureNames.Blob.DownloadInterval, - LimitPolicy.Month)] public async virtual Task GetContentByNameAsync(BlobDownloadByNameInput input) { var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs index 35d461b7f..683e6b1ce 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs @@ -1,7 +1,5 @@ using LINGYUN.Abp.BlobManagement.Dtos; -using LINGYUN.Abp.BlobManagement.Features; using LINGYUN.Abp.BlobManagement.Permissions; -using LINGYUN.Abp.Features.LimitValidation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; @@ -13,7 +11,6 @@ using System.Web; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Content; -using Volo.Abp.Features; using Volo.Abp.Specifications; namespace LINGYUN.Abp.BlobManagement; @@ -49,11 +46,44 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService await DeleteBlobAsync(blob); } - [RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.DownloadLimit, - BlobManagementFeatureNames.Blob.DownloadInterval, - LimitPolicy.Month)] + public async virtual Task GenerateDownloadUrlAsync(Guid id) + { + var blob = await BlobRepository.GetAsync(id); + var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId); + + await CheckGetPolicyAsync(blob); + + var fallbackUrlPrefix = blob.TenantId.HasValue + ? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download/t/{blob.TenantId:N}" + : $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download"; + + var downloadUrl = await BlobManager.GenerateDownloadUrlAsync( + blobContainer, + blob, + fallbackUrlPrefix); + + return downloadUrl; + } + + public async virtual Task DownloadAsync(BlobDownloadByKeyInput input) + { + using (CurrentTenant.Change(input.TenantId)) + { + var blob = await BlobManager.FindBlobByDownloadKeyAsync(input.Key); + if (blob == null) + { + return new RemoteStreamContent(Stream.Null, input.Key); + } + + // 临时下载链接一般用于临时预览与下载, 过期时间短, 不校验权限 + // await CheckGetPolicyAsync(blob); + + var stream = await BlobManager.DownloadBlobsync(blob); + + return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); + } + } + public async virtual Task GetContentAsync(Guid id) { var blob = await BlobRepository.GetAsync(id); @@ -62,7 +92,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService var stream = await BlobManager.DownloadBlobsync(blob); - return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, blob.Size); + return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); } public async virtual Task GetAsync(Guid id) @@ -120,7 +150,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService blobStream.Length, parentBlob); - blob = await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.File.ContentType, input.CompareMd5); + blob = await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.CompareMd5); return ObjectMapper.Map(blob); } @@ -167,7 +197,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService var stream = await BlobManager.DownloadBlobsync(blob); - return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, blob.Size); + return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); } protected async virtual Task FindBlobByNameAsync(BlobContainer blobContainer, string name) diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs index 2b0d608ba..66ed740d8 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs @@ -1,10 +1,7 @@ using LINGYUN.Abp.BlobManagement.Dtos; -using LINGYUN.Abp.BlobManagement.Features; -using LINGYUN.Abp.Features.LimitValidation; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Content; -using Volo.Abp.Features; namespace LINGYUN.Abp.BlobManagement; @@ -20,11 +17,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase { } - [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.UploadLimit, - BlobManagementFeatureNames.Blob.UploadInterval, - LimitPolicy.Month)] public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateWithoutContainerDto input) { await CheckCreatePolicyAsync(); @@ -32,11 +24,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase await base.CreateChunkFileAsync(ContainerName, input); } - [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.UploadLimit, - BlobManagementFeatureNames.Blob.UploadInterval, - LimitPolicy.Month)] public async virtual Task CreateFileAsync(BlobFileCreateWithoutContainerDto input) { await CheckCreatePolicyAsync(); @@ -55,11 +42,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase return await base.CreateFolderAsync(blobContainer, input); } - [RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.DownloadLimit, - BlobManagementFeatureNames.Blob.DownloadInterval, - LimitPolicy.Month)] public async virtual Task GetContentByNameAsync(string name) { var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs index 9bfa786c4..367da5ba9 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs @@ -1,7 +1,6 @@ using LINGYUN.Abp.BlobManagement.Dtos; using LINGYUN.Abp.BlobManagement.Features; using LINGYUN.Abp.BlobManagement.Integration.Dtos; -using LINGYUN.Abp.Features.LimitValidation; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -27,11 +26,6 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic { } - [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.UploadLimit, - BlobManagementFeatureNames.Blob.UploadInterval, - LimitPolicy.Month)] public async virtual Task CreateAsync(BlobFileCreateIntegrationDto input) { var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); @@ -62,7 +56,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic parentBlob, input.Overwrite == true); - await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.File.ContentType); + await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream); return ObjectMapper.Map(blob); } @@ -96,11 +90,6 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic } } - [RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] - [RequiresLimitFeature( - BlobManagementFeatureNames.Blob.DownloadLimit, - BlobManagementFeatureNames.Blob.DownloadInterval, - LimitPolicy.Month)] public async virtual Task GetContentAsync(BlobFileGetByNameIntegrationDto input) { try diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs index ca63fd966..b30a3de6e 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.BlobManagement.Features; +using LINGYUN.Abp.BlobManagement.Permissions; using System.Threading.Tasks; using Volo.Abp.Features; @@ -15,16 +16,19 @@ public class PublicBlobAppService : BlobWithoutContainerAppService, IPublicBlobA BlobManager blobManager) : base(blobContainerRepository, blobRepository, blobManager) { + DeletePolicyName = BlobManagementPermissionNames.Blob.Delete; } - protected override Task CheckGetPolicyAsync(string containerName, Blob blob) + protected async override Task CheckGetPolicyAsync(string containerName, Blob blob) { - // 公共文件访问不校验权限 - return Task.CompletedTask; + if (!CurrentUser.IsAuthenticated) + { + await FeatureChecker.CheckEnabledAsync(BlobManagementFeatureNames.PublicAccess); + } } - protected override Task CheckDeletePolicyAsync(string containerName, Blob blob) + protected async override Task CheckDeletePolicyAsync(string containerName, Blob blob) { - return base.CheckDeletePolicyAsync(blob); + await CheckPolicyAsync(DeletePolicyName); } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs new file mode 100644 index 000000000..43d9ec924 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using System.Collections.Generic; + +namespace LINGYUN.Abp.BlobManagement; + +public class AbpBlobManagementContentTypeResolveOptions +{ + [NotNull] + public List BlobContentTypeResolvers { get; } + + public AbpBlobManagementContentTypeResolveOptions() + { + BlobContentTypeResolvers = new List(); + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs new file mode 100644 index 000000000..66c61164c --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; + +namespace LINGYUN.Abp.BlobManagement; + +public class BlobContentTypeResolveContext +{ + public IServiceProvider ServiceProvider { get; } + public string BlobName { get; } + public Stream Stream { get; } + public bool Handled { get; private set; } + public string? ContentType { get; private set; } + public BlobContentTypeResolveContext( + IServiceProvider serviceProvider, + string blobName, + Stream stream) + { + ServiceProvider = serviceProvider; + BlobName = blobName; + Stream = stream; + } + + public void Success(string contentType) + { + ContentType = contentType; + Handled = true; + } + + public bool HasResolvedContentType() + { + return Handled && !ContentType.IsNullOrWhiteSpace(); + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs index 7109dc899..f3af7b7f2 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs @@ -32,7 +32,12 @@ public class BlobManagementFeatureDefinitionProvider : FeatureDefinitionProvider displayName: L("Features:DisplayName:EnableBlob"), description: L("Features:Description:EnableBlob"), valueType: new ToggleStringValueType(new BooleanValueValidator())); - + blobDefaultFeature.CreateChild( + name: BlobManagementFeatureNames.Blob.GenerateDownloadUrl, + defaultValue: true.ToString(), + displayName: L("Features:DisplayName:GenerateDownloadUrl"), + description: L("Features:Description:GenerateDownloadUrl"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); blobDefaultFeature.CreateChild( name: BlobManagementFeatureNames.Blob.DownloadFile, defaultValue: true.ToString(), diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs index adfd8d833..67bee1eb4 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs @@ -20,6 +20,10 @@ public static class BlobManagementFeatureNames /// public const string Enable = Default + ".Enable"; /// + /// 生成临时文件下载链接 + /// + public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl"; + /// /// 下载文件功能 /// public const string DownloadFile = Default + ".DownloadFile"; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs new file mode 100644 index 000000000..a86d8b386 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BlobManagement; + +public interface IBlobContentTypeResolveContributor +{ + string Name { get; } + int Priority { get; } + Task ResolveAsync(BlobContentTypeResolveContext context); +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json index cd79e95ae..7babc31ef 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json @@ -10,6 +10,7 @@ "Permission:Download": "Download", "Permission:Blob:Delete": "Delete Blob", "Permission:Blob:View": "View Blob", + "Permission:GenerateDownloadUrl": "Generate Download Url", "Features:BlobManagement": "Blob Management", "Features:DisplayName:EnableBlobManagement": "Enable Blob Management", "Features:Description:EnableBlobManagement": "Enable to give the application the ability to have an blob storage module.", @@ -29,6 +30,8 @@ "Features:Description:UploadLimit": "Limits the total number of file uploads within a specified period", "Features:DisplayName:UploadInterval": "Limit Uploads Interval", "Features:Description:UploadInterval": "The update period that limits the number of files uploaded. The time scale is: month. Default: 1", + "Features:DisplayName:GenerateDownloadUrl": "Generate Download Url", + "Features:Description:GenerateDownloadUrl": "Whether to allow users to generate download links for temporary files", "BlobManagement:001001": "A blob container named {Name} already exists!", "BlobManagement:001002": "The blob container is not empty and cannot be deleted!", "BlobManagement:001003": "The blob container is the system container and cannot be deleted!", @@ -55,6 +58,8 @@ "Description:FileLimitLength": "Limit size of uploaded file in MB", "DisplayName:AllowFileExtensions": "File extension", "Description:AllowFileExtensions": "List of allowed extensions to upload files, with multiple extensions separated by, don't need a notation", + "DisplayName:GenerateDownloadUrlExpirySeconds": "Download Url Expiry Seconds", + "Description:GenerateDownloadUrlExpirySeconds": "Set the validity period for the temporary download link (in seconds). If not set or left blank, it defaults to 600 seconds.", "DisplayName:BlobManagement": "Blob Management", "DisplayName:Blobs": "Blobs", "DisplayName:CreationTime": "Creation Time", diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json index 4d1bf8b24..59f356251 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json @@ -10,6 +10,7 @@ "Permission:Download": "下载", "Permission:Blob:Delete": "删除存储对象", "Permission:Blob:View": "查看存储对象", + "Permission:GenerateDownloadUrl": "生成下载链接", "Features:BlobManagement": "对象存储", "Features:DisplayName:EnableBlobManagement": "启用对象存储", "Features:Description:EnableBlobManagement": "启用以使应用程序拥有对象存储模块的能力.", @@ -29,6 +30,8 @@ "Features:Description:UploadLimit": "在指定的周期内限制上传文件的总次数", "Features:DisplayName:UploadInterval": "限制上传文件周期", "Features:Description:UploadInterval": "限制上传文件次数的更新周期,时间刻度为:月,默认:1", + "Features:DisplayName:GenerateDownloadUrl": "生成文件下载链接", + "Features:Description:GenerateDownloadUrl": "是否允许用户生成临时文件下载链接", "BlobManagement:001001": "已经存在名为 {Name} 的存储容器!", "BlobManagement:001002": "存储容器非空,不可删除!", "BlobManagement:001003": "存储容器为系统容器,不可删除!", @@ -55,6 +58,8 @@ "Description:FileLimitLength": "上传文件的限制大小,单位(MB)", "DisplayName:AllowFileExtensions": "文件扩展名", "Description:AllowFileExtensions": "允许的上传文件扩展名列表,多个扩展名以,分隔,无需输入.符号", + "DisplayName:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效期", + "Description:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效时间, 单位(s), 未设置或为空时默认600s.", "DisplayName:BlobManagement": "对象存储", "DisplayName:Blobs": "存储管理", "DisplayName:CreationTime": "创建时间", diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs index 9160143dd..e8d1f886f 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs @@ -37,6 +37,17 @@ public class BlobManagementSettingDefinitionProvider : SettingDefinitionProvider ConfigurationSettingValueProvider.ProviderName, GlobalSettingValueProvider.ProviderName, TenantSettingValueProvider.ProviderName), + new SettingDefinition( + name: BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds, + defaultValue: BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds.ToString(), + displayName: L("DisplayName:GenerateDownloadUrlExpirySeconds"), + description: L("Description:GenerateDownloadUrlExpirySeconds"), + isVisibleToClients: true) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), }; } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs index cbd6a3fb1..853153820 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs @@ -11,7 +11,12 @@ public static class BlobManagementSettingNames /// 允许的文件扩展名类型 /// public const string AllowFileExtensions = GroupName + ".AllowFileExtensions"; + /// + /// 生成临时下载链接有效期(s) + /// + public const string GenerateDownloadUrlExpirySeconds = GroupName + ".GenerateDownloadUrlExpirySeconds"; public const long DefaultFileLimitLength = 5L; - public const string DefaultAllowFileExtensions = "dll,zip,rar,txt,log,xml,config,json,jpeg,jpg,png,bmp,ico,xlsx,xltx,xls,xlt,docs,dots,doc,dot,pptx,potx,ppt,pot,chm"; + public const double DefaultGenerateDownloadUrlExpirySeconds = 600d; + public const string DefaultAllowFileExtensions = "dll,zip,rar,txt,log,xml,config,json,jpeg,jpg,png,bmp,ico,xlsx,xltx,xls,xlt,docs,dots,doc,dot,pdf,pptx,potx,ppt,pot,chm"; } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs new file mode 100644 index 000000000..0ef93ff89 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.BlobManagement; + +public class BlobContentTypeResolver : IBlobContentTypeResolver, ITransientDependency +{ + public ILogger Logger { get; set; } + + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected AbpBlobManagementContentTypeResolveOptions Options { get; } + + public BlobContentTypeResolver( + IOptions options, + IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; + + Logger = NullLogger.Instance; + } + + public async virtual Task ResolveContentTypeAsync(string blobName, Stream blobStream) + { + string? result = null; + + Logger.LogDebug("Starting resolving content type..."); + using (var serviceScope = ServiceScopeFactory.CreateScope()) + { + var context = new BlobContentTypeResolveContext(serviceScope.ServiceProvider, blobName, blobStream); + + foreach (var blobContentTypeResolver in Options.BlobContentTypeResolvers.OrderByDescending(x => x.Priority)) + { + var originalPosition = context.Stream.CanSeek ? context.Stream.Position : -1; + + Logger.LogDebug("Trying to resolve content type '{Name}'...", blobContentTypeResolver.Name); + + await blobContentTypeResolver.ResolveAsync(context); + + if (originalPosition >= 0 && context.Stream.CanSeek) + { + context.Stream.Position = originalPosition; + } + + if (context.HasResolvedContentType()) + { + result = context.ContentType; + + Logger.LogDebug("Content type resolved by '{Name}' as '{ContentType}'.", blobContentTypeResolver.Name, result); + break; + } + } + } + + if (result.IsNullOrWhiteSpace()) + { + Logger.LogWarning("No provider can resolve content type. Using the default \"application/octet-stream\"."); + + return "application/octet-stream"; + } + + return result; + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs new file mode 100644 index 000000000..7c001efa9 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs @@ -0,0 +1,24 @@ +using System; + +namespace LINGYUN.Abp.BlobManagement; + +public class BlobDownloadKeyCacheItem +{ + public string Url { get; set; } + public Guid BlobId { get; set; } + public BlobDownloadKeyCacheItem() + { + + } + + public BlobDownloadKeyCacheItem(Guid blobId, string url) + { + BlobId = blobId; + Url = url; + } + + public static string CalculateCacheKey(Guid blobId, string blobName) + { + return $"{blobId:N}{blobName.RemovePreFix("/")}"; + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs index f52b5ee62..68df23011 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs @@ -1,4 +1,7 @@ -using LINGYUN.Abp.BlobManagement.Settings; +using LINGYUN.Abp.BlobManagement.Features; +using LINGYUN.Abp.BlobManagement.Settings; +using LINGYUN.Abp.Features.LimitValidation; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using System; using System.IO; @@ -10,6 +13,7 @@ using Volo.Abp.Caching; using Volo.Abp.Content; using Volo.Abp.Domain.Services; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Features; using Volo.Abp.IO; using Volo.Abp.Settings; using Volo.Abp.Specifications; @@ -19,11 +23,13 @@ namespace LINGYUN.Abp.BlobManagement; public class BlobManager : DomainService { + private readonly IDistributedCache _blobDownloadKeyCache; private readonly IDistributedCache _blobDownloadCache; private readonly IDistributedEventBus _distributedEventBus; private readonly IMemoryCache _blobFileValidateCache; private readonly ISettingProvider _settingProvider; + private readonly IBlobContentTypeResolver _blobContentTypeResolver; private readonly IBlobProvider _blobProvider; private readonly IBlobRepository _blobRepository; private readonly IBlobContainerRepository _blobContainerRepository; @@ -36,7 +42,9 @@ public class BlobManager : DomainService IBlobContainerRepository blobContainerRepository, ICancellationTokenProvider cancellationTokenProvider, IDistributedEventBus distributedEventBus, + IDistributedCache blobDownloadKeyCache, IDistributedCache blobDownloadCache, + IBlobContentTypeResolver blobContentTypeResolver, IApplicationInfoAccessor applicationInfoAccessor, IMemoryCache blobFileValidateCache, ISettingProvider settingProvider) @@ -46,7 +54,9 @@ public class BlobManager : DomainService _blobContainerRepository = blobContainerRepository; _cancellationTokenProvider = cancellationTokenProvider; _distributedEventBus = distributedEventBus; + _blobDownloadKeyCache = blobDownloadKeyCache; _blobDownloadCache = blobDownloadCache; + _blobContentTypeResolver = blobContentTypeResolver; _applicationInfoAccessor = applicationInfoAccessor; _blobFileValidateCache = blobFileValidateCache; _settingProvider = settingProvider; @@ -183,11 +193,15 @@ public class BlobManager : DomainService } } + [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] + [RequiresLimitFeature( + BlobManagementFeatureNames.Blob.UploadLimit, + BlobManagementFeatureNames.Blob.UploadInterval, + LimitPolicy.Month)] public async virtual Task UploadBlobAsync( BlobContainer blobContainer, Blob blob, Stream stream, - string? contentType = null, string? compareMd5 = null) { await ValidateUploadFileAsync(blob.Name, stream.Length); @@ -204,7 +218,14 @@ public class BlobManager : DomainService blob.SetContentMd5(blobMd5); } - await _blobProvider.UploadBlobAsync(blobContainer.Name, blob.FullName, stream, GetCancellationToken()); + var contentType = await _blobContentTypeResolver.ResolveContentTypeAsync(blob.Name, stream); + + await _blobProvider.UploadBlobAsync( + blobContainer.Name, + blob.FullName, + stream, + contentType, + GetCancellationToken()); blob.SetProvider(_blobProvider.Name); blob.SetFileInfo(contentType, stream.Length); @@ -212,6 +233,7 @@ public class BlobManager : DomainService return blob; } + [RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)] public async virtual Task CreateChunkBlobAsync( string containerIdOrName, string name, @@ -285,6 +307,7 @@ public class BlobManager : DomainService // 多分片文件流合并上传 mergeFileStream.Seek(0, SeekOrigin.Begin); + await UploadBlobAsync(blobContainer, blob, mergeFileStream, compareMd5); } // 文件保存之后删除临时文件目录 @@ -299,6 +322,11 @@ public class BlobManager : DomainService } } + [RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] + [RequiresLimitFeature( + BlobManagementFeatureNames.Blob.DownloadLimit, + BlobManagementFeatureNames.Blob.DownloadInterval, + LimitPolicy.Month)] public async virtual Task DownloadBlobsync(Blob blob) { var containerName = await _blobContainerRepository.GetNameAsync(blob.ContainerId); @@ -306,6 +334,59 @@ public class BlobManager : DomainService return await DownloadBlobsync(containerName, blob.FullName); } + [RequiresFeature(BlobManagementFeatureNames.Blob.GenerateDownloadUrl)] + public async virtual Task GenerateDownloadUrlAsync( + BlobContainer blobContainer, + Blob blob, + string fallbackUrlPrefix) + { + var cacheKey = BlobDownloadKeyCacheItem.CalculateCacheKey(blob.Id, blob.FullName); + var cacheItem = await _blobDownloadKeyCache.GetAsync(cacheKey); + if (cacheItem == null) + { + var downloadUrlExpirySeconds = await _settingProvider.GetAsync( + BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds, + BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds); + + var downloadUrl = await _blobProvider.GenerateDownloadUrlAsync( + blobContainer.Name, + blob.FullName, + TimeSpan.FromSeconds(downloadUrlExpirySeconds), + GetCancellationToken()); + + if (downloadUrl.IsNullOrWhiteSpace()) + { + // 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url前缀 + downloadUrl = fallbackUrlPrefix.EnsureEndsWith('/') + cacheKey; + } + + cacheItem = new BlobDownloadKeyCacheItem( + blob.Id, + downloadUrl); + + await _blobDownloadKeyCache.SetAsync( + cacheKey, + cacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(downloadUrlExpirySeconds), + }); + } + + return cacheItem.Url; + } + + public async virtual Task FindBlobByDownloadKeyAsync(string downloadKey) + { + var cacheItem = await _blobDownloadKeyCache.GetAsync(downloadKey); + if (cacheItem == null) + { + return null; + } + + return await _blobRepository.FindAsync(cacheItem.BlobId); + } + public virtual string GetBlobProvider() { return _blobProvider.Name; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs new file mode 100644 index 000000000..8bab66334 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs @@ -0,0 +1,9 @@ +using System.IO; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BlobManagement; + +public interface IBlobContentTypeResolver +{ + Task ResolveContentTypeAsync(string blobName, Stream blobStream); +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs index 72a7a708f..01a012279 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -21,6 +22,12 @@ public interface IBlobProvider string blobName, CancellationToken cancellationToken = default); + Task GenerateDownloadUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + CancellationToken cancellationToken = default); + Task CreateFolderAsync( string containerName, string blobName, @@ -35,6 +42,7 @@ public interface IBlobProvider string containerName, string blobName, Stream stream, + string? contentType = null, CancellationToken cancellationToken = default); Task DeleteBlobAsync( diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs index 173590bbf..8fcbc8b0f 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs @@ -99,6 +99,16 @@ public class FileSystemBlobProvider : IBlobProvider return Task.FromResult(File.OpenRead(fileName)); } + public virtual Task GenerateDownloadUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + // 本地文件系统无需生成下载链接 + return Task.FromResult(null); + } + public virtual Task CreateFolderAsync( string containerName, string blobName, @@ -114,7 +124,8 @@ public class FileSystemBlobProvider : IBlobProvider public async virtual Task UploadBlobAsync( string containerName, string blobName, - Stream content, + Stream content, + string? contentType = null, CancellationToken cancellationToken = default) { var fileName = CalculateBlobName(NormalizeContainerName(containerName.RemovePostFix("/")), blobName); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs index d873970ab..2955c3b39 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.Modularity; namespace LINGYUN.Abp.BlobManagement; @@ -26,4 +27,17 @@ public class AbpBlobManagementHttpApiModule : AbpModule typeof(AbpBlobManagementApplicationContractsModule).Assembly); }); } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ContentTypeMaps.TryAdd(".log", "text/plain"); + options.ContentTypeMaps.TryAdd(".text", "text/plain"); + }); + Configure(options => + { + options.BlobContentTypeResolvers.Add(new FileExtensionBlobContentTypeResolveContributor()); + }); + } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs index 559abbbf4..696f7d391 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs @@ -73,4 +73,18 @@ public class BlobController : BlobControllerBase, IBlobAppService { return _service.GetListAsync(input); } + + [HttpGet("{id}/generate-url")] + public virtual Task GenerateDownloadUrlAsync(Guid id) + { + return _service.GenerateDownloadUrlAsync(id); + } + + [HttpGet] + [Route("download/{Key}")] + [Route("download/t/{TenantId}/{Key}")] + public virtual Task DownloadAsync(BlobDownloadByKeyInput input) + { + return _service.DownloadAsync(input); + } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs new file mode 100644 index 000000000..c7f80cc8c --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BlobManagement; + +public class FileExtensionBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor +{ + public string Name => "FileExtension"; + public int Priority => 0; + + public virtual Task ResolveAsync(BlobContentTypeResolveContext context) + { + var contentTypeProvider = context.ServiceProvider.GetService(); + if (contentTypeProvider != null && + contentTypeProvider.TryGetContentType(context.BlobName, out var contentType) && + !contentType.IsNullOrWhiteSpace()) + { + context.Success(contentType); + } + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj new file mode 100644 index 000000000..3d93dbe81 --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj @@ -0,0 +1,25 @@ + + + + + + + net10.0 + LINGYUN.Abp.BlobManagement.MimeCheck + LINGYUN.Abp.BlobManagement.MimeCheck + false + false + false + enable + + + + + + + + + + + + diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs new file mode 100644 index 000000000..f39f98c4b --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.BlobManagement.MimeCheck; + +[DependsOn(typeof(AbpBlobManagementDomainModule))] +public class AbpBlobManagementMimeCheckModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.BlobContentTypeResolvers.Add(new MimeCheckBlobContentTypeResolveContributor()); + }); + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs new file mode 100644 index 000000000..16a68583c --- /dev/null +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MimeCheck.Detection; +using MimeCheck.Validation; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BlobManagement.MimeCheck; + +public class MimeCheckBlobContentTypeResolveContributor : IBlobContentTypeResolveContributor +{ + public string Name => "MimeCheck"; + public int Priority => 200; + + public async virtual Task ResolveAsync(BlobContentTypeResolveContext context) + { + try + { + var streamResult = await MimeValidator.DetectAsync(context.Stream); + if (!streamResult.MimeType.IsNullOrWhiteSpace() && + !string.Equals(streamResult.MimeType, DetectionResult.Unknown.MimeType)) + { + context.Success(streamResult.MimeType); + } + } + catch (Exception ex) + { + context.ServiceProvider + .GetService>() + ?.LogWarning("Failed to decetion the blob mime type: {message}!", ex.Message); + } + } +} diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs index bc449401f..69414c0c9 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring.Minio; using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; namespace LINGYUN.Abp.BlobManagement.Minio; @@ -19,17 +20,20 @@ public class MinioBlobProvider : IBlobProvider public const string ProviderName = "Minio"; public string Name => ProviderName; + protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } protected IHttpClientFactory HttpClientFactory { get; } protected MinioBlobNamingNormalizer BlobNamingNormalizer { get; } protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } public MinioBlobProvider( + IClock clock, ICurrentTenant currentTenant, IHttpClientFactory httpClientFactory, MinioBlobNamingNormalizer blobNamingNormalizer, IBlobContainerConfigurationProvider configurationProvider) { + Clock = clock; CurrentTenant = currentTenant; HttpClientFactory = httpClientFactory; BlobNamingNormalizer = blobNamingNormalizer; @@ -101,6 +105,29 @@ public class MinioBlobProvider : IBlobProvider string containerName, string blobName, CancellationToken cancellationToken = default) + { + var configuration = GetBlobConfiguration(); + + var downloadUrl = await GenerateDownloadUrlAsync( + containerName, + blobName, + TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), + cancellationToken); + if (downloadUrl.IsNullOrWhiteSpace()) + { + return null; + } + + var httpClient = HttpClientFactory.CreateMinioHttpClient(); + + return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + } + + public async virtual Task GenerateDownloadUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + CancellationToken cancellationToken = default) { var client = GetMinioClient(); var bucket = NormalizeContainerName(containerName); @@ -111,17 +138,11 @@ public class MinioBlobProvider : IBlobProvider return null; } - var configuration = GetBlobConfiguration(); - - var downloadUrl = await client.PresignedGetObjectAsync( - new PresignedGetObjectArgs() - .WithBucket(bucket) - .WithObject(objectName) - .WithExpiry(configuration.PresignedGetExpirySeconds)); - - var httpClient = HttpClientFactory.CreateMinioHttpClient(); - - return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + return await client.PresignedGetObjectAsync( + new PresignedGetObjectArgs() + .WithBucket(bucket) + .WithObject(objectName) + .WithExpiry(expiration.TotalSeconds.To())); } public virtual Task CreateFolderAsync( @@ -136,7 +157,8 @@ public class MinioBlobProvider : IBlobProvider public async virtual Task UploadBlobAsync( string containerName, string blobName, - Stream content, + Stream content, + string? contentType = null, CancellationToken cancellationToken = default) { var client = GetMinioClient(); @@ -145,12 +167,17 @@ public class MinioBlobProvider : IBlobProvider await CreateBucketIfNotExists(client, bucket); - await client.PutObjectAsync(new PutObjectArgs() + var putObjectArgs = new PutObjectArgs() .WithBucket(bucket) .WithObject(objectName) .WithStreamData(content) - .WithObjectSize(content.Length), - cancellationToken); + .WithObjectSize(content.Length); + if (!contentType.IsNullOrWhiteSpace()) + { + putObjectArgs.WithContentType(contentType); + } + + await client.PutObjectAsync(putObjectArgs, cancellationToken); } protected virtual IMinioClient GetMinioClient() diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs index 7e9425a50..1a6cfa5e1 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs @@ -51,6 +51,11 @@ public class BlobManagementSettingAppService : ApplicationService, IBlobManageme var ossObjectSetting = ossSettingGroup.AddSetting(L["DisplayName:Blobs"], L["DisplayName:Blobs"]); + ossObjectSetting.AddDetail( + await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds, providerName, providerKey), + ValueType.Number); ossObjectSetting.AddDetail( await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.FileLimitLength), StringLocalizerFactory, diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs index e17691b9c..671525b8d 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs @@ -10,9 +10,9 @@ using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Volo.Abp; using Volo.Abp.BlobStoring; using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; namespace LINGYUN.Abp.BlobManagement.Tencent; @@ -21,6 +21,7 @@ public class TencentBlobProvider : IBlobProvider public const string ProviderName = "TencentCloud"; public string Name => ProviderName; + protected IClock Clock { get; } protected IConfiguration Configuration { get; } protected ICurrentTenant CurrentTenant { get; } protected ICosClientFactory CosClientFactory { get; } @@ -29,6 +30,7 @@ public class TencentBlobProvider : IBlobProvider protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } public TencentBlobProvider( + IClock clock, IConfiguration configuration, ICurrentTenant currentTenant, ICosClientFactory cosClientFactory, @@ -36,6 +38,7 @@ public class TencentBlobProvider : IBlobProvider TencentBlobNamingNormalizer blobNamingNormalizer, IBlobContainerConfigurationProvider configurationProvider) { + Clock = clock; Configuration = configuration; CurrentTenant = currentTenant; CosClientFactory = cosClientFactory; @@ -97,6 +100,29 @@ public class TencentBlobProvider : IBlobProvider string containerName, string blobName, CancellationToken cancellationToken = default) + { + var configuration = await GetBlobConfiguration(); + + var downloadUrl = await GenerateDownloadUrlAsync( + containerName, + blobName, + TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), + cancellationToken); + if (downloadUrl.IsNullOrWhiteSpace()) + { + return null; + } + + var httpClient = HttpClientFactory.CreateTencentHttpClient(); + + return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + } + + public async virtual Task GenerateDownloadUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); @@ -108,8 +134,6 @@ public class TencentBlobProvider : IBlobProvider } var configuration = await GetBlobConfiguration(); - var blobProviderConfig = Configuration.GetSection($"BlobManagement:{ProviderName}"); - // See: https://cloud.tencent.com/document/product/436/47238 var preSignatureStruct = new PreSignatureStruct { @@ -119,16 +143,12 @@ public class TencentBlobProvider : IBlobProvider key = objectName, //对象键 httpMethod = "GET", //HTTP 请求方法 isHttps = true, //生成 HTTPS 请求 URL - signDurationSecond = blobProviderConfig.GetValue("SignDurationSecond", 600), //请求签名时间 + signDurationSecond = expiration.TotalSeconds.To(), //请求签名时间 headers = null, //签名中需要校验的 header queryParameters = null //签名中需要校验的 URL 中请求参数 }; - var downloadUrl = client.GenerateSignURL(preSignatureStruct); - - var httpClient = HttpClientFactory.CreateTencentHttpClient(); - - return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + return client.GenerateSignURL(preSignatureStruct); } public virtual Task CreateFolderAsync( @@ -144,7 +164,8 @@ public class TencentBlobProvider : IBlobProvider public async virtual Task UploadBlobAsync( string containerName, string blobName, - Stream content, + Stream content, + string? contentType = null, CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); @@ -153,7 +174,13 @@ public class TencentBlobProvider : IBlobProvider CreateBucketIfNotExists(client, bucket); - client.PutObject(new PutObjectRequest(bucket, objectName, content)); + var putObjectRequest = new PutObjectRequest(bucket, objectName, content); + if (!contentType.IsNullOrWhiteSpace()) + { + putObjectRequest.SetRequestHeader("Content-Type", contentType); + } + + client.PutObject(putObjectRequest); } protected async virtual Task CreateClientAsync() diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj index 5537e2c55..e04cb7fee 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj +++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj @@ -151,6 +151,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs index 287123617..fdd8aee01 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs @@ -1,3 +1,4 @@ +using LINGYUN.Abp.BlobManagement.MimeCheck; using LINGYUN.Abp.Exporter.MiniSoftware; using LINGYUN.Abp.Notifications.Calendar; @@ -130,6 +131,8 @@ namespace LY.MicroService.Applications.Single; typeof(AbpBlobManagementMinioModule), // 对象存储模块 腾讯云 typeof(AbpBlobManagementTencentModule), + // 对象存储模块 MimeCheck扩展 + typeof(AbpBlobManagementMimeCheckModule), // 对象存储模块 应用服务 typeof(AbpBlobManagementDomainModule), // 对象存储模块 控制器 From 6d725513a8c869dea8eea524e80ea5a3c60fa85e Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 May 2026 09:34:03 +0800 Subject: [PATCH 2/4] feat(aliyun): Optimize the integration module of Alibaba Cloud - Use Alibaba Cloud v2 SDK --- Directory.Packages.props | 4 + .../LINGYUN.Abp.Aliyun.csproj | 2 +- .../LINGYUN/Abp/Aliyun/AcsClientFactory.cs | 34 ----- .../AliyunBasicSessionCredentialsCacheItem.cs | 11 +- .../LINGYUN/Abp/Aliyun/AliyunClientFactory.cs | 97 +++++++++----- .../LINGYUN/Abp/Aliyun/IAcsClientFactory.cs | 14 --- .../Aliyun/Settings/AliyunSettingProvider.cs | 2 +- .../LINGYUN.Abp.BlobStoring.Aliyun.csproj | 2 +- .../BlobStoring/Aliyun/AliyunBlobProvider.cs | 109 ++++++++++------ .../Aliyun/AliyunBlobProviderConfiguration.cs | 19 ++- .../AliyunBlobProviderConfigurationNames.cs | 8 ++ .../BlobStoring/Aliyun/IOssClientFactory.cs | 6 +- .../BlobStoring/Aliyun/OssClientFactory.cs | 86 +++++++++---- .../LINGYUN.Abp.Sms.Aliyun.csproj | 2 + .../LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs | 119 +++++++----------- .../Abp/Sms/Aliyun/DypnsClientFactory.cs | 41 ++++++ .../Abp/Sms/Aliyun/DysmsClientFactory.cs | 38 ++++++ .../Abp/Sms/Aliyun/IDypnsClientFactory.cs | 13 ++ .../Abp/Sms/Aliyun/IDysmsClientFactory.cs | 13 ++ 19 files changed, 390 insertions(+), 230 deletions(-) delete mode 100644 aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs delete mode 100644 aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs create mode 100644 aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs create mode 100644 aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs create mode 100644 aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs create mode 100644 aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index b9d54b3e0..7ef269c33 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -333,6 +333,10 @@ + + + + diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj index c9604faa5..fd6b3232a 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj @@ -25,7 +25,7 @@ - + diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs deleted file mode 100644 index 4401aee25..000000000 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AcsClientFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Aliyun.Acs.Core; -using Aliyun.Acs.Core.Auth; -using Aliyun.Acs.Core.Profile; -using LINGYUN.Abp.Aliyun.Features; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Features; -using Volo.Abp.Settings; - -namespace LINGYUN.Abp.Aliyun; - -[RequiresFeature(AliyunFeatureNames.Enable)] -public class AcsClientFactory : AliyunClientFactory, IAcsClientFactory, ITransientDependency -{ - public AcsClientFactory( - ISettingProvider settingProvider, - IDistributedCache cache) - : base(settingProvider, cache) - { - } - - protected override IAcsClient GetClient(string regionId, string accessKeyId, string accessKeySecret) - { - return new DefaultAcsClient( - DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret)); - } - - protected override IAcsClient GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken) - { - var profile = DefaultProfile.GetProfile(regionId); - var credentials = new BasicSessionCredentials(accessKeyId, accessKeySecret, securityToken); - return new DefaultAcsClient(profile, credentials); - } -} diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs index 41630d221..8533d6ed0 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunBasicSessionCredentialsCacheItem.cs @@ -5,26 +5,21 @@ namespace LINGYUN.Abp.Aliyun; [Serializable] public class AliyunBasicSessionCredentialsCacheItem { - private readonly static string _cacheKey; - public static string CacheKey => _cacheKey; public string AccessKeyId { get; set; } public string AccessKeySecret { get; set; } public string SecurityToken { get; set; } - - static AliyunBasicSessionCredentialsCacheItem() - { - _cacheKey = Guid.NewGuid().ToString("N"); - } + public DateTime? Expiration { get; set; } public AliyunBasicSessionCredentialsCacheItem() { } - public AliyunBasicSessionCredentialsCacheItem(string accessKeyId, string accessKeySecret, string securityToken) + public AliyunBasicSessionCredentialsCacheItem(string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null) { AccessKeyId = accessKeyId; AccessKeySecret = accessKeySecret; SecurityToken = securityToken; + Expiration = expiration; } } diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs index df800cb11..f87b007ca 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs @@ -1,14 +1,12 @@ -using Aliyun.Acs.Core; -using Aliyun.Acs.Core.Auth.Sts; -using Aliyun.Acs.Core.Http; -using Aliyun.Acs.Core.Profile; -using LINGYUN.Abp.Aliyun.Settings; +using LINGYUN.Abp.Aliyun.Settings; using Microsoft.Extensions.Caching.Distributed; using System; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Caching; using Volo.Abp.Settings; +using OpenApiConfig = AlibabaCloud.OpenApiClient.Models.Config; +using StsClient = AlibabaCloud.SDK.Sts20150401.Client; namespace LINGYUN.Abp.Aliyun; @@ -42,19 +40,33 @@ public abstract class AliyunClientFactory { var cacheItem = await GetCacheItemAsync(accessKey, accessKeySecret, regionId); - return GetSecurityTokenClient(regionId, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken); + return GetSecurityTokenClient( + regionId, + cacheItem.AccessKeyId, + cacheItem.AccessKeySecret, + cacheItem.SecurityToken, + cacheItem.Expiration); } return GetClient(regionId, accessKey, accessKeySecret); } - protected abstract TClient GetClient(string regionId, string accessKeyId, string accessKeySecret); + protected abstract TClient GetClient( + string regionId, + string accessKeyId, + string accessKeySecret); - protected abstract TClient GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken); + protected abstract TClient GetSecurityTokenClient( + string regionId, + string accessKeyId, + string accessKeySecret, + string securityToken, + DateTime? expiration = null); protected async virtual Task GetCacheItemAsync(string accessKeyId, string accessKeySecret, string regionId) { - var cacheItem = await Cache.GetAsync(AliyunBasicSessionCredentialsCacheItem.CacheKey); + var cacheKey = $"{accessKeyId}:{accessKeySecret}".ToMd5(); + var cacheItem = await Cache.GetAsync(cacheKey); if (cacheItem == null) { var roleArn = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.RamRoleArn); @@ -64,30 +76,41 @@ public abstract class AliyunClientFactory var policy = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Authorization.Policy); var durationSeconds = await SettingProvider.GetAsync(AliyunSettingNames.Authorization.DurationSeconds, 3000); - var profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); - var request = new AssumeRoleRequest - { - AcceptFormat = FormatType.JSON, - RoleArn = roleArn, - RoleSessionName = roleSession, - DurationSeconds = durationSeconds, - Policy = policy.IsNullOrWhiteSpace() ? null : policy - }; + var stsClient = new StsClient( + new OpenApiConfig + { + RegionId = regionId, + AccessKeyId = accessKeyId, + AccessKeySecret = accessKeySecret, + }); - var client = new DefaultAcsClient(profile); - var response = client.GetAcsResponse(request); + var assumeRoleResponse = await stsClient.AssumeRoleAsync( + new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest + { + RoleArn = roleArn, + RoleSessionName = roleSession, + DurationSeconds = durationSeconds, + Policy = policy.IsNullOrWhiteSpace() ? null : policy, + }); cacheItem = new AliyunBasicSessionCredentialsCacheItem( - response.Credentials.AccessKeyId, - response.Credentials.AccessKeySecret, - response.Credentials.SecurityToken); + assumeRoleResponse.Body.Credentials.AccessKeyId, + assumeRoleResponse.Body.Credentials.AccessKeySecret, + assumeRoleResponse.Body.Credentials.SecurityToken); + + var expirationTimeSpan = TimeSpan.FromSeconds(durationSeconds - 10); + if (DateTime.TryParse(assumeRoleResponse.Body.Credentials.Expiration, out var expiration)) + { + cacheItem.Expiration = expiration; + expirationTimeSpan = new TimeSpan(expiration.AddSeconds(-10).Ticks); + } await Cache.SetAsync( - AliyunBasicSessionCredentialsCacheItem.CacheKey, + cacheKey, cacheItem, new DistributedCacheEntryOptions { - AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds - 10) + AbsoluteExpirationRelativeToNow = expirationTimeSpan, }); } @@ -122,13 +145,29 @@ public abstract class AliyunClientFactory : AliyunClien { var cacheItem = await GetCacheItemAsync(accessKey, accessKeySecret, regionId); - return GetSecurityTokenClient(configuration, regionId, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken); + return GetSecurityTokenClient( + configuration, + regionId, + cacheItem.AccessKeyId, + cacheItem.AccessKeySecret, + cacheItem.SecurityToken, + cacheItem.Expiration); } return GetClient(configuration, regionId, accessKey, accessKeySecret); } - protected abstract TClient GetClient(TConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret); - - protected abstract TClient GetSecurityTokenClient(TConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret, string securityToken); + protected abstract TClient GetClient( + TConfiguration configuration, + string regionId, + string accessKeyId, + string accessKeySecret); + + protected abstract TClient GetSecurityTokenClient( + TConfiguration configuration, + string regionId, + string accessKeyId, + string accessKeySecret, + string securityToken, + DateTime? expiration = null); } diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs deleted file mode 100644 index bf81b77c1..000000000 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/IAcsClientFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Aliyun.Acs.Core; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Aliyun; - -public interface IAcsClientFactory -{ - /// - /// 构造一个通用的Acs客户端调用 - /// 通过CommonRequest调用可以不需要集成其他SDK包 - /// - /// - Task CreateAsync(); -} diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs index 5e5f98805..955594a6a 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/Settings/AliyunSettingProvider.cs @@ -79,7 +79,7 @@ public class AliyunSettingProvider : SettingDefinitionProvider TenantSettingValueProvider.ProviderName), new SettingDefinition( AliyunSettingNames.Authorization.RegionId, - defaultValue: "oss-cn-hangzhou", + defaultValue: "cn-hangzhou", displayName: L("DisplayName:RegionId"), description: L("Description:RegionId"), isVisibleToClients: false diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj index 59822d893..7f8460f95 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj @@ -15,7 +15,7 @@ - + diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs index 0064ce2cc..af8bd175d 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs @@ -1,29 +1,33 @@ -using Aliyun.OSS; +using AlibabaCloud.OSS.V2; +using AlibabaCloud.OSS.V2.Models; using LINGYUN.Abp.Aliyun.Features; using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.BlobStoring; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; +using Volo.Abp.Timing; namespace LINGYUN.Abp.BlobStoring.Aliyun; [RequiresFeature(AliyunFeatureNames.BlobStoring.Enable)] public class AliyunBlobProvider : BlobProviderBase, ITransientDependency { + protected IClock Clock { get; } protected IOssClientFactory OssClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; } protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; } public AliyunBlobProvider( + IClock clock, IOssClientFactory ossClientFactory, IHttpClientFactory httpClientFactory, IAliyunBlobNameCalculator aliyunBlobNameCalculator) { + Clock = clock; OssClientFactory = ossClientFactory; HttpClientFactory = httpClientFactory; AliyunBlobNameCalculator = aliyunBlobNameCalculator; @@ -36,7 +40,14 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency if (await BlobExistsAsync(ossClient, args, blobName)) { - return ossClient.DeleteObject(GetBucketName(args), blobName).DeleteMarker; + var deleteObjectRes = await ossClient.DeleteObjectAsync( + new DeleteObjectRequest + { + Bucket = GetBucketName(args), + Key = blobName, + }, + cancellationToken: args.CancellationToken); + return deleteObjectRes.DeleteMarker == true; } return false; @@ -60,11 +71,27 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency return null; } - var downloadUri = ossClient.GeneratePresignedUri(GetBucketName(args), blobName); - - var httpClient = HttpClientFactory.CreateAliyunHttpClient(); - - return await httpClient.GetStreamAsync(downloadUri, args.CancellationToken); + var result = await ossClient.GetObjectAsync( + new GetObjectRequest + { + Bucket = GetBucketName(args), + Key = blobName, + }); + return result.Body; + + // TODO: 阿里云sdk预签名不可用[2026/05/23] + //var configuration = args.Configuration.GetAliyunConfiguration(); + //var presignResult = ossClient.Presign( + // new GetObjectRequest + // { + // Bucket = GetBucketName(args), + // Key = blobName, + // }, + // Clock.Now.AddSeconds(configuration.PresignedGetExpirySeconds)); + + //var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + + //return await httpClient.GetStreamAsync(presignResult.Url, args.CancellationToken); } public override async Task SaveAsync(BlobProviderSaveArgs args) @@ -76,7 +103,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency // 先检查Bucket if (configuration.CreateBucketIfNotExists) { - await CreateBucketIfNotExists(ossClient, args, configuration.CreateBucketReferer); + await CreateBucketIfNotExists(ossClient, args, configuration.CreateBucketAcl); } var bucketName = GetBucketName(args); @@ -92,60 +119,70 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency else { // 删除原文件 - ossClient.DeleteObject(bucketName, blobName); + await ossClient.DeleteObjectAsync( + new DeleteObjectRequest + { + Bucket = bucketName, + Key = blobName, + }, + cancellationToken: args.CancellationToken); } } // 保存 - ossClient.PutObject(bucketName, blobName, args.BlobStream); + await ossClient.PutObjectAsync( + new PutObjectRequest + { + Bucket = bucketName, + Key = blobName, + Body = args.BlobStream, + }, + cancellationToken: args.CancellationToken); } - protected async virtual Task GetOssClientAsync(BlobProviderArgs args) + protected async virtual Task GetOssClientAsync(BlobProviderArgs args) { - var ossClient = await OssClientFactory.CreateAsync(); - return ossClient; + return await OssClientFactory.CreateAsync(); } - protected async virtual Task CreateBucketIfNotExists(IOss ossClient, BlobProviderArgs args, IList refererList = null) + protected async virtual Task CreateBucketIfNotExists(Client ossClient, BlobProviderArgs args, BucketAclType? bucketAcl = null) { - if (! await BucketExistsAsync(ossClient, args)) + if (!await BucketExistsAsync(ossClient, args)) { var bucketName = GetBucketName(args); - var request = new CreateBucketRequest(bucketName) - { - //设置存储空间访问权限ACL。 - ACL = CannedAccessControlList.PublicReadWrite, - //设置数据容灾类型。 - DataRedundancyType = DataRedundancyType.ZRS - }; - - ossClient.CreateBucket(request); + await ossClient.PutBucketAsync( + new PutBucketRequest + { + Bucket = bucketName, + }, + cancellationToken: args.CancellationToken); - if (refererList != null && refererList.Count > 0) + if (bucketAcl.HasValue) { - var srq = new SetBucketRefererRequest(bucketName, refererList); - ossClient.SetBucketReferer(srq); + await ossClient.PutBucketAclAsync( + new PutBucketAclRequest + { + Bucket = bucketName, + Acl = bucketAcl.Value.GetString(), + }, + cancellationToken: args.CancellationToken); } } } - private async Task BlobExistsAsync(IOss ossClient, BlobProviderArgs args, string blobName) + private async Task BlobExistsAsync(Client ossClient, BlobProviderArgs args, string blobName) { var bucketExists = await BucketExistsAsync(ossClient, args); if (bucketExists) { - var objectExists = ossClient.DoesObjectExist(GetBucketName(args), blobName); - - return objectExists; + return await ossClient.IsObjectExistAsync(GetBucketName(args), blobName, cancellationToken: args.CancellationToken); } return false; } - private Task BucketExistsAsync(IOss ossClient, BlobProviderArgs args) + private async Task BucketExistsAsync(Client ossClient, BlobProviderArgs args) { - var bucketExists = ossClient.DoesBucketExist(GetBucketName(args)); - - return Task.FromResult(bucketExists); + return await ossClient.IsBucketExistAsync(GetBucketName(args), args.CancellationToken); } private static string GetBucketName(BlobProviderArgs args) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs index d7efa95a4..2401174e1 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using AlibabaCloud.OSS.V2.Models; +using System.Collections.Generic; using Volo.Abp.BlobStoring; namespace LINGYUN.Abp.BlobStoring.Aliyun; @@ -14,6 +15,13 @@ public class AliyunBlobProviderConfiguration set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, value); } /// + /// 跳过服务器证书验证 + /// + public bool InsecureSkipVerify { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, false); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, value); + } + /// /// 命名空间不存在是否创建 /// public bool CreateBucketIfNotExists @@ -22,11 +30,18 @@ public class AliyunBlobProviderConfiguration set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value); } /// + /// 创建命名空间时的Acl + /// + public BucketAclType? CreateBucketAcl { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketAcl); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketAcl, value); + } + /// /// 创建命名空间时防盗链列表 /// public List CreateBucketReferer { - get => _containerConfiguration.GetConfiguration>(AliyunBlobProviderConfigurationNames.CreateBucketReferer); + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketReferer, new List()); set { if (value == null) diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs index e3e0c9221..d0bc9f7a7 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs @@ -11,6 +11,10 @@ public static class AliyunBlobProviderConfigurationNames /// public const string BucketName = "Aliyun:OSS:BucketName"; /// + /// 跳过服务器证书验证 + /// + public const string InsecureSkipVerify = "Aliyun:OSS:InsecureSkipVerify"; + /// /// 命名空间不存在是否创建 /// public const string CreateBucketIfNotExists = "Aliyun:OSS:CreateBucketIfNotExists"; @@ -19,6 +23,10 @@ public static class AliyunBlobProviderConfigurationNames /// public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer"; /// + /// 创建命名空间时的Acl + /// + public const string CreateBucketAcl = "Aliyun:OSS:CreateBucketAcl"; + /// /// 生成预签名Uri的过期时间(s) /// public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds"; diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs index 6a4ccd6fa..7dea3e3d5 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs @@ -1,4 +1,4 @@ -using Aliyun.OSS; +using AlibabaCloud.OSS.V2; using System.Threading.Tasks; namespace LINGYUN.Abp.BlobStoring.Aliyun; @@ -9,11 +9,11 @@ public interface IOssClientFactory /// 构建Oss客户端 /// /// - Task CreateAsync(); + Task CreateAsync(); /// /// 构建Oss客户端 /// /// /// - Task CreateAsync(AliyunBlobProviderConfiguration configuration); + Task CreateAsync(AliyunBlobProviderConfiguration configuration); } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs index 9388454f0..6fc4da256 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs @@ -1,12 +1,14 @@ -using Aliyun.OSS; +using AlibabaCloud.OSS.V2; +using AlibabaCloud.OSS.V2.Credentials; using LINGYUN.Abp.Aliyun; +using System; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Settings; namespace LINGYUN.Abp.BlobStoring.Aliyun; -public class OssClientFactory : AliyunClientFactory, IOssClientFactory, ITransientDependency +public class OssClientFactory : AliyunClientFactory, IOssClientFactory, ITransientDependency { public OssClientFactory( ISettingProvider settingProvider, @@ -22,12 +24,22 @@ public class OssClientFactory : AliyunClientFactory /// /// - protected override IOss GetClient(string regionId, string accessKeyId, string accessKeySecret) + protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret) { - return new OssClient( - regionId, - accessKeyId, - accessKeySecret); + + return new Client( + new Configuration + { + Region = regionId, + CredentialsProvider = new CredentialsProviderFunc(() => + { + return new Credentials(accessKeyId, accessKeySecret); + }), + }); + //return new OssClient( + // regionId, + // accessKeyId, + // accessKeySecret); } /// @@ -38,12 +50,18 @@ public class OssClientFactory : AliyunClientFactory /// /// - protected override IOss GetClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret) + protected override Client GetClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret) { - return new OssClient( - regionId, - accessKeyId, - accessKeySecret); + return new Client( + new Configuration + { + Region = regionId, + CredentialsProvider = new CredentialsProviderFunc(() => + { + return new Credentials(accessKeyId, accessKeySecret); + }), + InsecureSkipVerify = configuration.InsecureSkipVerify, + }); } /// @@ -54,13 +72,22 @@ public class OssClientFactory : AliyunClientFactory /// /// - protected override IOss GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken) + protected override Client GetSecurityTokenClient( + string regionId, + string accessKeyId, + string accessKeySecret, + string securityToken, + DateTime? expiration = null) { - return new OssClient( - regionId, - accessKeyId, - accessKeySecret, - securityToken); + return new Client( + new Configuration + { + Region = regionId, + CredentialsProvider = new CredentialsProviderFunc(() => + { + return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration); + }), + }); } /// /// 通过用户安全令牌构建Oss客户端 @@ -71,12 +98,23 @@ public class OssClientFactory : AliyunClientFactory /// /// - protected override IOss GetSecurityTokenClient(AliyunBlobProviderConfiguration configuration, string regionId, string accessKeyId, string accessKeySecret, string securityToken) + protected override Client GetSecurityTokenClient( + AliyunBlobProviderConfiguration configuration, + string regionId, + string accessKeyId, + string accessKeySecret, + string securityToken, + DateTime? expiration = null) { - return new OssClient( - regionId, - accessKeyId, - accessKeySecret, - securityToken); + return new Client( + new Configuration + { + Region = regionId, + CredentialsProvider = new CredentialsProviderFunc(() => + { + return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration); + }), + InsecureSkipVerify = configuration.InsecureSkipVerify, + }); } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj index a2d642c68..aabe2f950 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj @@ -21,6 +21,8 @@ + + diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs index 2e749fb0e..745422f5f 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs @@ -1,14 +1,11 @@ -using Aliyun.Acs.Core; -using Aliyun.Acs.Core.Exceptions; -using Aliyun.Acs.Core.Http; -using LINGYUN.Abp.Aliyun; +using AlibabaCloud.SDK.Dypnsapi20170525.Models; +using AlibabaCloud.SDK.Dysmsapi20170525.Models; using LINGYUN.Abp.Aliyun.Features; using LINGYUN.Abp.Aliyun.Settings; using LINGYUN.Abp.Features.LimitValidation; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; -using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -30,17 +27,20 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender protected IJsonSerializer JsonSerializer { get; } protected ISettingProvider SettingProvider { get; } protected IServiceProvider ServiceProvider { get; } - protected IAcsClientFactory AcsClientFactory { get; } + protected IDypnsClientFactory DypnsClientFactory { get; } + protected IDysmsClientFactory DysmsClientFactory { get; } public AliyunSmsSender( IJsonSerializer jsonSerializer, ISettingProvider settingProvider, IServiceProvider serviceProvider, - IAcsClientFactory acsClientFactory) + IDypnsClientFactory dypnsClientFactory, + IDysmsClientFactory dysmsClientFactory) { JsonSerializer = jsonSerializer; SettingProvider = settingProvider; ServiceProvider = serviceProvider; - AcsClientFactory = acsClientFactory; + DypnsClientFactory = dypnsClientFactory; + DysmsClientFactory = dysmsClientFactory; } [RequiresLimitFeature( @@ -74,41 +74,23 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender Check.NotNullOrWhiteSpace(action, AliyunSettingNames.Sms.ActionName); Check.NotNullOrWhiteSpace(version, AliyunSettingNames.Sms.Version); - var request = new CommonRequest - { - Method = MethodType.POST, - Domain = domain, - Action = action, - Version = version - }; + var request = new SendSmsRequest(); + await TryAddTemplateCodeAsync(request, smsMessage); await TryAddSignNameAsync(request, smsMessage); await TryAddSendPhoneAsync(request, smsMessage); - TryAddTemplateParam(request, smsMessage); - try + + var dysmsClient = await DysmsClientFactory.CreateAsync(); + var response = await dysmsClient.SendSmsAsync(request); + if (!string.Equals(response.Body.Code, "OK", StringComparison.CurrentCultureIgnoreCase)) { - var client = await AcsClientFactory.CreateAsync(); - var response = client.GetCommonResponse(request); - var responseContent = Encoding.Default.GetString(response.HttpResponse.Content); - var aliyunResponse = JsonSerializer.Deserialize(responseContent); - if (!aliyunResponse.IsSuccess()) + if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient)) { - if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient)) - { - throw new UserFriendlyException(aliyunResponse.Code, aliyunResponse.Message); - } - throw new AliyunSmsException(aliyunResponse.Code, $"Text message sending failed, code:{aliyunResponse.Code}, message:{aliyunResponse.Message}!"); + throw new UserFriendlyException(response.Body.Code, response.Body.Message); } - } - catch(ServerException se) - { - throw new AliyunSmsException(se.ErrorCode, $"Sending text messages to aliyun server is abnormal,type: {se.ErrorType}, error: {se.ErrorMessage}"); - } - catch(ClientException ce) - { - throw new AliyunSmsException(ce.ErrorCode, $"A client exception occurred in sending SMS messages,type: {ce.ErrorType}, error: {ce.ErrorMessage}"); + throw new AliyunSmsException(response.Body.Code, $"Text message sending failed, code:{response.Body.Code}, message:{response.Body.Message}!"); } } @@ -132,75 +114,58 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender Check.NotNullOrWhiteSpace(signName, AliyunSettingNames.SmsVerifyCode.DefaultSignName); Check.NotNullOrWhiteSpace(templateCode, AliyunSettingNames.SmsVerifyCode.DefaultTemplateCode); - var request = new CommonRequest + var dypnsClient = await DypnsClientFactory.CreateAsync(); + + var request = new SendSmsVerifyCodeRequest { - Domain = domain, - Version = version, - Product = "Dypnsapi", - Method = MethodType.POST, - Action = "SendSmsVerifyCode", + PhoneNumber = message.PhoneNumber, + SignName = signName, + TemplateCode = templateCode, + TemplateParam = JsonSerializer.Serialize(message.TemplateParam), }; - request.AddBodyParameters("PhoneNumber", message.PhoneNumber); - request.AddBodyParameters("SignName", signName); - request.AddBodyParameters("TemplateCode", templateCode); - request.AddBodyParameters("TemplateParam", JsonSerializer.Serialize(message.TemplateParam)); - try + var response = await dypnsClient.SendSmsVerifyCodeAsync(request); + if (response.Body.Success == false) { - var client = await AcsClientFactory.CreateAsync(); - var response = client.GetCommonResponse(request); - var responseContent = Encoding.Default.GetString(response.HttpResponse.Content); - var aliyunResponse = JsonSerializer.Deserialize(responseContent); - if (!aliyunResponse.Success) + if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient)) { - if (await SettingProvider.IsTrueAsync(AliyunSettingNames.Sms.VisableErrorToClient)) - { - throw new UserFriendlyException(aliyunResponse.Code, aliyunResponse.Message); - } - throw new AliyunSmsException(aliyunResponse.Code, $"Text message sending failed, code:{aliyunResponse.Code}, message:{aliyunResponse.Message}!"); + throw new UserFriendlyException(response.Body.Code, response.Body.Message); } - } - catch (ServerException se) - { - throw new AliyunSmsException(se.ErrorCode, $"Sending text messages to aliyun server is abnormal,type: {se.ErrorType}, error: {se.ErrorMessage}"); - } - catch (ClientException ce) - { - throw new AliyunSmsException(ce.ErrorCode, $"A client exception occurred in sending SMS messages,type: {ce.ErrorType}, error: {ce.ErrorMessage}"); + throw new AliyunSmsException(response.Body.Code, $"Text message sending failed, code:{response.Body.Code}, message:{response.Body.Message}!"); } } - private async Task TryAddTemplateCodeAsync(CommonRequest request, SmsMessage smsMessage) + private async Task TryAddTemplateCodeAsync(SendSmsRequest request, SmsMessage smsMessage) { - if (smsMessage.Properties.TryGetValue("TemplateCode", out object template) && template != null) + if (smsMessage.Properties.TryGetValue("TemplateCode", out var template) && template != null) { - request.AddQueryParameters("TemplateCode", template.ToString()); + request.TemplateCode = template.ToString(); smsMessage.Properties.Remove("TemplateCode"); } else { var defaultTemplateCode = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.DefaultTemplateCode); Check.NotNullOrWhiteSpace(defaultTemplateCode, "TemplateCode"); - request.AddQueryParameters("TemplateCode", defaultTemplateCode); + request.TemplateCode = defaultTemplateCode; } } - private async Task TryAddSignNameAsync(CommonRequest request, SmsMessage smsMessage) + private async Task TryAddSignNameAsync(SendSmsRequest request, SmsMessage smsMessage) { - if (smsMessage.Properties.TryGetValue("SignName", out object signName) && signName != null) + if (smsMessage.Properties.TryGetValue("SignName", out var signName) && signName != null) { - request.AddQueryParameters("SignName", signName.ToString()); + request.SignName = signName.ToString(); smsMessage.Properties.Remove("SignName"); } else { var defaultSignName = await SettingProvider.GetOrNullAsync(AliyunSettingNames.Sms.DefaultSignName); Check.NotNullOrWhiteSpace(defaultSignName, "SignName"); - request.AddQueryParameters("SignName", defaultSignName); + request.SignName = defaultSignName; } } - private async Task TryAddSendPhoneAsync(CommonRequest request, SmsMessage smsMessage) + private async Task TryAddSendPhoneAsync(SendSmsRequest request, SmsMessage smsMessage) { if (smsMessage.PhoneNumber.IsNullOrWhiteSpace()) { @@ -210,20 +175,20 @@ public class AliyunSmsSender : ISmsSender, IAliyunSmsVerifyCodeSender defaultPhoneNumber, AliyunSettingNames.Sms.DefaultPhoneNumber, maxLength: 11, minLength: 11); - request.AddQueryParameters("PhoneNumbers", defaultPhoneNumber); + request.PhoneNumbers = defaultPhoneNumber; } else { - request.AddQueryParameters("PhoneNumbers", smsMessage.PhoneNumber); + request.PhoneNumbers = smsMessage.PhoneNumber; } } - private void TryAddTemplateParam(CommonRequest request, SmsMessage smsMessage) + private void TryAddTemplateParam(SendSmsRequest request, SmsMessage smsMessage) { if (smsMessage.Properties.Any()) { var queryParamJson = JsonSerializer.Serialize(smsMessage.Properties); - request.AddQueryParameters("TemplateParam", queryParamJson); + request.TemplateParam = queryParamJson; } } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs new file mode 100644 index 000000000..3009b0f4e --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DypnsClientFactory.cs @@ -0,0 +1,41 @@ +using AlibabaCloud.SDK.Dypnsapi20170525; +using LINGYUN.Abp.Aliyun; +using System; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Sms.Aliyun; + +public class DypnsClientFactory : AliyunClientFactory, IDypnsClientFactory, ITransientDependency +{ + public DypnsClientFactory( + ISettingProvider settingProvider, + IDistributedCache cache) + : base(settingProvider, cache) + { + } + + protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret) + { + return new Client( + new AlibabaCloud.OpenApiClient.Models.Config + { + RegionId = regionId, + AccessKeyId = accessKeyId, + AccessKeySecret = accessKeySecret, + }); + } + + protected override Client GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null) + { + return new Client( + new AlibabaCloud.OpenApiClient.Models.Config + { + RegionId = regionId, + AccessKeyId = accessKeyId, + AccessKeySecret = accessKeySecret, + SecurityToken = securityToken, + }); + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs new file mode 100644 index 000000000..66aaff90e --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/DysmsClientFactory.cs @@ -0,0 +1,38 @@ +using AlibabaCloud.SDK.Dysmsapi20170525; +using LINGYUN.Abp.Aliyun; +using System; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Sms.Aliyun; + +public class DysmsClientFactory : AliyunClientFactory, IDysmsClientFactory, ITransientDependency +{ + public DysmsClientFactory(ISettingProvider settingProvider, IDistributedCache cache) : base(settingProvider, cache) + { + } + + protected override Client GetClient(string regionId, string accessKeyId, string accessKeySecret) + { + return new Client( + new AlibabaCloud.OpenApiClient.Models.Config + { + RegionId = regionId, + AccessKeyId = accessKeyId, + AccessKeySecret = accessKeySecret, + }); + } + + protected override Client GetSecurityTokenClient(string regionId, string accessKeyId, string accessKeySecret, string securityToken, DateTime? expiration = null) + { + return new Client( + new AlibabaCloud.OpenApiClient.Models.Config + { + RegionId = regionId, + AccessKeyId = accessKeyId, + AccessKeySecret = accessKeySecret, + SecurityToken = securityToken, + }); + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs new file mode 100644 index 000000000..21364c171 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDypnsClientFactory.cs @@ -0,0 +1,13 @@ +using AlibabaCloud.SDK.Dypnsapi20170525; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Sms.Aliyun; + +public interface IDypnsClientFactory +{ + /// + /// 构建短信认证客户端 + /// + /// + Task CreateAsync(); +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs new file mode 100644 index 000000000..b368f5066 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN/Abp/Sms/Aliyun/IDysmsClientFactory.cs @@ -0,0 +1,13 @@ +using AlibabaCloud.SDK.Dysmsapi20170525; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Sms.Aliyun; + +public interface IDysmsClientFactory +{ + /// + /// 构建短信客户端 + /// + /// + Task CreateAsync(); +} From 62aa3570518a96a35aebf864284096f8c03feecc Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 May 2026 09:41:32 +0800 Subject: [PATCH 3/4] feat: Optimize BlobManagement module --- Directory.Packages.props | 1 + .../Aliyun/AliyunBlobProvider.cs | 177 +++++++++++++----- ...ByKeyInput.cs => BlobDownloadByIdInput.cs} | 4 +- .../Dtos/BlobDownloadByNameInput.cs | 5 +- .../Abp/BlobManagement/IBlobAppService.cs | 8 +- .../BlobManagement/IPrivateBlobAppService.cs | 4 +- .../BlobManagement/IPublicBlobAppService.cs | 4 +- .../Integration/IBlobIntegrationService.cs | 2 +- .../Abp/BlobManagement/BlobAppService.cs | 25 ++- .../Abp/BlobManagement/BlobAppServiceBase.cs | 58 +++--- .../BlobWithoutContainerAppService.cs | 8 +- .../Integration/BlobIntegrationService.cs | 10 +- .../Abp/BlobManagement/BlobDownloadEto.cs | 8 +- .../LINGYUN/Abp/BlobManagement/Blob.cs | 42 +++-- .../Abp/BlobManagement/BlobDataSeeder.cs | 2 +- .../BlobManagement/BlobDownloadIncreaser.cs | 2 +- .../BlobDownloadKeyCacheItem.cs | 5 - .../LINGYUN/Abp/BlobManagement/BlobManager.cs | 31 +-- .../BlobManagement/BlobTempCleanupService.cs | 2 +- .../IBlobContainerRepository.cs | 1 + .../IBlobContainerRepositoryExtenssions.cs | 3 +- .../Abp/BlobManagement/IBlobProvider.cs | 4 +- .../EfCoreBlobContainerRepository.cs | 3 +- .../FileSystem/FileSystemBlobProvider.cs | 3 +- .../Abp/BlobManagement/BlobController.cs | 41 ++-- .../Integration/BlobIntegrationController.cs | 4 +- .../BlobManagement/PrivateBlobController.cs | 12 +- .../BlobManagement/PublicBlobController.cs | 12 +- .../BlobManagement/Minio/MinioBlobProvider.cs | 24 ++- .../Tencent/TencentBlobProvider.cs | 16 +- ...LY.MicroService.Applications.Single.csproj | 1 + 31 files changed, 343 insertions(+), 179 deletions(-) rename aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/{BlobDownloadByKeyInput.cs => BlobDownloadByIdInput.cs} (69%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7ef269c33..7c7d36b3d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -354,6 +354,7 @@ + diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs index 912320b7a..fa6af872e 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs @@ -1,4 +1,5 @@ -using Aliyun.OSS; +using AlibabaCloud.OSS.V2; +using AlibabaCloud.OSS.V2.Models; using LINGYUN.Abp.BlobStoring.Aliyun; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -50,23 +51,29 @@ public class AliyunBlobProvider : IBlobProvider { var client = await CreateClientAsync(); var bucket = NormalizeContainerName(name); + var configuration = GetBlobConfiguration(); - CreateBucketIfNotExists(client, bucket); + await CreateBucketIfNotExists(client, bucket, configuration.CreateBucketAcl, cancellationToken); } public async virtual Task DeleteContainerAsync( string name, CancellationToken cancellationToken = default) { - var client = await CreateClientAsync(); + var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(name); - if (!BucketExists(client, bucket)) + if (!await BucketExists(ossClient, bucket, cancellationToken)) { return; } - client.DeleteBucket(bucket); + await ossClient.DeleteBucketAsync( + new DeleteBucketRequest + { + Bucket = bucket, + }, + cancellationToken: cancellationToken); } public async virtual Task DeleteBlobAsync( @@ -74,13 +81,19 @@ public class AliyunBlobProvider : IBlobProvider string blobName, CancellationToken cancellationToken = default) { - var client = await CreateClientAsync(); + var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); var objectName = CalculateBlobName(blobName); - if (ObjectExists(client, bucket, objectName)) + if (await ObjectExists(ossClient, bucket, objectName, cancellationToken)) { - client.DeleteObject(bucket, objectName); + await ossClient.DeleteObjectAsync( + new DeleteObjectRequest + { + Bucket = bucket, + Key = objectName, + }, + cancellationToken: cancellationToken); } } @@ -98,39 +111,75 @@ public class AliyunBlobProvider : IBlobProvider string blobName, CancellationToken cancellationToken = default) { - var configuration = GetBlobConfiguration(); + var ossClient = await CreateClientAsync(); + var bucket = NormalizeContainerName(containerName); + var objectName = CalculateBlobName(blobName); - var downloadUrl = await GenerateDownloadUrlAsync( - containerName, - blobName, - TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), - cancellationToken); - if (downloadUrl.IsNullOrWhiteSpace()) + if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) { return null; } - var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + var result = await ossClient.GetObjectAsync( + new GetObjectRequest + { + Bucket = bucket, + Key = blobName, + }); + + return result.Body; - return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + // TODO: 阿里云sdk预签名不可用[2026/05/23] + //var configuration = GetBlobConfiguration(); + + //var downloadUrl = await GeneratePresignedUrlAsync( + // containerName, + // blobName, + // TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), + // cancellationToken: cancellationToken); + //if (downloadUrl.IsNullOrWhiteSpace()) + //{ + // return null; + //} + + //var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + + //return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); } - public async virtual Task GenerateDownloadUrlAsync( + public virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { - var client = await CreateClientAsync(); - var bucket = NormalizeContainerName(containerName); - var objectName = CalculateBlobName(blobName); - - if (!ObjectExists(client, bucket, objectName)) - { - return null; - } - - return client.GeneratePresignedUri(bucket, objectName, Clock.Now.Add(expiration)).ToString(); + // TODO: 阿里云sdk预签名不可用[2026/05/23] + return Task.FromResult(null); + //var ossClient = await CreateClientAsync(); + //var bucket = NormalizeContainerName(containerName); + //var objectName = CalculateBlobName(blobName); + + //if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) + //{ + // return null; + //} + + //var fileName = Path.GetFileName(blobName); + //var type = isAttachmentContent ? "attachment" : "inline"; + //var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " + + // $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}"; + + //var presignResult = ossClient.Presign( + // new GetObjectRequest + // { + // Bucket = bucket, + // Key = blobName, + // ResponseContentDisposition = disposition, + // }, + // Clock.Now.Add(expiration)); + + //return presignResult.Url; } public virtual Task CreateFolderAsync( @@ -150,49 +199,79 @@ public class AliyunBlobProvider : IBlobProvider string? contentType = null, CancellationToken cancellationToken = default) { - var client = await CreateClientAsync(); + var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); var objectName = CalculateBlobName(blobName); + var configuration = GetBlobConfiguration(); - CreateBucketIfNotExists(client, bucket); - - var objectMetadata = new ObjectMetadata(); - if (!contentType.IsNullOrWhiteSpace()) - { - objectMetadata.ContentType = contentType; - } - - client.PutObject(bucket, objectName, content, objectMetadata); + await CreateBucketIfNotExists(ossClient, bucket, configuration.CreateBucketAcl, cancellationToken); + + await ossClient.PutObjectAsync( + new PutObjectRequest + { + Bucket = bucket, + Key = objectName, + Body = content, + ContentType = contentType, + }, + cancellationToken: cancellationToken); } protected virtual AliyunBlobProviderConfiguration GetBlobConfiguration() { var configuration = ConfigurationProvider.Get(); - var blobConfiguration = configuration.GetAliyunConfiguration(); - return blobConfiguration; + return configuration.GetAliyunConfiguration(); } - protected async virtual Task CreateClientAsync() + protected async virtual Task CreateClientAsync() { - return await OssClientFactory.CreateAsync(); + var configuration = GetBlobConfiguration(); + return await OssClientFactory.CreateAsync(configuration); } - protected virtual void CreateBucketIfNotExists(IOss oss, string bucket) + protected async virtual Task CreateBucketIfNotExists( + Client ossClient, + string bucket, + BucketAclType? bucketAcl = null, + CancellationToken cancellationToken = default) { - if (!BucketExists(oss, bucket)) + if (!await BucketExists(ossClient, bucket, cancellationToken)) { - oss.CreateBucket(bucket); + await ossClient.PutBucketAsync( + new PutBucketRequest + { + Bucket = bucket, + }, + cancellationToken: cancellationToken); + + if (bucketAcl.HasValue) + { + await ossClient.PutBucketAclAsync( + new PutBucketAclRequest + { + Bucket = bucket, + Acl = bucketAcl.Value.GetString(), + }, + cancellationToken: cancellationToken); + } } } - protected virtual bool BucketExists(IOss oss, string bucket) + protected async virtual Task BucketExists( + Client ossClient, + string bucket, + CancellationToken cancellationToken = default) { - return oss.DoesBucketExist(bucket); + return await ossClient.IsBucketExistAsync(bucket, cancellationToken); } - protected virtual bool ObjectExists(IOss oss, string bucket, string objectName) + protected async virtual Task ObjectExists( + Client ossClient, + string bucket, + string objectName, + CancellationToken cancellationToken = default) { - return oss.DoesObjectExist(bucket, objectName); + return await ossClient.IsObjectExistAsync(bucket, objectName, cancellationToken: cancellationToken); } protected virtual string GetPrefixPath() diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs similarity index 69% rename from aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs rename to aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs index fb5da0020..4ac8d1f43 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs @@ -3,10 +3,10 @@ using System.ComponentModel.DataAnnotations; namespace LINGYUN.Abp.BlobManagement.Dtos; -public class BlobDownloadByKeyInput +public class BlobDownloadByIdInput { public Guid? TenantId { get; set; } [Required] - public string Key { get; set; } + public Guid Id { get; set; } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs index 77715bb07..06663bc67 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs @@ -1,10 +1,13 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Volo.Abp.Validation; namespace LINGYUN.Abp.BlobManagement.Dtos; public class BlobDownloadByNameInput { + public Guid? TenantId { get; set; } + [Required] [DynamicStringLength(typeof(BlobContainerConsts), nameof(BlobContainerConsts.MaxNameLength))] public string ContainerName { get; set; } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs index 2d4679569..4ed63da58 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs @@ -17,13 +17,15 @@ public interface IBlobAppService : IApplicationService Task DeleteAsync(Guid id); + Task GeneratePreviewUrlAsync(Guid id); + Task GenerateDownloadUrlAsync(Guid id); - Task DownloadAsync(BlobDownloadByKeyInput input); + Task DownloadAsync(BlobDownloadByIdInput input); - Task GetContentAsync(Guid id); + Task PreviewAsync(BlobDownloadByIdInput input); - Task GetContentByNameAsync(BlobDownloadByNameInput input); + Task DownloadByNameAsync(BlobDownloadByNameInput input); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs index 9d25f2704..7164d938a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs @@ -17,9 +17,9 @@ public interface IPrivateBlobAppService : IApplicationService Task DeleteAsync(Guid id); - Task GetContentAsync(Guid id); + Task DownloadAsync(Guid id); - Task GetContentByNameAsync(string name); + Task DownloadByNameAsync(string name); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs index dff540187..c381482c4 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs @@ -9,9 +9,9 @@ namespace LINGYUN.Abp.BlobManagement; public interface IPublicBlobAppService : IApplicationService { - Task GetContentAsync(Guid id); + Task DownloadAsync(Guid id); - Task GetContentByNameAsync(string name); + Task DownloadByNameAsync(string name); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs index 40a1fa835..ce105fec2 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs @@ -16,5 +16,5 @@ public interface IBlobIntegrationService : IApplicationService Task ExistsAsync(BlobFileGetByNameIntegrationDto input); - Task GetContentAsync(BlobFileGetByNameIntegrationDto input); + Task DownloadAsync(BlobFileGetByNameIntegrationDto input); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs index 689395ef4..ecafc86c1 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs @@ -47,11 +47,30 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService return await base.CreateFolderAsync(blobContainer, input); } - public async virtual Task GetContentByNameAsync(BlobDownloadByNameInput input) + public async virtual Task DownloadAsync(BlobDownloadByIdInput input) { - var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); + using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id)) + { + return await base.DownloadAsync(input.Id); + } + } + + public async virtual Task PreviewAsync(BlobDownloadByIdInput input) + { + using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id)) + { + return await base.DownloadAsync(input.Id); + } + } + + public async virtual Task DownloadByNameAsync(BlobDownloadByNameInput input) + { + using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id)) + { + var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName); - return await base.GetContentByNameAsync(blobContainer, input.BlobName); + return await base.DownloadByNameAsync(blobContainer, input.BlobName); + } } protected async override Task CheckGetPolicyAsync(string containerName, Blob blob) diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs index 683e6b1ce..638234cf8 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs @@ -48,43 +48,15 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService public async virtual Task GenerateDownloadUrlAsync(Guid id) { - var blob = await BlobRepository.GetAsync(id); - var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId); - - await CheckGetPolicyAsync(blob); - - var fallbackUrlPrefix = blob.TenantId.HasValue - ? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download/t/{blob.TenantId:N}" - : $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download"; - - var downloadUrl = await BlobManager.GenerateDownloadUrlAsync( - blobContainer, - blob, - fallbackUrlPrefix); - - return downloadUrl; + return await GenerateDownloadUrlAsync(id, "download"); } - public async virtual Task DownloadAsync(BlobDownloadByKeyInput input) + public async virtual Task GeneratePreviewUrlAsync(Guid id) { - using (CurrentTenant.Change(input.TenantId)) - { - var blob = await BlobManager.FindBlobByDownloadKeyAsync(input.Key); - if (blob == null) - { - return new RemoteStreamContent(Stream.Null, input.Key); - } - - // 临时下载链接一般用于临时预览与下载, 过期时间短, 不校验权限 - // await CheckGetPolicyAsync(blob); - - var stream = await BlobManager.DownloadBlobsync(blob); - - return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); - } + return await GenerateDownloadUrlAsync(id, "preview", isAttachmentContent: false); } - public async virtual Task GetContentAsync(Guid id) + public async virtual Task DownloadAsync(Guid id) { var blob = await BlobRepository.GetAsync(id); @@ -185,7 +157,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService return ObjectMapper.Map(blob); } - protected async virtual Task GetContentByNameAsync(BlobContainer blobContainer, string name) + protected async virtual Task DownloadByNameAsync(BlobContainer blobContainer, string name) { var blob = await FindBlobByNameAsync(blobContainer, name) ?? throw new BusinessException( @@ -200,6 +172,26 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); } + protected async virtual Task GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true) + { + var blob = await BlobRepository.GetAsync(id); + var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId); + + await CheckGetPolicyAsync(blob); + + var fallbackDownloadUrl = blob.TenantId.HasValue + ? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/t/{blob.TenantId:N}/{blob.Id:N}" + : $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/{blob.Id:N}"; + + var downloadUrl = await BlobManager.GenerateDownloadUrlAsync( + blobContainer, + blob, + fallbackDownloadUrl, + isAttachmentContent); + + return downloadUrl; + } + protected async virtual Task FindBlobByNameAsync(BlobContainer blobContainer, string name) { var blobName = HttpUtility.UrlDecode(name); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs index 66ed740d8..b4de50123 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs @@ -42,11 +42,11 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase return await base.CreateFolderAsync(blobContainer, input); } - public async virtual Task GetContentByNameAsync(string name) + public async virtual Task DownloadByNameAsync(string name) { - var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName); + var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), ContainerName); - return await base.GetContentByNameAsync(blobContainer, name); + return await base.DownloadByNameAsync(blobContainer, name); } public async virtual Task> GetListAsync(BlobGetPagedListWithoutContainerInput input) @@ -67,7 +67,7 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase protected async virtual Task GetOrCreateContainer() { - var blobContainer = await BlobContainerRepository.FindByNameAsync(ContainerName) + var blobContainer = await BlobManager.FindContainerAsync(ContainerName) ?? await BlobManager.CreateContainerAsync(ContainerName); return blobContainer; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs index 367da5ba9..b1c1cf24a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs @@ -28,7 +28,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic public async virtual Task CreateAsync(BlobFileCreateIntegrationDto input) { - var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); + var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName); // Example: /a/b/c/d/f.txt var blobName = HttpUtility.UrlDecode(input.BlobName); @@ -63,7 +63,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic public async virtual Task DeleteAsync(BlobFileGetByNameIntegrationDto input) { - var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); + var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName); var blob = await FindBlobByNameAsync(blobContainer, input.BlobName) ?? throw new BusinessException( @@ -90,12 +90,12 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic } } - public async virtual Task GetContentAsync(BlobFileGetByNameIntegrationDto input) + public async virtual Task DownloadAsync(BlobFileGetByNameIntegrationDto input) { try { - var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); - return await GetContentByNameAsync(blobContainer, input.BlobName); + var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName); + return await DownloadByNameAsync(blobContainer, input.BlobName); } catch (Exception ex) { diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs index 03d5d2d0a..f5fa5f2fb 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs @@ -6,6 +6,7 @@ namespace LINGYUN.Abp.BlobManagement; public class BlobDownloadEto : IMultiTenant { public Guid? TenantId { get; set; } + public string Provider { get; set; } public string ContainerName { get; set; } public string FullName { get; set; } public BlobDownloadEto() @@ -13,8 +14,13 @@ public class BlobDownloadEto : IMultiTenant } - public BlobDownloadEto(string containerName, string fullName, Guid? tenantId = null) + public BlobDownloadEto( + string provider, + string containerName, + string fullName, + Guid? tenantId = null) { + Provider = provider; ContainerName = containerName; FullName = fullName; TenantId = tenantId; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs index 51258d7be..162938e81 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Xml.Linq; using Volo.Abp; using Volo.Abp.Data; using Volo.Abp.Domain.Entities.Auditing; @@ -67,6 +68,14 @@ public class Blob : AuditedAggregateRoot, IMultiTenant this.SetDefaultsForExtraProperties(); } + public void MoveTo(Blob parentBlob) + { + ValidateSubBlob(parentBlob, Name); + ParentId = parentBlob.Id; + parentBlob.Blobs.Add(this); + parentBlob.CalculateSize(); + } + public void SetFullName(string fullName) { FullName = Check.NotNullOrWhiteSpace(fullName, nameof(fullName), BlobConsts.MaxFullNameLength); @@ -99,20 +108,7 @@ public class Blob : AuditedAggregateRoot, IMultiTenant BlobType type, long? size = null) { - if (Type != BlobType.Folder) - { - throw new BusinessException( - BlobManagementErrorCodes.Blob.NonFolderChildBlob, - "Sub-files/directories cannot be added to non-directory types!"); - } - - if (HasBlob(name)) - { - throw new BusinessException( - BlobManagementErrorCodes.Blob.NameAlreadyExists, - $"There is already a file/directory named {name} in the current directory!") - .WithData("Name", name); - } + ValidateSubBlob(this, name); var blob = new Blob( id, @@ -183,4 +179,22 @@ public class Blob : AuditedAggregateRoot, IMultiTenant return blobPath.EnsureEndsWith('/') + blobName.RemovePreFix("/"); } + + public static void ValidateSubBlob(Blob parentBlob, string subBlobName) + { + if (parentBlob.Type != BlobType.Folder) + { + throw new BusinessException( + BlobManagementErrorCodes.Blob.NonFolderChildBlob, + "Sub-files/directories cannot be added to non-directory types!"); + } + + if (parentBlob.HasBlob(subBlobName)) + { + throw new BusinessException( + BlobManagementErrorCodes.Blob.NameAlreadyExists, + $"There is already a file/directory named {subBlobName} in the current directory!") + .WithData("Name", subBlobName); + } + } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs index e83516959..703e0ac6c 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs @@ -41,7 +41,7 @@ public class BlobDataSeeder : IBlobDataSeeder, ITransientDependency { Logger.LogInformation("Seeding container {containerName}...", containerName); - if (await BlobContainerRepository.FindByNameAsync(containerName) == null) + if (await BlobManager.FindContainerAsync(containerName) == null) { await BlobManager.CreateContainerAsync(containerName); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs index bcf2da457..ca074afce 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs @@ -37,7 +37,7 @@ public class BlobDownloadIncreaser : return; } - var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.ContainerName); + var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.Provider, eventData.ContainerName); if (blobContainer == null) { return; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs index 7c001efa9..2f4914c91 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs @@ -16,9 +16,4 @@ public class BlobDownloadKeyCacheItem BlobId = blobId; Url = url; } - - public static string CalculateCacheKey(Guid blobId, string blobName) - { - return $"{blobId:N}{blobName.RemovePreFix("/")}"; - } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs index 68df23011..bf2c0a00d 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs @@ -62,11 +62,16 @@ public class BlobManager : DomainService _settingProvider = settingProvider; } - public async virtual Task CreateContainerAsync(string name) + public async virtual Task FindContainerAsync(string name) { Check.NotNullOrWhiteSpace(name, nameof(name)); - if (await _blobContainerRepository.FindByNameAsync(name) != null) + return await _blobContainerRepository.FindByNameAsync(GetBlobProvider(), name); + } + + public async virtual Task CreateContainerAsync(string name) + { + if (await FindContainerAsync(name) != null) { throw new BusinessException( BlobManagementErrorCodes.Container.NameAlreadyExists, @@ -81,7 +86,7 @@ public class BlobManager : DomainService await _blobProvider.CreateContainerAsync(blobContainer.Name, GetCancellationToken()); - blobContainer.SetProvider(_blobProvider.Name); + blobContainer.SetProvider(GetBlobProvider()); await _blobContainerRepository.InsertAsync(blobContainer); @@ -153,7 +158,7 @@ public class BlobManager : DomainService { await _blobProvider.CreateFolderAsync(blobContainer.Name, blob.FullName, GetCancellationToken()); - blob.SetProvider(_blobProvider.Name); + blob.SetProvider(GetBlobProvider()); } await _blobRepository.InsertAsync(blob); @@ -227,7 +232,7 @@ public class BlobManager : DomainService contentType, GetCancellationToken()); - blob.SetProvider(_blobProvider.Name); + blob.SetProvider(GetBlobProvider()); blob.SetFileInfo(contentType, stream.Length); return blob; @@ -292,7 +297,7 @@ public class BlobManager : DomainService } else { - blobContainer = await _blobContainerRepository.GetByNameAsync(containerIdOrName); + blobContainer = await _blobContainerRepository.GetByNameAsync(GetBlobProvider(), containerIdOrName); } if (parentId.HasValue) { @@ -338,9 +343,10 @@ public class BlobManager : DomainService public async virtual Task GenerateDownloadUrlAsync( BlobContainer blobContainer, Blob blob, - string fallbackUrlPrefix) + string fallbackDownloadUrl, + bool isAttachmentContent = true) { - var cacheKey = BlobDownloadKeyCacheItem.CalculateCacheKey(blob.Id, blob.FullName); + var cacheKey = $"{fallbackDownloadUrl.ToMd5()}"; var cacheItem = await _blobDownloadKeyCache.GetAsync(cacheKey); if (cacheItem == null) { @@ -348,16 +354,17 @@ public class BlobManager : DomainService BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds, BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds); - var downloadUrl = await _blobProvider.GenerateDownloadUrlAsync( + var downloadUrl = await _blobProvider.GeneratePresignedUrlAsync( blobContainer.Name, blob.FullName, TimeSpan.FromSeconds(downloadUrlExpirySeconds), + isAttachmentContent, GetCancellationToken()); if (downloadUrl.IsNullOrWhiteSpace()) { - // 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url前缀 - downloadUrl = fallbackUrlPrefix.EnsureEndsWith('/') + cacheKey; + // 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url + downloadUrl = fallbackDownloadUrl; } cacheItem = new BlobDownloadKeyCacheItem( @@ -403,7 +410,7 @@ public class BlobManager : DomainService downloadCacheItem.DownloadCount += 1; await _blobDownloadCache.SetAsync(downloadCacheKey, downloadCacheItem); - await _distributedEventBus.PublishAsync(new BlobDownloadEto(containerName, blobName, CurrentTenant.Id)); + await _distributedEventBus.PublishAsync(new BlobDownloadEto(GetBlobProvider(), containerName, blobName, CurrentTenant.Id)); } return blobStream; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs index c34de069f..8daa9c837 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs @@ -50,7 +50,7 @@ public class BlobTempCleanupService : ITransientDependency try { - var tempBlobContainer = await BlobContainerRepository.FindByNameAsync("temp"); + var tempBlobContainer = await BlobManager.FindContainerAsync("temp"); if (tempBlobContainer == null) { Logger.LogInformation($"No temp container found."); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs index f80d6d228..1dc197e48 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs @@ -14,6 +14,7 @@ public interface IBlobContainerRepository : IBasicRepository FindByNameAsync( + string provider, string name, CancellationToken cancellationToken = default); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs index b5795e01f..e967db5e0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs @@ -8,10 +8,11 @@ public static class IBlobContainerRepositoryExtenssions { public async static Task GetByNameAsync( this IBlobContainerRepository repository, + string provider, string name, CancellationToken cancellationToken = default) { - return await repository.FindByNameAsync(name, cancellationToken) + return await repository.FindByNameAsync(provider, name, cancellationToken) ?? throw new BusinessException( BlobManagementErrorCodes.Container.NameNotFound, $"There is no blob container named {name}!") diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs index 01a012279..61066f83b 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -22,10 +23,11 @@ public interface IBlobProvider string blobName, CancellationToken cancellationToken = default); - Task GenerateDownloadUrlAsync( + Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default); Task CreateFolderAsync( diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs index 145b304fe..aca9be141 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs @@ -30,11 +30,12 @@ public class EfCoreBlobContainerRepository : EfCoreRepository FindByNameAsync( + string provider, string name, CancellationToken cancellationToken = default) { return await (await GetQueryableAsync()) - .Where(x => x.Name == name) + .Where(x => x.Provider == provider && x.Name == name) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs index 8fcbc8b0f..097e4138b 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs @@ -99,10 +99,11 @@ public class FileSystemBlobProvider : IBlobProvider return Task.FromResult(File.OpenRead(fileName)); } - public virtual Task GenerateDownloadUrlAsync( + public virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { // 本地文件系统无需生成下载链接 diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs index 696f7d391..4cff719d0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.BlobManagement.Dtos; using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; using System; using System.Threading.Tasks; using Volo.Abp; @@ -50,16 +51,34 @@ public class BlobController : BlobControllerBase, IBlobAppService return _service.DeleteAsync(id); } - [HttpGet("uid/{id}/content")] - public virtual Task GetContentAsync(Guid id) + [HttpGet("download/{id}")] + [HttpGet("download/t/{tenantId}/{id}")] + public virtual Task DownloadAsync(BlobDownloadByIdInput input) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(input); + } + + [HttpGet("preview/{id}")] + [HttpGet("preview/t/{tenantId}/{id}")] + public async virtual Task PreviewAsync(BlobDownloadByIdInput input) + { + var content = await _service.PreviewAsync(input); + + if (!content.FileName.IsNullOrWhiteSpace()) + { + var contentDisposition = new ContentDispositionHeaderValue("inline"); + contentDisposition.SetHttpFileName(content.FileName); + HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); + } + + return content; } - [HttpGet("content")] - public virtual Task GetContentByNameAsync(BlobDownloadByNameInput input) + [HttpGet("download/by-name/{name}")] + [HttpGet("download/by-name/t/{tenantId}/{name}")] + public virtual Task DownloadByNameAsync(BlobDownloadByNameInput input) { - return _service.GetContentByNameAsync(input); + return _service.DownloadByNameAsync(input); } [HttpGet("{id}")] @@ -74,17 +93,15 @@ public class BlobController : BlobControllerBase, IBlobAppService return _service.GetListAsync(input); } - [HttpGet("{id}/generate-url")] + [HttpGet("generate/download-url/{id}")] public virtual Task GenerateDownloadUrlAsync(Guid id) { return _service.GenerateDownloadUrlAsync(id); } - [HttpGet] - [Route("download/{Key}")] - [Route("download/t/{TenantId}/{Key}")] - public virtual Task DownloadAsync(BlobDownloadByKeyInput input) + [HttpGet("generate/preview-url/{id}")] + public virtual Task GeneratePreviewUrlAsync(Guid id) { - return _service.DownloadAsync(input); + return _service.GeneratePreviewUrlAsync(id); } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs index dac00f68f..dc6b4efa0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs @@ -40,8 +40,8 @@ public class BlobIntegrationController : AbpControllerBase, IBlobIntegrationServ } [HttpGet("download")] - public virtual Task GetContentAsync(BlobFileGetByNameIntegrationDto input) + public virtual Task DownloadAsync(BlobFileGetByNameIntegrationDto input) { - return _service.GetContentAsync(input); + return _service.DownloadAsync(input); } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs index f8ec336a7..20a3b4c1d 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs @@ -50,16 +50,16 @@ public class PrivateBlobController : BlobControllerBase, IPrivateBlobAppService return _service.DeleteAsync(id); } - [HttpGet("uid/{id}/content")] - public virtual Task GetContentAsync(Guid id) + [HttpGet("download/{id}")] + public virtual Task DownloadAsync(Guid id) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(id); } - [HttpGet("content/{name}")] - public virtual Task GetContentByNameAsync(string name) + [HttpGet("download/by-name/{name}")] + public virtual Task DownloadByNameAsync(string name) { - return _service.GetContentByNameAsync(name); + return _service.DownloadByNameAsync(name); } [HttpGet("{id}")] diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs index aca0a128f..ab4610536 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs @@ -22,16 +22,16 @@ public class PublicBlobController : BlobControllerBase, IPublicBlobAppService _service = service; } - [HttpGet("uid/{id}/content")] - public virtual Task GetContentAsync(Guid id) + [HttpGet("download/{id}")] + public virtual Task DownloadAsync(Guid id) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(id); } - [HttpGet("content/{name}")] - public virtual Task GetContentByNameAsync(string name) + [HttpGet("download/by-name/{name}")] + public virtual Task DownloadByNameAsync(string name) { - return _service.GetContentByNameAsync(name); + return _service.DownloadByNameAsync(name); } [HttpGet("{id}")] diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs index 69414c0c9..a3922136a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs @@ -4,6 +4,7 @@ using Minio; using Minio.DataModel.Args; using Minio.Exceptions; using System; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading; @@ -108,11 +109,11 @@ public class MinioBlobProvider : IBlobProvider { var configuration = GetBlobConfiguration(); - var downloadUrl = await GenerateDownloadUrlAsync( + var downloadUrl = await GeneratePresignedUrlAsync( containerName, blobName, TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), - cancellationToken); + cancellationToken: cancellationToken); if (downloadUrl.IsNullOrWhiteSpace()) { return null; @@ -123,10 +124,11 @@ public class MinioBlobProvider : IBlobProvider return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); } - public async virtual Task GenerateDownloadUrlAsync( + public async virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { var client = GetMinioClient(); @@ -138,11 +140,21 @@ public class MinioBlobProvider : IBlobProvider return null; } - return await client.PresignedGetObjectAsync( - new PresignedGetObjectArgs() + var presignedGetObjectArgs = new PresignedGetObjectArgs() .WithBucket(bucket) .WithObject(objectName) - .WithExpiry(expiration.TotalSeconds.To())); + .WithExpiry(expiration.TotalSeconds.To()); + + var fileName = Path.GetFileName(blobName); + var type = isAttachmentContent ? "attachment" : "inline"; + var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " + + $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}"; + presignedGetObjectArgs.WithHeaders(new Dictionary() + { + { "response-content-disposition", disposition }, + }); + + return await client.PresignedGetObjectAsync(presignedGetObjectArgs); } public virtual Task CreateFolderAsync( diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs index 671525b8d..acdac8f47 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs @@ -6,6 +6,7 @@ using LINGYUN.Abp.BlobStoring.Tencent; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading; @@ -103,11 +104,11 @@ public class TencentBlobProvider : IBlobProvider { var configuration = await GetBlobConfiguration(); - var downloadUrl = await GenerateDownloadUrlAsync( + var downloadUrl = await GeneratePresignedUrlAsync( containerName, blobName, TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), - cancellationToken); + cancellationToken: cancellationToken); if (downloadUrl.IsNullOrWhiteSpace()) { return null; @@ -118,10 +119,11 @@ public class TencentBlobProvider : IBlobProvider return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); } - public async virtual Task GenerateDownloadUrlAsync( + public async virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); @@ -147,6 +149,14 @@ public class TencentBlobProvider : IBlobProvider headers = null, //签名中需要校验的 header queryParameters = null //签名中需要校验的 URL 中请求参数 }; + var fileName = Path.GetFileName(blobName); + var type = isAttachmentContent ? "attachment" : "inline"; + var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " + + $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}"; + preSignatureStruct.queryParameters = new Dictionary + { + { "response-content-disposition", disposition }, + }; return client.GenerateSignURL(preSignatureStruct); } diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj index e04cb7fee..e238f4cc1 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj +++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj @@ -6,6 +6,7 @@ net10.0 enable LY.MicroService.Applications.Single + A47DB958-CC3F-430B-A18A-57AA535BAB49 enable 10.2.0 From 215c9364ded64114f9ea83713556b313ca2d1a80 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 May 2026 09:43:50 +0800 Subject: [PATCH 4/4] feat: Optimize the file preview - use `vue-files-preview` package --- apps/vben5/apps/app-antd/src/store/auth.ts | 1 + .../@abp/blob-management/package.json | 5 +- .../blob-management/src/api/useBlobsApi.ts | 26 ++++- .../components/blobs/BlobFilePreviewModal.vue | 4 +- .../blobs/BlobFilePreviewModal1.vue | 106 ++++++++++++++++++ .../src/components/blobs/BlobFileTable.vue | 9 +- apps/vben5/pnpm-workspace.yaml | 2 + 7 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue diff --git a/apps/vben5/apps/app-antd/src/store/auth.ts b/apps/vben5/apps/app-antd/src/store/auth.ts index 51e03bfcb..e93399281 100644 --- a/apps/vben5/apps/app-antd/src/store/auth.ts +++ b/apps/vben5/apps/app-antd/src/store/auth.ts @@ -180,6 +180,7 @@ export const useAuthStore = defineStore('auth', () => { userId: userInfoRes.sub ?? abpConfig.currentUser.id, username: userInfoRes.uniqueName ?? abpConfig.currentUser.userName, realName: + userInfoRes.given_name ?? userInfoRes.name ?? abpConfig.currentUser.name ?? abpConfig.currentUser.userName, diff --git a/apps/vben5/packages/@abp/blob-management/package.json b/apps/vben5/packages/@abp/blob-management/package.json index de1e2f0ec..93fddb80b 100644 --- a/apps/vben5/packages/@abp/blob-management/package.json +++ b/apps/vben5/packages/@abp/blob-management/package.json @@ -37,13 +37,10 @@ "@vben/locales": "workspace:*", "@vben/stores": "workspace:*", "@vben/utils": "workspace:*", - "@vue-office/docx": "catalog:", - "@vue-office/excel": "catalog:", - "@vue-office/pdf": "catalog:", - "@vue-office/pptx": "catalog:", "ant-design-vue": "catalog:", "vue": "catalog:", "vue-demi": "catalog:", + "vue-files-preview": "catalog:", "vue-simple-uploader": "catalog:", "vxe-table": "catalog:" } diff --git a/apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts b/apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts index f1c109147..9ea8590f7 100644 --- a/apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts +++ b/apps/vben5/packages/@abp/blob-management/src/api/useBlobsApi.ts @@ -32,8 +32,26 @@ export function useBlobsApi() { }); } - function getContentApi(id: string): Promise { - return request(`/api/blob-management/blobs/uid/${id}/content`, { + function generateDownloadUrlApi(id: string): Promise { + return request( + `/api/blob-management/blobs/generate/download-url/${id}`, + { + method: 'GET', + }, + ); + } + + function generatePreviewUrlApi(id: string): Promise { + return request( + `/api/blob-management/blobs/generate/preview-url/${id}`, + { + method: 'GET', + }, + ); + } + + function downloadApi(id: string): Promise { + return request(`/api/blob-management/blobs/download/${id}`, { method: 'GET', responseType: 'blob', }); @@ -70,8 +88,10 @@ export function useBlobsApi() { cancel, createFolderApi, deleteApi, + downloadApi, getApi, - getContentApi, + generateDownloadUrlApi, + generatePreviewUrlApi, getFolderPagedListApi, getFilePagedListApi, getPagedListApi, diff --git a/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue index 55caa5ddf..69868c3ff 100644 --- a/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue +++ b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal.vue @@ -64,7 +64,7 @@ const blobType = ref(); const errorMessage = ref(''); const excelComponentKey = ref(0); // 只对 Excel 组件使用 key -const { getContentApi } = useBlobsApi(); +const { downloadApi } = useBlobsApi(); // 文件类型配置 const allowFiles: Record = { @@ -175,7 +175,7 @@ async function onInit() { } // 获取文件内容 - const content = await getContentApi(blob.value.id); + const content = await downloadApi(blob.value.id); // 检查文件大小 if (!isFileSizeValid(content)) { diff --git a/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue new file mode 100644 index 000000000..aac734a60 --- /dev/null +++ b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFilePreviewModal1.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue index 36dab6532..2d8cdd2ad 100644 --- a/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue +++ b/apps/vben5/packages/@abp/blob-management/src/components/blobs/BlobFileTable.vue @@ -35,7 +35,7 @@ const kbUnit = 1 * 1024; const mbUnit = kbUnit * 1024; const gbUnit = mbUnit * 1024; -const { deleteApi, getPagedListApi } = useBlobsApi(); +const { deleteApi, getPagedListApi, generateDownloadUrlApi } = useBlobsApi(); const selectedKeys = ref([]); @@ -169,7 +169,7 @@ const [BlobFileUploadModal, uploadModalApi] = useVbenModal({ }); const [BlobFilePreviewModal, previewModalApi] = useVbenModal({ connectedComponent: defineAsyncComponent( - () => import('./BlobFilePreviewModal.vue'), + () => import('./BlobFilePreviewModal1.vue'), ), }); @@ -191,10 +191,11 @@ function onDownload(row: BlobDto) { centered: true, content: $t('BlobManagement.BlobWellDownloadMessage', [row.name]), onCancel: () => {}, - onOk: () => { + onOk: async () => { + const downloadUrl = await generateDownloadUrlApi(row.id); downloadFileFromUrl({ fileName: row.name, - source: `/api/blob-management/blobs/uid/${row.id}/content`, + source: downloadUrl, }); }, title: $t('AbpUi.AreYouSure'), diff --git a/apps/vben5/pnpm-workspace.yaml b/apps/vben5/pnpm-workspace.yaml index e267880d2..058ee7799 100644 --- a/apps/vben5/pnpm-workspace.yaml +++ b/apps/vben5/pnpm-workspace.yaml @@ -83,6 +83,7 @@ catalog: '@vite-pwa/vitepress': ^1.1.0 '@vitejs/plugin-vue': ^6.0.4 '@vitejs/plugin-vue-jsx': ^5.1.4 + '@vue-files-preview': ^0.0.0 '@vue-office/docx': ^1.6.3 '@vue-office/excel': ^1.7.14 '@vue-office/pdf': ^2.0.10 @@ -235,6 +236,7 @@ catalog: vue: ^3.5.27 vue-demi: ^0.14.6 vue-eslint-parser: ^10.2.0 + vue-files-preview: ^1.0.43 vue-i18n: ^11.2.8 vue-json-viewer: ^3.0.4 vue-router: ^5.0.3