Browse Source

feat: Enhance blob management module

- Add the interface for generating download links
- Alibaba Cloud Object Storage adds configuration for the expiration time of pre-signed links
- Tencent Cloud Object Storage adds configuration for the expiration time of pre-signed links
- Add generation of download url feature
- Add the setting item for the expiration time of the generated download url
- Add the interface for parsing file ContentType
- Integrate the MimeCheck module to parse the Content-Type of the file
pull/1489/head
colin 4 months ago
parent
commit
5550ce36f7
  1. 2
      aspnet-core/LINGYUN.MicroService.SingleProject.slnx
  2. 10
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs
  3. 4
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs
  4. 2
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentCloudBlobProvider.cs
  5. 3
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj
  6. 5
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs
  7. 12
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  8. 10
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  9. 4
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs
  10. 18
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/Microsoft/Extensions/DependencyInjection/AliyunHttpClientFactoryServiceCollectionExtensions.cs
  11. 58
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs
  12. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs
  13. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs
  14. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionDefinitionProvider.cs
  15. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Permissions/BlobManagementPermissionNames.cs
  16. 16
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs
  17. 52
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs
  18. 18
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs
  19. 13
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs
  20. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/PublicBlobAppService.cs
  21. 15
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/AbpBlobManagementContentTypeResolveOptions.cs
  22. 33
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobContentTypeResolveContext.cs
  23. 7
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureDefinitionProvider.cs
  24. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs
  25. 10
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolveContributor.cs
  26. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/en.json
  27. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Localization/Resources/zh-Hans.json
  28. 11
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingDefinitionProvider.cs
  29. 7
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs
  30. 71
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobContentTypeResolver.cs
  31. 24
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs
  32. 87
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs
  33. 9
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContentTypeResolver.cs
  34. 10
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs
  35. 13
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs
  36. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs
  37. 14
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs
  38. 24
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/FileExtensionBlobContentTypeResolveContributor.cs
  39. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml
  40. 30
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd
  41. 25
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj
  42. 15
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/AbpBlobManagementMimeCheckModule.cs
  43. 33
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN/Abp/BlobManagement/MimeCheck/MimeCheckBlobContentTypeResolveContributor.cs
  44. 57
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs
  45. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN/Abp/BlobManagement/SettingManagement/BlobManagementSettingAppService.cs
  46. 49
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs
  47. 1
      aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
  48. 3
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

2
aspnet-core/LINGYUN.MicroService.SingleProject.slnx

@ -11,6 +11,7 @@
<Folder Name="/framework/alibaba-cloud/"> <Folder Name="/framework/alibaba-cloud/">
<Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN.Abp.Aliyun.SettingManagement.csproj" /> <Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun.SettingManagement/LINGYUN.Abp.Aliyun.SettingManagement.csproj" />
<Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj" /> <Project Path="framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN.Abp.Aliyun.csproj" />
<Project Path="framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj" />
<Project Path="framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj" /> <Project Path="framework/common/LINGYUN.Abp.Sms.Aliyun/LINGYUN.Abp.Sms.Aliyun.csproj" />
</Folder> </Folder>
<Folder Name="/framework/authentication/"> <Folder Name="/framework/authentication/">
@ -211,6 +212,7 @@
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN.Abp.BlobManagement.FileSystem.csproj" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN.Abp.BlobManagement.FileSystem.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi.Client/LINGYUN.Abp.BlobManagement.HttpApi.Client.csproj" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi.Client/LINGYUN.Abp.BlobManagement.HttpApi.Client.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN.Abp.BlobManagement.HttpApi.csproj" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN.Abp.BlobManagement.HttpApi.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj" Id="bb981955-78c8-45b7-a46a-cc9eefa2166a" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN.Abp.BlobManagement.Minio.csproj" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN.Abp.BlobManagement.Minio.csproj" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN.Abp.BlobManagement.SettingManagement.csproj" Id="649cb4c3-e7c9-4736-b254-0899bb26bfcb" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.SettingManagement/LINGYUN.Abp.BlobManagement.SettingManagement.csproj" Id="649cb4c3-e7c9-4736-b254-0899bb26bfcb" />
<Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN.Abp.BlobManagement.Tencent.csproj" /> <Project Path="modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN.Abp.BlobManagement.Tencent.csproj" />

10
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfiguration.cs

@ -53,6 +53,16 @@ public class TencentBlobProviderConfiguration
} }
} }
/// <summary>
/// Default value: 600.
/// </summary>
public int PresignedGetExpirySeconds {
get => _containerConfiguration.GetConfigurationOrDefault(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds);
set => _containerConfiguration.SetConfiguration(TencentBlobProviderConfigurationNames.PresignedGetExpirySeconds, value);
}
private int _defaultExpirySeconds = 600;
private readonly BlobContainerConfiguration _containerConfiguration; private readonly BlobContainerConfiguration _containerConfiguration;
public TencentBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) public TencentBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)

4
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.BlobStoring.Tencent/LINGYUN/Abp/BlobStoring/Tencent/TencentBlobProviderConfigurationNames.cs

@ -22,4 +22,8 @@ public static class TencentBlobProviderConfigurationNames
/// 创建命名空间时防盗链列表 /// 创建命名空间时防盗链列表
/// </summary> /// </summary>
public const string CreateBucketReferer = "Tencent:OSS:CreateBucketReferer"; public const string CreateBucketReferer = "Tencent:OSS:CreateBucketReferer";
/// <summary>
/// 生成预签名Uri的过期时间(s)
/// </summary>
public const string PresignedGetExpirySeconds = "Tencent:OSS:PresignedGetExpirySeconds";
} }

