From b4960fa2e4e5ccbac20c5ef7ea49b798e8406feb Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 May 2026 16:56:27 +0800 Subject: [PATCH] feat: Optimize BlobProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 阿里云OSS SDK v4签名存在缺陷, 默认使用v1签名 - 阿里云OSS SDK v2不支持CORS配置, 默认不开启预览, 使用后台代理链接预览文件 - 阿里云BlobProvider增加多个适配SDK v2的配置项 - 腾讯云OSS默认不支持文件预览功能, 需要额外开通数据处理服务, 默认不开启预览, 使用后台代理链接预览文件 --- .../LINGYUN/Abp/Aliyun/AliyunClientFactory.cs | 11 +- .../Aliyun/AliyunBlobNamingNormalizer.cs | 8 +- .../BlobStoring/Aliyun/AliyunBlobProvider.cs | 45 +++--- .../Aliyun/AliyunBlobProviderConfiguration.cs | 67 ++++++++- .../AliyunBlobProviderConfigurationNames.cs | 12 ++ .../BlobStoring/Aliyun/OssClientFactory.cs | 47 +++--- .../Aliyun/AliyunBlobProvider.cs | 136 +++++++++--------- .../Abp/BlobManagement/BlobAppServiceBase.cs | 12 +- .../Tencent/TencentBlobProvider.cs | 88 +++++++----- 9 files changed, 269 insertions(+), 157 deletions(-) diff --git a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs index f87b007ca..7071db02d 100644 --- a/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs +++ b/aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs @@ -98,20 +98,19 @@ public abstract class AliyunClientFactory assumeRoleResponse.Body.Credentials.AccessKeySecret, assumeRoleResponse.Body.Credentials.SecurityToken); - var expirationTimeSpan = TimeSpan.FromSeconds(durationSeconds - 10); + var cacheOptions = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds - 10), + }; if (DateTime.TryParse(assumeRoleResponse.Body.Credentials.Expiration, out var expiration)) { cacheItem.Expiration = expiration; - expirationTimeSpan = new TimeSpan(expiration.AddSeconds(-10).Ticks); } await Cache.SetAsync( cacheKey, cacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = expirationTimeSpan, - }); + cacheOptions); } return cacheItem; diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs index 555810724..1e8ffa978 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System; +using System.Text.RegularExpressions; using Volo.Abp.BlobStoring; using Volo.Abp.DependencyInjection; @@ -14,7 +15,10 @@ public class AliyunBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDepen /// public virtual string NormalizeBlobName(string blobName) { - return blobName; + // 不能以正斜线(/)或者反斜线(\)开头。 + return blobName + .RemovePreFix("/") + .RemovePreFix("\\"); } /// 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 af8bd175d..4aa760801 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 @@ -35,7 +35,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency public override async Task DeleteAsync(BlobProviderDeleteArgs args) { - var ossClient = await GetOssClientAsync(args); + using var ossClient = await GetOssClientAsync(args); var blobName = AliyunBlobNameCalculator.Calculate(args); if (await BlobExistsAsync(ossClient, args, blobName)) @@ -55,7 +55,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency public override async Task ExistsAsync(BlobProviderExistsArgs args) { - var ossClient = await GetOssClientAsync(args); + using var ossClient = await GetOssClientAsync(args); var blobName = AliyunBlobNameCalculator.Calculate(args); return await BlobExistsAsync(ossClient, args, blobName); @@ -63,40 +63,39 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency public override async Task GetOrNullAsync(BlobProviderGetArgs args) { - var ossClient = await GetOssClientAsync(args); + using var ossClient = await GetOssClientAsync(args); var blobName = AliyunBlobNameCalculator.Calculate(args); - if (!await BlobExistsAsync(ossClient, args, blobName)) - { - return null; - } - - var result = await ossClient.GetObjectAsync( - new GetObjectRequest - { - Bucket = GetBucketName(args), - Key = blobName, - }); - return result.Body; + //if (!await BlobExistsAsync(ossClient, args, blobName)) + //{ + // return null; + //} - // TODO: 阿里云sdk预签名不可用[2026/05/23] - //var configuration = args.Configuration.GetAliyunConfiguration(); - //var presignResult = ossClient.Presign( + //var result = await ossClient.GetObjectAsync( // new GetObjectRequest // { // Bucket = GetBucketName(args), // Key = blobName, - // }, - // Clock.Now.AddSeconds(configuration.PresignedGetExpirySeconds)); + // }); + //return result.Body; + + var configuration = args.Configuration.GetAliyunConfiguration(); + var presignResult = ossClient.Presign( + new GetObjectRequest + { + Bucket = GetBucketName(args), + Key = blobName, + }, + Clock.Now.AddSeconds(configuration.PresignedGetExpirySeconds)); - //var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + var httpClient = HttpClientFactory.CreateAliyunHttpClient(); - //return await httpClient.GetStreamAsync(presignResult.Url, args.CancellationToken); + return await httpClient.GetStreamAsync(presignResult.Url, args.CancellationToken); } public override async Task SaveAsync(BlobProviderSaveArgs args) { - var ossClient = await GetOssClientAsync(args); + using var ossClient = await GetOssClientAsync(args); var blobName = AliyunBlobNameCalculator.Calculate(args); var configuration = args.Configuration.GetAliyunConfiguration(); 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 2401174e1..8cdf5e126 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 @@ -6,6 +6,22 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun; public class AliyunBlobProviderConfiguration { + /// + /// 默认签名版本 + /// + public const string DefaultSignatureVersion = "v1"; //TODO: 阿里云 OSS SDKv2 SignerV4.ResourcePath存在缺陷, 临时使用v1签名 + /// + /// 默认跳过服务器证书验证 + /// + public const bool DefaultInsecureSkipVerify = false; + /// + /// 默认命名空间不存在是否创建 + /// + public const bool DefaultCreateBucketIfNotExists = false; + /// + /// 默认预签名链接过期时间 + /// + public const int DefaultPresignedGetExpirySeconds = 600; /// /// 命名空间 /// @@ -15,10 +31,18 @@ public class AliyunBlobProviderConfiguration set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, value); } /// + /// 签名版本(可选项:v1、v4) + /// 默认: v1 + /// + public string SignatureVersion { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.SignatureVersion, DefaultSignatureVersion); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.SignatureVersion, value); + } + /// /// 跳过服务器证书验证 /// public bool InsecureSkipVerify { - get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, false); + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, DefaultInsecureSkipVerify); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, value); } /// @@ -26,7 +50,7 @@ public class AliyunBlobProviderConfiguration /// public bool CreateBucketIfNotExists { - get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, false); + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, DefaultCreateBucketIfNotExists); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value); } /// @@ -55,15 +79,44 @@ public class AliyunBlobProviderConfiguration } } - /// - /// Default value: 7 * 24 * 3600. - /// + public int PresignedGetExpirySeconds { - get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds); + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, DefaultPresignedGetExpirySeconds); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, value); } - private int _defaultExpirySeconds = 7 * 24 * 3600; + public string Endpoint { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.Endpoint, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, value); + } + public bool? UsePathStyle { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UsePathStyle, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UsePathStyle, value); + } + public bool? UseCName { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseCName, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseCName, value); + } + public bool? UseDualStackEndpoint { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseDualStackEndpoint, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseDualStackEndpoint, value); + } + public bool? UseAccelerateEndpoint { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseAccelerateEndpoint, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseAccelerateEndpoint, value); + } + public bool? UseInternalEndpoint { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseInternalEndpoint, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseInternalEndpoint, value); + } + public bool? DisableUploadCrc64Check { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.DisableUploadCrc64Check, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DisableUploadCrc64Check, value); + } + public bool? DisableDownloadCrc64Check { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.DisableDownloadCrc64Check, null); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DisableDownloadCrc64Check, value); + } private readonly 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 d0bc9f7a7..feb2d7ced 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs @@ -11,6 +11,10 @@ public static class AliyunBlobProviderConfigurationNames /// public const string BucketName = "Aliyun:OSS:BucketName"; /// + /// 签名版本(可选项:v1、v4) + /// + public const string SignatureVersion = "Aliyun:OSS:SignatureVersion"; + /// /// 跳过服务器证书验证 /// public const string InsecureSkipVerify = "Aliyun:OSS:InsecureSkipVerify"; @@ -30,4 +34,12 @@ public static class AliyunBlobProviderConfigurationNames /// 生成预签名Uri的过期时间(s) /// public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds"; + + public const string UsePathStyle = "Aliyun:OSS:UsePathStyle"; + public const string UseCName = "Aliyun:OSS:UseCName"; + public const string UseDualStackEndpoint = "Aliyun:OSS:UseDualStackEndpoint"; + public const string UseAccelerateEndpoint = "Aliyun:OSS:UseAccelerateEndpoint"; + public const string UseInternalEndpoint = "Aliyun:OSS:UseInternalEndpoint"; + public const string DisableUploadCrc64Check = "Aliyun:OSS:DisableUploadCrc64Check"; + public const string DisableDownloadCrc64Check = "Aliyun:OSS:DisableDownloadCrc64Check"; } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs index 6fc4da256..9faa9ccdb 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs @@ -1,5 +1,6 @@ using AlibabaCloud.OSS.V2; using AlibabaCloud.OSS.V2.Credentials; +using AlibabaCloud.OSS.V2.Transport; using LINGYUN.Abp.Aliyun; using System; using Volo.Abp.Caching; @@ -31,10 +32,10 @@ public class OssClientFactory : AliyunClientFactory - { - return new Credentials(accessKeyId, accessKeySecret); - }), + SignatureVersion = AliyunBlobProviderConfiguration.DefaultSignatureVersion, + InsecureSkipVerify = AliyunBlobProviderConfiguration.DefaultInsecureSkipVerify, + CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret), + HttpTransport = HttpTransport.Shared, }); //return new OssClient( // regionId, @@ -56,11 +57,18 @@ public class OssClientFactory : AliyunClientFactory - { - return new Credentials(accessKeyId, accessKeySecret); - }), + Endpoint = configuration.Endpoint, + SignatureVersion = configuration.SignatureVersion, InsecureSkipVerify = configuration.InsecureSkipVerify, + UseCName = configuration.UseCName, + UsePathStyle = configuration.UsePathStyle, + UseAccelerateEndpoint = configuration.UseAccelerateEndpoint, + UseDualStackEndpoint = configuration.UseDualStackEndpoint, + UseInternalEndpoint = configuration.UseInternalEndpoint, + DisableUploadCrc64Check = configuration.DisableUploadCrc64Check, + DisableDownloadCrc64Check = configuration.DisableDownloadCrc64Check, + CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret), + HttpTransport = HttpTransport.Shared, }); } @@ -83,10 +91,10 @@ public class OssClientFactory : AliyunClientFactory - { - return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration); - }), + SignatureVersion = AliyunBlobProviderConfiguration.DefaultSignatureVersion, + InsecureSkipVerify = AliyunBlobProviderConfiguration.DefaultInsecureSkipVerify, + CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret, securityToken), + HttpTransport = HttpTransport.Shared, }); } /// @@ -110,11 +118,18 @@ public class OssClientFactory : AliyunClientFactory - { - return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration); - }), + Endpoint = configuration.Endpoint, + SignatureVersion = configuration.SignatureVersion, InsecureSkipVerify = configuration.InsecureSkipVerify, + UseCName = configuration.UseCName, + UsePathStyle = configuration.UsePathStyle, + UseAccelerateEndpoint = configuration.UseAccelerateEndpoint, + UseDualStackEndpoint = configuration.UseDualStackEndpoint, + UseInternalEndpoint = configuration.UseInternalEndpoint, + DisableUploadCrc64Check = configuration.DisableUploadCrc64Check, + DisableDownloadCrc64Check = configuration.DisableDownloadCrc64Check, + CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret, securityToken), + HttpTransport = HttpTransport.Shared, }); } } 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 fa6af872e..e368e1bda 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 @@ -49,7 +49,7 @@ public class AliyunBlobProvider : IBlobProvider string name, CancellationToken cancellationToken = default) { - var client = await CreateClientAsync(); + using var client = await CreateClientAsync(); var bucket = NormalizeContainerName(name); var configuration = GetBlobConfiguration(); @@ -60,7 +60,7 @@ public class AliyunBlobProvider : IBlobProvider string name, CancellationToken cancellationToken = default) { - var ossClient = await CreateClientAsync(); + using var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(name); if (!await BucketExists(ossClient, bucket, cancellationToken)) @@ -81,7 +81,7 @@ public class AliyunBlobProvider : IBlobProvider string blobName, CancellationToken cancellationToken = default) { - var ossClient = await CreateClientAsync(); + using var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); var objectName = CalculateBlobName(blobName); @@ -111,40 +111,39 @@ public class AliyunBlobProvider : IBlobProvider string blobName, CancellationToken cancellationToken = default) { - var ossClient = await CreateClientAsync(); - var bucket = NormalizeContainerName(containerName); - var objectName = CalculateBlobName(blobName); + //using var ossClient = await CreateClientAsync(); + //var bucket = NormalizeContainerName(containerName); + //var objectName = CalculateBlobName(blobName); - if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) - { - return null; - } + //if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) + //{ + // return null; + //} - var result = await ossClient.GetObjectAsync( - new GetObjectRequest - { - Bucket = bucket, - Key = blobName, - }); + //var result = await ossClient.GetObjectAsync( + // new GetObjectRequest + // { + // Bucket = bucket, + // Key = objectName, + // }); - return result.Body; + //return result.Body; - // TODO: 阿里云sdk预签名不可用[2026/05/23] - //var configuration = GetBlobConfiguration(); + var configuration = GetBlobConfiguration(); - //var downloadUrl = await GeneratePresignedUrlAsync( - // containerName, - // blobName, - // TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), - // cancellationToken: cancellationToken); - //if (downloadUrl.IsNullOrWhiteSpace()) - //{ - // return null; - //} + var downloadUrl = await InternalGeneratePresignedUrlAsync( + containerName, + blobName, + TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), + cancellationToken: cancellationToken); + if (downloadUrl.IsNullOrWhiteSpace()) + { + return null; + } - //var httpClient = HttpClientFactory.CreateAliyunHttpClient(); + var httpClient = HttpClientFactory.CreateAliyunHttpClient(); - //return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); + return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); } public virtual Task GeneratePresignedUrlAsync( @@ -154,32 +153,14 @@ public class AliyunBlobProvider : IBlobProvider bool isAttachmentContent = true, CancellationToken cancellationToken = default) { - // TODO: 阿里云sdk预签名不可用[2026/05/23] + // TODO: 阿里云SDK2.0不支持Bucket跨域配置, 不启用阿里云的预览方式 + //return InternalGeneratePresignedUrlAsync( + // containerName, + // blobName, + // expiration, + // isAttachmentContent, + // cancellationToken); return Task.FromResult(null); - //var ossClient = await CreateClientAsync(); - //var bucket = NormalizeContainerName(containerName); - //var objectName = CalculateBlobName(blobName); - - //if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) - //{ - // return null; - //} - - //var fileName = Path.GetFileName(blobName); - //var type = isAttachmentContent ? "attachment" : "inline"; - //var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " + - // $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}"; - - //var presignResult = ossClient.Presign( - // new GetObjectRequest - // { - // Bucket = bucket, - // Key = blobName, - // ResponseContentDisposition = disposition, - // }, - // Clock.Now.Add(expiration)); - - //return presignResult.Url; } public virtual Task CreateFolderAsync( @@ -199,7 +180,7 @@ public class AliyunBlobProvider : IBlobProvider string? contentType = null, CancellationToken cancellationToken = default) { - var ossClient = await CreateClientAsync(); + using var ossClient = await CreateClientAsync(); var bucket = NormalizeContainerName(containerName); var objectName = CalculateBlobName(blobName); var configuration = GetBlobConfiguration(); @@ -241,20 +222,43 @@ public class AliyunBlobProvider : IBlobProvider new PutBucketRequest { Bucket = bucket, + Acl = bucketAcl?.GetString(), }, cancellationToken: cancellationToken); + } + } - if (bucketAcl.HasValue) - { - await ossClient.PutBucketAclAsync( - new PutBucketAclRequest - { - Bucket = bucket, - Acl = bucketAcl.Value.GetString(), - }, - cancellationToken: cancellationToken); - } + protected async virtual Task InternalGeneratePresignedUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + bool isAttachmentContent = true, + CancellationToken cancellationToken = default) + { + using var ossClient = await CreateClientAsync(); + var bucket = NormalizeContainerName(containerName); + var objectName = CalculateBlobName(blobName); + + if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) + { + return null; } + + var fileName = Path.GetFileName(blobName); + var type = isAttachmentContent ? "attachment" : "inline"; + var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " + + $"filename*=UTF-8''{Uri.EscapeDataString(fileName)}"; + + var presignResult = ossClient.Presign( + new GetObjectRequest + { + Bucket = bucket, + Key = objectName, + ResponseContentDisposition = disposition, + }, + Clock.Now.Add(expiration)); + + return presignResult.Url; } protected async virtual Task BucketExists( 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 638234cf8..ecff6b46d 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 @@ -64,7 +64,11 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService var stream = await BlobManager.DownloadBlobsync(blob); - return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); + return new RemoteStreamContent( + stream ?? Stream.Null, + blob.Name, + blob.ContentType, + stream != null ? blob.Size : null); } public async virtual Task GetAsync(Guid id) @@ -169,7 +173,11 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService var stream = await BlobManager.DownloadBlobsync(blob); - return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length); + return new RemoteStreamContent( + stream ?? Stream.Null, + blob.Name, + blob.ContentType, + stream != null ? blob.Size : null); } protected async virtual Task GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true) 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 acdac8f47..54ae39fa8 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 @@ -104,7 +104,7 @@ public class TencentBlobProvider : IBlobProvider { var configuration = await GetBlobConfiguration(); - var downloadUrl = await GeneratePresignedUrlAsync( + var downloadUrl = await InternalGeneratePresignedUrlAsync( containerName, blobName, TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), @@ -119,11 +119,61 @@ public class TencentBlobProvider : IBlobProvider return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); } - public async virtual Task GeneratePresignedUrlAsync( + public virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, - bool isAttachmentContent = true, + bool isAttachmentContent = true, + CancellationToken cancellationToken = default) + { + // TODO: 腾讯云需用户开通对象处理服务以支持预览, 不启用腾讯云的预览方式. + //return InternalGeneratePresignedUrlAsync( + // containerName, + // blobName, + // expiration, + // isAttachmentContent, + // cancellationToken); + + return Task.FromResult(null); + } + + public virtual Task CreateFolderAsync( + string containerName, + string blobName, + CancellationToken cancellationToken = default) + { + // 腾讯云Oss没有目录的概念,新建对象时可以模拟目录 + // https://cloud.tencent.com/document/product/436/13324 + return Task.CompletedTask; + } + + public async virtual Task UploadBlobAsync( + string containerName, + string blobName, + Stream content, + string? contentType = null, + CancellationToken cancellationToken = default) + { + var client = await CreateClientAsync(); + var bucket = NormalizeContainerName(containerName); + var objectName = CalculateBlobName(blobName); + + CreateBucketIfNotExists(client, bucket); + + var putObjectRequest = new PutObjectRequest(bucket, objectName, content); + if (!contentType.IsNullOrWhiteSpace()) + { + putObjectRequest.SetRequestHeader("Content-Type", contentType); + } + + client.PutObject(putObjectRequest); + } + + protected async virtual Task InternalGeneratePresignedUrlAsync( + string containerName, + string blobName, + TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { var client = await CreateClientAsync(); @@ -161,38 +211,6 @@ public class TencentBlobProvider : IBlobProvider return client.GenerateSignURL(preSignatureStruct); } - public virtual Task CreateFolderAsync( - string containerName, - string blobName, - CancellationToken cancellationToken = default) - { - // 腾讯云Oss没有目录的概念,新建对象时可以模拟目录 - // https://cloud.tencent.com/document/product/436/13324 - return Task.CompletedTask; - } - - public async virtual Task UploadBlobAsync( - string containerName, - string blobName, - Stream content, - string? contentType = null, - CancellationToken cancellationToken = default) - { - var client = await CreateClientAsync(); - var bucket = NormalizeContainerName(containerName); - var objectName = CalculateBlobName(blobName); - - CreateBucketIfNotExists(client, bucket); - - var putObjectRequest = new PutObjectRequest(bucket, objectName, content); - if (!contentType.IsNullOrWhiteSpace()) - { - putObjectRequest.SetRequestHeader("Content-Type", contentType); - } - - client.PutObject(putObjectRequest); - } - protected async virtual Task CreateClientAsync() { return await CosClientFactory.CreateAsync();