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), // 对象存储模块 控制器