2
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, //对象键 key = blobName, //对象键
httpMethod = "GET", //HTTP 请求方法 httpMethod = "GET", //HTTP 请求方法
isHttps = true, //生成 HTTPS 请求 URL isHttps = true, //生成 HTTPS 请求 URL
signDurationSecond = 600, //请求签名时间为600s signDurationSecond = configuration.PresignedGetExpirySeconds, //请求签名时间为600s
headers = null, //签名中需要校验的 header headers = null, //签名中需要校验的 header
queryParameters = null //签名中需要校验的 URL 中请求参数 queryParameters = null //签名中需要校验的 URL 中请求参数
}; };

3
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj

@ -4,7 +4,7 @@
<Import Project="..\..\..\..\common.props" /> <Import Project="..\..\..\..\common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks> <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.BlobStoring.Aliyun</AssemblyName> <AssemblyName>LINGYUN.Abp.BlobStoring.Aliyun</AssemblyName>
<PackageId>LINGYUN.Abp.BlobStoring.Aliyun</PackageId> <PackageId>LINGYUN.Abp.BlobStoring.Aliyun</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Aliyun.OSS.SDK.NetCore" /> <PackageReference Include="Aliyun.OSS.SDK.NetCore" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Volo.Abp.BlobStoring" /> <PackageReference Include="Volo.Abp.BlobStoring" />
</ItemGroup> </ItemGroup>

5
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Aliyun; using LINGYUN.Abp.Aliyun;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -9,6 +10,10 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun;
typeof(AbpAliyunModule))] typeof(AbpAliyunModule))]
public class AbpBlobStoringAliyunModule : AbpModule public class AbpBlobStoringAliyunModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAliyunHttpClient();
}
// 需要时引用配置 // 需要时引用配置
//public override void ConfigureServices(ServiceConfigurationContext context) //public override void ConfigureServices(ServiceConfigurationContext context)
//{ //{

12
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs

@ -1,8 +1,10 @@
using Aliyun.OSS; using Aliyun.OSS;
using LINGYUN.Abp.Aliyun.Features; using LINGYUN.Abp.Aliyun.Features;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -14,13 +16,16 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun;
public class AliyunBlobProvider : BlobProviderBase, ITransientDependency public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
{ {
protected IOssClientFactory OssClientFactory { get; } protected IOssClientFactory OssClientFactory { get; }
protected IHttpClientFactory HttpClientFactory { get; }
protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; } protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; }
public AliyunBlobProvider( public AliyunBlobProvider(
IOssClientFactory ossClientFactory, IOssClientFactory ossClientFactory,
IHttpClientFactory httpClientFactory,
IAliyunBlobNameCalculator aliyunBlobNameCalculator) IAliyunBlobNameCalculator aliyunBlobNameCalculator)
{ {
OssClientFactory = ossClientFactory; OssClientFactory = ossClientFactory;
HttpClientFactory = httpClientFactory;
AliyunBlobNameCalculator = aliyunBlobNameCalculator; AliyunBlobNameCalculator = aliyunBlobNameCalculator;
} }
@ -55,8 +60,11 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
return null; return null;
} }
var ossObject = ossClient.GetObject(GetBucketName(args), blobName); var downloadUri = ossClient.GeneratePresignedUri(GetBucketName(args), blobName);
return ossObject.Content;
var httpClient = HttpClientFactory.CreateAliyunHttpClient();
return await httpClient.GetStreamAsync(downloadUri, args.CancellationToken);
} }
public override async Task SaveAsync(BlobProviderSaveArgs args) public override async Task SaveAsync(BlobProviderSaveArgs args)

10
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs

@ -40,6 +40,16 @@ public class AliyunBlobProviderConfiguration
} }
} }
/// <summary>
/// Default value: 7 * 24 * 3600.
/// </summary>
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; private readonly BlobContainerConfiguration _containerConfiguration;
public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)

4
aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs

@ -18,4 +18,8 @@ public static class AliyunBlobProviderConfigurationNames
/// 创建命名空间时防盗链列表 /// 创建命名空间时防盗链列表
/// </summary> /// </summary>
public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer"; public const string CreateBucketReferer = "Aliyun:OSS:CreateBucketReferer";
/// <summary>
/// 生成预签名Uri的过期时间(s)
/// </summary>
public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds";
} }

18
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);
}
}

