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