58
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.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp.BlobStoring;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Aliyun; namespace LINGYUN.Abp.BlobManagement.Aliyun;
@ -17,24 +18,30 @@ public class AliyunBlobProvider : IBlobProvider
public const string ProviderName = "Aliyun"; public const string ProviderName = "Aliyun";
public string Name => ProviderName; public string Name => ProviderName;
protected IClock Clock { get; }
protected IConfiguration Configuration { get; } protected IConfiguration Configuration { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected IOssClientFactory OssClientFactory { get; } protected IOssClientFactory OssClientFactory { get; }
protected IHttpClientFactory HttpClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; }
protected AliyunBlobNamingNormalizer BlobNamingNormalizer { get; } protected AliyunBlobNamingNormalizer BlobNamingNormalizer { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public AliyunBlobProvider( public AliyunBlobProvider(
IClock clock,
IConfiguration configuration, IConfiguration configuration,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IOssClientFactory ossClientFactory, IOssClientFactory ossClientFactory,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
AliyunBlobNamingNormalizer blobNamingNormalizer) AliyunBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider)
{ {
Clock = clock;
Configuration = configuration; Configuration = configuration;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
OssClientFactory = ossClientFactory; OssClientFactory = ossClientFactory;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
BlobNamingNormalizer = blobNamingNormalizer; BlobNamingNormalizer = blobNamingNormalizer;
ConfigurationProvider = configurationProvider;
} }
public async virtual Task CreateContainerAsync( public async virtual Task CreateContainerAsync(
@ -90,6 +97,29 @@ public class AliyunBlobProvider : IBlobProvider
string containerName, string containerName,
string blobName, string blobName,
CancellationToken cancellationToken = default) 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<string?> GenerateDownloadUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); var client = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName); var bucket = NormalizeContainerName(containerName);
@ -100,11 +130,7 @@ public class AliyunBlobProvider : IBlobProvider
return null; return null;
} }
var downloadUrl = client.GeneratePresignedUri(bucket, objectName); return client.GeneratePresignedUri(bucket, objectName, Clock.Now.Add(expiration)).ToString();
var httpClient = HttpClientFactory.CreateAliyunHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
} }
public virtual Task CreateFolderAsync( public virtual Task CreateFolderAsync(
@ -120,7 +146,8 @@ public class AliyunBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync( public async virtual Task UploadBlobAsync(
string containerName, string containerName,
string blobName, string blobName,
Stream content, Stream content,
string? contentType = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); var client = await CreateClientAsync();
@ -129,7 +156,20 @@ public class AliyunBlobProvider : IBlobProvider
CreateBucketIfNotExists(client, bucket); 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<BlobManagementContainer>();
var blobConfiguration = configuration.GetAliyunConfiguration();
return blobConfiguration;
} }
protected async virtual Task<IOss> CreateClientAsync() protected async virtual Task<IOss> CreateClientAsync()

12
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; }
}

4
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 DeleteAsync(Guid id);
Task<string> GenerateDownloadUrlAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByKeyInput input);
Task<IRemoteStreamContent> GetContentAsync(Guid id); Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input); Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input);

2
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.Delete, L("Permission:Delete"));
blob.AddChild(BlobManagementPermissionNames.Blob.Download, L("Permission:Download")) blob.AddChild(BlobManagementPermissionNames.Blob.Download, L("Permission:Download"))
.RequireFeatures(BlobManagementFeatureNames.Blob.DownloadFile); .RequireFeatures(BlobManagementFeatureNames.Blob.DownloadFile);
blob.AddChild(BlobManagementPermissionNames.Blob.GenerateDownloadUrl, L("Permission:GenerateDownloadUrl"))
.RequireFeatures(BlobManagementFeatureNames.Blob.GenerateDownloadUrl);
context.AddResourcePermission( context.AddResourcePermission(
name: BlobManagementPermissionNames.Blob.Resources.Delete, name: BlobManagementPermissionNames.Blob.Resources.Delete,

2
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 Download = Default + ".Download";
public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl";
public const string ManagePermissions = Default + ".ManagePermissions"; public const string ManagePermissions = Default + ".ManagePermissions";
public static class Resources public static class Resources

16
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.Dtos;
using LINGYUN.Abp.BlobManagement.Features; using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Permissions; using LINGYUN.Abp.BlobManagement.Permissions;
using LINGYUN.Abp.Features.LimitValidation;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.Features; using Volo.Abp.Features;
@ -23,11 +22,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
GetListPolicyName = BlobManagementPermissionNames.Blob.Default; GetListPolicyName = BlobManagementPermissionNames.Blob.Default;
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateDto input) public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateDto input)
{ {
await CheckCreatePolicyAsync(); await CheckCreatePolicyAsync();
@ -35,11 +29,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
await base.CreateChunkFileAsync(input.ContainerId.ToString(), input); await base.CreateChunkFileAsync(input.ContainerId.ToString(), input);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateDto input) public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateDto input)
{ {
await CheckCreatePolicyAsync(); await CheckCreatePolicyAsync();
@ -58,11 +47,6 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
return await base.CreateFolderAsync(blobContainer, input); return await base.CreateFolderAsync(blobContainer, input);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input) public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input)
{ {
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);

52
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.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Permissions; using LINGYUN.Abp.BlobManagement.Permissions;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
@ -13,7 +11,6 @@ using System.Web;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.Features;
using Volo.Abp.Specifications; using Volo.Abp.Specifications;
namespace LINGYUN.Abp.BlobManagement; namespace LINGYUN.Abp.BlobManagement;
@ -49,11 +46,44 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
await DeleteBlobAsync(blob); await DeleteBlobAsync(blob);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)] public async virtual Task<string> GenerateDownloadUrlAsync(Guid id)
[RequiresLimitFeature( {
BlobManagementFeatureNames.Blob.DownloadLimit, var blob = await BlobRepository.GetAsync(id);
BlobManagementFeatureNames.Blob.DownloadInterval, var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId);
LimitPolicy.Month)]
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<IRemoteStreamContent> 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<IRemoteStreamContent> GetContentAsync(Guid id) public async virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
{ {
var blob = await BlobRepository.GetAsync(id); var blob = await BlobRepository.GetAsync(id);
@ -62,7 +92,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
var stream = await BlobManager.DownloadBlobsync(blob); 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<BlobDto> GetAsync(Guid id) public async virtual Task<BlobDto> GetAsync(Guid id)
@ -120,7 +150,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
blobStream.Length, blobStream.Length,
parentBlob); 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, BlobDto>(blob); return ObjectMapper.Map<Blob, BlobDto>(blob);
} }
@ -167,7 +197,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
var stream = await BlobManager.DownloadBlobsync(blob); 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<Blob?> FindBlobByNameAsync(BlobContainer blobContainer, string name) protected async virtual Task<Blob?> FindBlobByNameAsync(BlobContainer blobContainer, string name)

18
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.Dtos;
using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.Features.LimitValidation;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.Features;
namespace LINGYUN.Abp.BlobManagement; 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) public async virtual Task CreateChunkFileAsync(BlobFileChunkCreateWithoutContainerDto input)
{ {
await CheckCreatePolicyAsync(); await CheckCreatePolicyAsync();
@ -32,11 +24,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
await base.CreateChunkFileAsync(ContainerName, input); await base.CreateChunkFileAsync(ContainerName, input);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.UploadLimit,
BlobManagementFeatureNames.Blob.UploadInterval,
LimitPolicy.Month)]
public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateWithoutContainerDto input) public async virtual Task<BlobDto> CreateFileAsync(BlobFileCreateWithoutContainerDto input)
{ {
await CheckCreatePolicyAsync(); await CheckCreatePolicyAsync();
@ -55,11 +42,6 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
return await base.CreateFolderAsync(blobContainer, input); return await base.CreateFolderAsync(blobContainer, input);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.DownloadFile)]
[RequiresLimitFeature(
BlobManagementFeatureNames.Blob.DownloadLimit,
BlobManagementFeatureNames.Blob.DownloadInterval,
LimitPolicy.Month)]
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name) public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
{ {
var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName); var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName);

13
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.Dtos;
using LINGYUN.Abp.BlobManagement.Features; using LINGYUN.Abp.BlobManagement.Features;
using LINGYUN.Abp.BlobManagement.Integration.Dtos; using LINGYUN.Abp.BlobManagement.Integration.Dtos;
using LINGYUN.Abp.Features.LimitValidation;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; 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<BlobDto> CreateAsync(BlobFileCreateIntegrationDto input) public async virtual Task<BlobDto> CreateAsync(BlobFileCreateIntegrationDto input)
{ {
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName); var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
@ -62,7 +56,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
parentBlob, parentBlob,
input.Overwrite == true); input.Overwrite == true);
await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream, input.File.ContentType); await BlobManager.UploadBlobAsync(blobContainer, blob, blobStream);
return ObjectMapper.Map<Blob, BlobDto>(blob); return ObjectMapper.Map<Blob, BlobDto>(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<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input) public async virtual Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input)
{ {
try try

14
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.Features;
using LINGYUN.Abp.BlobManagement.Permissions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Features; using Volo.Abp.Features;
@ -15,16 +16,19 @@ public class PublicBlobAppService : BlobWithoutContainerAppService, IPublicBlobA
BlobManager blobManager) BlobManager blobManager)
: base(blobContainerRepository, blobRepository, 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)
{ {
// 公共文件访问不校验权限 if (!CurrentUser.IsAuthenticated)
return Task.CompletedTask; {
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);
} }
} }

15
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<IBlobContentTypeResolveContributor> BlobContentTypeResolvers { get; }
public AbpBlobManagementContentTypeResolveOptions()
{
BlobContentTypeResolvers = new List<IBlobContentTypeResolveContributor>();
}
}

33
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();
}
}

7
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"), displayName: L("Features:DisplayName:EnableBlob"),
description: L("Features:Description:EnableBlob"), description: L("Features:Description:EnableBlob"),
valueType: new ToggleStringValueType(new BooleanValueValidator())); 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( blobDefaultFeature.CreateChild(
name: BlobManagementFeatureNames.Blob.DownloadFile, name: BlobManagementFeatureNames.Blob.DownloadFile,
defaultValue: true.ToString(), defaultValue: true.ToString(),

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Features/BlobManagementFeatureNames.cs

@ -20,6 +20,10 @@ public static class BlobManagementFeatureNames
/// </summary> /// </summary>
public const string Enable = Default + ".Enable"; public const string Enable = Default + ".Enable";
/// <summary> /// <summary>
/// 生成临时文件下载链接
/// </summary>
public const string GenerateDownloadUrl = Default + ".GenerateDownloadUrl";
/// <summary>
/// 下载文件功能 /// 下载文件功能
/// </summary> /// </summary>
public const string DownloadFile = Default + ".DownloadFile"; public const string DownloadFile = Default + ".DownloadFile";

10
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);
}

5
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:Download": "Download",
"Permission:Blob:Delete": "Delete Blob", "Permission:Blob:Delete": "Delete Blob",
"Permission:Blob:View": "View Blob", "Permission:Blob:View": "View Blob",
"Permission:GenerateDownloadUrl": "Generate Download Url",
"Features:BlobManagement": "Blob Management", "Features:BlobManagement": "Blob Management",
"Features:DisplayName:EnableBlobManagement": "Enable Blob Management", "Features:DisplayName:EnableBlobManagement": "Enable Blob Management",
"Features:Description:EnableBlobManagement": "Enable to give the application the ability to have an blob storage module.", "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:Description:UploadLimit": "Limits the total number of file uploads within a specified period",
"Features:DisplayName:UploadInterval": "Limit Uploads Interval", "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: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:001001": "A blob container named {Name} already exists!",
"BlobManagement:001002": "The blob container is not empty and cannot be deleted!", "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!", "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", "Description:FileLimitLength": "Limit size of uploaded file in MB",
"DisplayName:AllowFileExtensions": "File extension", "DisplayName:AllowFileExtensions": "File extension",
"Description:AllowFileExtensions": "List of allowed extensions to upload files, with multiple extensions separated by, don't need a notation", "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:BlobManagement": "Blob Management",
"DisplayName:Blobs": "Blobs", "DisplayName:Blobs": "Blobs",
"DisplayName:CreationTime": "Creation Time", "DisplayName:CreationTime": "Creation Time",

5
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:Download": "下载",
"Permission:Blob:Delete": "删除存储对象", "Permission:Blob:Delete": "删除存储对象",
"Permission:Blob:View": "查看存储对象", "Permission:Blob:View": "查看存储对象",
"Permission:GenerateDownloadUrl": "生成下载链接",
"Features:BlobManagement": "对象存储", "Features:BlobManagement": "对象存储",
"Features:DisplayName:EnableBlobManagement": "启用对象存储", "Features:DisplayName:EnableBlobManagement": "启用对象存储",
"Features:Description:EnableBlobManagement": "启用以使应用程序拥有对象存储模块的能力.", "Features:Description:EnableBlobManagement": "启用以使应用程序拥有对象存储模块的能力.",
@ -29,6 +30,8 @@
"Features:Description:UploadLimit": "在指定的周期内限制上传文件的总次数", "Features:Description:UploadLimit": "在指定的周期内限制上传文件的总次数",
"Features:DisplayName:UploadInterval": "限制上传文件周期", "Features:DisplayName:UploadInterval": "限制上传文件周期",
"Features:Description:UploadInterval": "限制上传文件次数的更新周期,时间刻度为:月,默认:1", "Features:Description:UploadInterval": "限制上传文件次数的更新周期,时间刻度为:月,默认:1",
"Features:DisplayName:GenerateDownloadUrl": "生成文件下载链接",
"Features:Description:GenerateDownloadUrl": "是否允许用户生成临时文件下载链接",
"BlobManagement:001001": "已经存在名为 {Name} 的存储容器!", "BlobManagement:001001": "已经存在名为 {Name} 的存储容器!",
"BlobManagement:001002": "存储容器非空,不可删除!", "BlobManagement:001002": "存储容器非空,不可删除!",
"BlobManagement:001003": "存储容器为系统容器,不可删除!", "BlobManagement:001003": "存储容器为系统容器,不可删除!",
@ -55,6 +58,8 @@
"Description:FileLimitLength": "上传文件的限制大小,单位(MB)", "Description:FileLimitLength": "上传文件的限制大小,单位(MB)",
"DisplayName:AllowFileExtensions": "文件扩展名", "DisplayName:AllowFileExtensions": "文件扩展名",
"Description:AllowFileExtensions": "允许的上传文件扩展名列表,多个扩展名以,分隔,无需输入.符号", "Description:AllowFileExtensions": "允许的上传文件扩展名列表,多个扩展名以,分隔,无需输入.符号",
"DisplayName:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效期",
"Description:GenerateDownloadUrlExpirySeconds": "生成临时下载链接有效时间, 单位(s), 未设置或为空时默认600s.",
"DisplayName:BlobManagement": "对象存储", "DisplayName:BlobManagement": "对象存储",
"DisplayName:Blobs": "存储管理", "DisplayName:Blobs": "存储管理",
"DisplayName:CreationTime": "创建时间", "DisplayName:CreationTime": "创建时间",

11
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, ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName, GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.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),
}; };
} }

7
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/Settings/BlobManagementSettingNames.cs

@ -11,7 +11,12 @@ public static class BlobManagementSettingNames
/// 允许的文件扩展名类型 /// 允许的文件扩展名类型
/// </summary> /// </summary>
public const string AllowFileExtensions = GroupName + ".AllowFileExtensions"; public const string AllowFileExtensions = GroupName + ".AllowFileExtensions";
/// <summary>
/// 生成临时下载链接有效期(s)
/// </summary>
public const string GenerateDownloadUrlExpirySeconds = GroupName + ".GenerateDownloadUrlExpirySeconds";
public const long DefaultFileLimitLength = 5L; 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";
} }

71
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<BlobContentTypeResolver> Logger { get; set; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpBlobManagementContentTypeResolveOptions Options { get; }
public BlobContentTypeResolver(
IOptions<AbpBlobManagementContentTypeResolveOptions> options,
IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Options = options.Value;
Logger = NullLogger<BlobContentTypeResolver>.Instance;
}
public async virtual Task<string> 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;
}
}

24
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("/")}";
}
}

87
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 Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.IO; using System.IO;
@ -10,6 +13,7 @@ using Volo.Abp.Caching;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Features;
using Volo.Abp.IO; using Volo.Abp.IO;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Specifications; using Volo.Abp.Specifications;
@ -19,11 +23,13 @@ namespace LINGYUN.Abp.BlobManagement;
public class BlobManager : DomainService public class BlobManager : DomainService
{ {
private readonly IDistributedCache<BlobDownloadKeyCacheItem> _blobDownloadKeyCache;
private readonly IDistributedCache<BlobDownloadCacheItem> _blobDownloadCache; private readonly IDistributedCache<BlobDownloadCacheItem> _blobDownloadCache;
private readonly IDistributedEventBus _distributedEventBus; private readonly IDistributedEventBus _distributedEventBus;
private readonly IMemoryCache _blobFileValidateCache; private readonly IMemoryCache _blobFileValidateCache;
private readonly ISettingProvider _settingProvider; private readonly ISettingProvider _settingProvider;
private readonly IBlobContentTypeResolver _blobContentTypeResolver;
private readonly IBlobProvider _blobProvider; private readonly IBlobProvider _blobProvider;
private readonly IBlobRepository _blobRepository; private readonly IBlobRepository _blobRepository;
private readonly IBlobContainerRepository _blobContainerRepository; private readonly IBlobContainerRepository _blobContainerRepository;
@ -36,7 +42,9 @@ public class BlobManager : DomainService
IBlobContainerRepository blobContainerRepository, IBlobContainerRepository blobContainerRepository,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
IDistributedEventBus distributedEventBus, IDistributedEventBus distributedEventBus,
IDistributedCache<BlobDownloadKeyCacheItem> blobDownloadKeyCache,
IDistributedCache<BlobDownloadCacheItem> blobDownloadCache, IDistributedCache<BlobDownloadCacheItem> blobDownloadCache,
IBlobContentTypeResolver blobContentTypeResolver,
IApplicationInfoAccessor applicationInfoAccessor, IApplicationInfoAccessor applicationInfoAccessor,
IMemoryCache blobFileValidateCache, IMemoryCache blobFileValidateCache,
ISettingProvider settingProvider) ISettingProvider settingProvider)
@ -46,7 +54,9 @@ public class BlobManager : DomainService
_blobContainerRepository = blobContainerRepository; _blobContainerRepository = blobContainerRepository;
_cancellationTokenProvider = cancellationTokenProvider; _cancellationTokenProvider = cancellationTokenProvider;
_distributedEventBus = distributedEventBus; _distributedEventBus = distributedEventBus;
_blobDownloadKeyCache = blobDownloadKeyCache;
_blobDownloadCache = blobDownloadCache; _blobDownloadCache = blobDownloadCache;
_blobContentTypeResolver = blobContentTypeResolver;
_applicationInfoAccessor = applicationInfoAccessor; _applicationInfoAccessor = applicationInfoAccessor;
_blobFileValidateCache = blobFileValidateCache; _blobFileValidateCache = blobFileValidateCache;
_settingProvider = settingProvider; _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<Blob> UploadBlobAsync( public async virtual Task<Blob> UploadBlobAsync(
BlobContainer blobContainer, BlobContainer blobContainer,
Blob blob, Blob blob,
Stream stream, Stream stream,
string? contentType = null,
string? compareMd5 = null) string? compareMd5 = null)
{ {
await ValidateUploadFileAsync(blob.Name, stream.Length); await ValidateUploadFileAsync(blob.Name, stream.Length);
@ -204,7 +218,14 @@ public class BlobManager : DomainService
blob.SetContentMd5(blobMd5); 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.SetProvider(_blobProvider.Name);
blob.SetFileInfo(contentType, stream.Length); blob.SetFileInfo(contentType, stream.Length);
@ -212,6 +233,7 @@ public class BlobManager : DomainService
return blob; return blob;
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.UploadFile)]
public async virtual Task CreateChunkBlobAsync( public async virtual Task CreateChunkBlobAsync(
string containerIdOrName, string containerIdOrName,
string name, string name,
@ -285,6 +307,7 @@ public class BlobManager : DomainService
// 多分片文件流合并上传 // 多分片文件流合并上传
mergeFileStream.Seek(0, SeekOrigin.Begin); mergeFileStream.Seek(0, SeekOrigin.Begin);
await UploadBlobAsync(blobContainer, blob, mergeFileStream, compareMd5); 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<Stream?> DownloadBlobsync(Blob blob) public async virtual Task<Stream?> DownloadBlobsync(Blob blob)
{ {
var containerName = await _blobContainerRepository.GetNameAsync(blob.ContainerId); var containerName = await _blobContainerRepository.GetNameAsync(blob.ContainerId);
@ -306,6 +334,59 @@ public class BlobManager : DomainService
return await DownloadBlobsync(containerName, blob.FullName); return await DownloadBlobsync(containerName, blob.FullName);
} }
[RequiresFeature(BlobManagementFeatureNames.Blob.GenerateDownloadUrl)]
public async virtual Task<string> 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<Blob?> FindBlobByDownloadKeyAsync(string downloadKey)
{
var cacheItem = await _blobDownloadKeyCache.GetAsync(downloadKey);
if (cacheItem == null)
{
return null;
}
return await _blobRepository.FindAsync(cacheItem.BlobId);
}
public virtual string GetBlobProvider() public virtual string GetBlobProvider()
{ {
return _blobProvider.Name; return _blobProvider.Name;

9
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<string> ResolveContentTypeAsync(string blobName, Stream blobStream);
}

10
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;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,6 +22,12 @@ public interface IBlobProvider
string blobName, string blobName,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
Task<string?> GenerateDownloadUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
CancellationToken cancellationToken = default);
Task CreateFolderAsync( Task CreateFolderAsync(
string containerName, string containerName,
string blobName, string blobName,
@ -35,6 +42,7 @@ public interface IBlobProvider
string containerName, string containerName,
string blobName, string blobName,
Stream stream, Stream stream,
string? contentType = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
Task DeleteBlobAsync( Task DeleteBlobAsync(

13
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<Stream?>(File.OpenRead(fileName)); return Task.FromResult<Stream?>(File.OpenRead(fileName));
} }
public virtual Task<string?> GenerateDownloadUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
// 本地文件系统无需生成下载链接
return Task.FromResult<string?>(null);
}
public virtual Task CreateFolderAsync( public virtual Task CreateFolderAsync(
string containerName, string containerName,
string blobName, string blobName,
@ -114,7 +124,8 @@ public class FileSystemBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync( public async virtual Task UploadBlobAsync(
string containerName, string containerName,
string blobName, string blobName,
Stream content, Stream content,
string? contentType = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var fileName = CalculateBlobName(NormalizeContainerName(containerName.RemovePostFix("/")), blobName); var fileName = CalculateBlobName(NormalizeContainerName(containerName.RemovePostFix("/")), blobName);

14
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/AbpBlobManagementHttpApiModule.cs

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BlobManagement; namespace LINGYUN.Abp.BlobManagement;
@ -26,4 +27,17 @@ public class AbpBlobManagementHttpApiModule : AbpModule
typeof(AbpBlobManagementApplicationContractsModule).Assembly); typeof(AbpBlobManagementApplicationContractsModule).Assembly);
}); });
} }
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAspNetCoreContentOptions>(options =>
{
options.ContentTypeMaps.TryAdd(".log", "text/plain");
options.ContentTypeMaps.TryAdd(".text", "text/plain");
});
Configure<AbpBlobManagementContentTypeResolveOptions>(options =>
{
options.BlobContentTypeResolvers.Add(new FileExtensionBlobContentTypeResolveContributor());
});
}
} }

14
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); return _service.GetListAsync(input);
} }
[HttpGet("{id}/generate-url")]
public virtual Task<string> GenerateDownloadUrlAsync(Guid id)
{
return _service.GenerateDownloadUrlAsync(id);
}
[HttpGet]
[Route("download/{Key}")]
[Route("download/t/{TenantId}/{Key}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByKeyInput input)
{
return _service.DownloadAsync(input);
}
} }

24
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<IContentTypeProvider>();
if (contentTypeProvider != null &&
contentTypeProvider.TryGetContentType(context.BlobName, out var contentType) &&
!contentType.IsNullOrWhiteSpace())
{
context.Success(contentType);
}
return Task.CompletedTask;
}
}

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

25
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.MimeCheck/LINGYUN.Abp.BlobManagement.MimeCheck.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.BlobManagement.MimeCheck</AssemblyName>
<PackageId>LINGYUN.Abp.BlobManagement.MimeCheck</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Nullable>enable</Nullable>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MimeCheck" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" />
</ItemGroup>
</Project>

15
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<AbpBlobManagementContentTypeResolveOptions>(options =>
{
options.BlobContentTypeResolvers.Add(new MimeCheckBlobContentTypeResolveContributor());
});
}
}

33
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<ILogger<MimeCheckBlobContentTypeResolveContributor>>()
?.LogWarning("Failed to decetion the blob mime type: {message}!", ex.Message);
}
}
}

57
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;
using Volo.Abp.BlobStoring.Minio; using Volo.Abp.BlobStoring.Minio;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Minio; namespace LINGYUN.Abp.BlobManagement.Minio;
@ -19,17 +20,20 @@ public class MinioBlobProvider : IBlobProvider
public const string ProviderName = "Minio"; public const string ProviderName = "Minio";
public string Name => ProviderName; public string Name => ProviderName;
protected IClock Clock { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected IHttpClientFactory HttpClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; }
protected MinioBlobNamingNormalizer BlobNamingNormalizer { get; } protected MinioBlobNamingNormalizer BlobNamingNormalizer { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public MinioBlobProvider( public MinioBlobProvider(
IClock clock,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
MinioBlobNamingNormalizer blobNamingNormalizer, MinioBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider) IBlobContainerConfigurationProvider configurationProvider)
{ {
Clock = clock;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
BlobNamingNormalizer = blobNamingNormalizer; BlobNamingNormalizer = blobNamingNormalizer;
@ -101,6 +105,29 @@ public class MinioBlobProvider : IBlobProvider
string containerName, string containerName,
string blobName, string blobName,
CancellationToken cancellationToken = default) 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<string?> GenerateDownloadUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{ {
var client = GetMinioClient(); var client = GetMinioClient();
var bucket = NormalizeContainerName(containerName); var bucket = NormalizeContainerName(containerName);
@ -111,17 +138,11 @@ public class MinioBlobProvider : IBlobProvider
return null; return null;
} }
var configuration = GetBlobConfiguration(); return await client.PresignedGetObjectAsync(
new PresignedGetObjectArgs()
var downloadUrl = await client.PresignedGetObjectAsync( .WithBucket(bucket)
new PresignedGetObjectArgs() .WithObject(objectName)
.WithBucket(bucket) .WithExpiry(expiration.TotalSeconds.To<int>()));
.WithObject(objectName)
.WithExpiry(configuration.PresignedGetExpirySeconds));
var httpClient = HttpClientFactory.CreateMinioHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
} }
public virtual Task CreateFolderAsync( public virtual Task CreateFolderAsync(
@ -136,7 +157,8 @@ public class MinioBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync( public async virtual Task UploadBlobAsync(
string containerName, string containerName,
string blobName, string blobName,
Stream content, Stream content,
string? contentType = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var client = GetMinioClient(); var client = GetMinioClient();
@ -145,12 +167,17 @@ public class MinioBlobProvider : IBlobProvider
await CreateBucketIfNotExists(client, bucket); await CreateBucketIfNotExists(client, bucket);
await client.PutObjectAsync(new PutObjectArgs() var putObjectArgs = new PutObjectArgs()
.WithBucket(bucket) .WithBucket(bucket)
.WithObject(objectName) .WithObject(objectName)
.WithStreamData(content) .WithStreamData(content)
.WithObjectSize(content.Length), .WithObjectSize(content.Length);
cancellationToken); if (!contentType.IsNullOrWhiteSpace())
{
putObjectArgs.WithContentType(contentType);
}
await client.PutObjectAsync(putObjectArgs, cancellationToken);
} }
protected virtual IMinioClient GetMinioClient() protected virtual IMinioClient GetMinioClient()

5
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"]); 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( ossObjectSetting.AddDetail(
await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.FileLimitLength), await SettingDefinitionManager.GetAsync(BlobManagementSettingNames.FileLimitLength),
StringLocalizerFactory, StringLocalizerFactory,

49
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.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BlobManagement.Tencent; namespace LINGYUN.Abp.BlobManagement.Tencent;
@ -21,6 +21,7 @@ public class TencentBlobProvider : IBlobProvider
public const string ProviderName = "TencentCloud"; public const string ProviderName = "TencentCloud";
public string Name => ProviderName; public string Name => ProviderName;
protected IClock Clock { get; }
protected IConfiguration Configuration { get; } protected IConfiguration Configuration { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected ICosClientFactory CosClientFactory { get; } protected ICosClientFactory CosClientFactory { get; }
@ -29,6 +30,7 @@ public class TencentBlobProvider : IBlobProvider
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public TencentBlobProvider( public TencentBlobProvider(
IClock clock,
IConfiguration configuration, IConfiguration configuration,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
ICosClientFactory cosClientFactory, ICosClientFactory cosClientFactory,
@ -36,6 +38,7 @@ public class TencentBlobProvider : IBlobProvider
TencentBlobNamingNormalizer blobNamingNormalizer, TencentBlobNamingNormalizer blobNamingNormalizer,
IBlobContainerConfigurationProvider configurationProvider) IBlobContainerConfigurationProvider configurationProvider)
{ {
Clock = clock;
Configuration = configuration; Configuration = configuration;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
CosClientFactory = cosClientFactory; CosClientFactory = cosClientFactory;
@ -97,6 +100,29 @@ public class TencentBlobProvider : IBlobProvider
string containerName, string containerName,
string blobName, string blobName,
CancellationToken cancellationToken = default) 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<string?> GenerateDownloadUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); var client = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName); var bucket = NormalizeContainerName(containerName);
@ -108,8 +134,6 @@ public class TencentBlobProvider : IBlobProvider
} }
var configuration = await GetBlobConfiguration(); var configuration = await GetBlobConfiguration();
var blobProviderConfig = Configuration.GetSection($"BlobManagement:{ProviderName}");
// See: https://cloud.tencent.com/document/product/436/47238 // See: https://cloud.tencent.com/document/product/436/47238
var preSignatureStruct = new PreSignatureStruct var preSignatureStruct = new PreSignatureStruct
{ {
@ -119,16 +143,12 @@ public class TencentBlobProvider : IBlobProvider
key = objectName, //对象键 key = objectName, //对象键
httpMethod = "GET", //HTTP 请求方法 httpMethod = "GET", //HTTP 请求方法
isHttps = true, //生成 HTTPS 请求 URL isHttps = true, //生成 HTTPS 请求 URL
signDurationSecond = blobProviderConfig.GetValue("SignDurationSecond", 600), //请求签名时间 signDurationSecond = expiration.TotalSeconds.To<long>(), //请求签名时间
headers = null, //签名中需要校验的 header headers = null, //签名中需要校验的 header
queryParameters = null //签名中需要校验的 URL 中请求参数 queryParameters = null //签名中需要校验的 URL 中请求参数
}; };
var downloadUrl = client.GenerateSignURL(preSignatureStruct); return client.GenerateSignURL(preSignatureStruct);
var httpClient = HttpClientFactory.CreateTencentHttpClient();
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
} }
public virtual Task CreateFolderAsync( public virtual Task CreateFolderAsync(
@ -144,7 +164,8 @@ public class TencentBlobProvider : IBlobProvider
public async virtual Task UploadBlobAsync( public async virtual Task UploadBlobAsync(
string containerName, string containerName,
string blobName, string blobName,
Stream content, Stream content,
string? contentType = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); var client = await CreateClientAsync();
@ -153,7 +174,13 @@ public class TencentBlobProvider : IBlobProvider
CreateBucketIfNotExists(client, bucket); 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<CosXml> CreateClientAsync() protected async virtual Task<CosXml> CreateClientAsync()

1
aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj

@ -151,6 +151,7 @@
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Domain\LINGYUN.Abp.BlobManagement.Domain.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.FileSystem\LINGYUN.Abp.BlobManagement.FileSystem.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.FileSystem\LINGYUN.Abp.BlobManagement.FileSystem.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.HttpApi\LINGYUN.Abp.BlobManagement.HttpApi.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.HttpApi\LINGYUN.Abp.BlobManagement.HttpApi.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.MimeCheck\LINGYUN.Abp.BlobManagement.MimeCheck.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Minio\LINGYUN.Abp.BlobManagement.Minio.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Minio\LINGYUN.Abp.BlobManagement.Minio.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Tencent\LINGYUN.Abp.BlobManagement.Tencent.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.Tencent\LINGYUN.Abp.BlobManagement.Tencent.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.SettingManagement\LINGYUN.Abp.BlobManagement.SettingManagement.csproj" /> <ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobManagement.SettingManagement\LINGYUN.Abp.BlobManagement.SettingManagement.csproj" />

3
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.Exporter.MiniSoftware;
using LINGYUN.Abp.Notifications.Calendar; using LINGYUN.Abp.Notifications.Calendar;
@ -130,6 +131,8 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpBlobManagementMinioModule), typeof(AbpBlobManagementMinioModule),
// 对象存储模块 腾讯云 // 对象存储模块 腾讯云
typeof(AbpBlobManagementTencentModule), typeof(AbpBlobManagementTencentModule),
// 对象存储模块 MimeCheck扩展
typeof(AbpBlobManagementMimeCheckModule),
// 对象存储模块 应用服务 // 对象存储模块 应用服务
typeof(AbpBlobManagementDomainModule), typeof(AbpBlobManagementDomainModule),
// 对象存储模块 控制器 // 对象存储模块 控制器

Loading…
Cancel
Save