Browse Source

feat: Optimize BlobProvider

- 阿里云OSS SDK v4签名存在缺陷, 默认使用v1签名
- 阿里云OSS SDK v2不支持CORS配置, 默认不开启预览, 使用后台代理链接预览文件
- 阿里云BlobProvider增加多个适配SDK v2的配置项
- 腾讯云OSS默认不支持文件预览功能, 需要额外开通数据处理服务, 默认不开启预览, 使用后台代理链接预览文件
pull/1490/head
colin 4 days ago
parent
commit
b4960fa2e4
  1. 11
      aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs
  2. 8
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs
  3. 45
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  4. 67
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  5. 12
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs
  6. 47
      aspnet-core/framework/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs
  7. 136
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs
  8. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs
  9. 88
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs

11
aspnet-core/framework/cloud-aliyun/LINGYUN.Abp.Aliyun/LINGYUN/Abp/Aliyun/AliyunClientFactory.cs

@ -98,20 +98,19 @@ public abstract class AliyunClientFactory<TClient>
assumeRoleResponse.Body.Credentials.AccessKeySecret, assumeRoleResponse.Body.Credentials.AccessKeySecret,
assumeRoleResponse.Body.Credentials.SecurityToken); 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)) if (DateTime.TryParse(assumeRoleResponse.Body.Credentials.Expiration, out var expiration))
{ {
cacheItem.Expiration = expiration; cacheItem.Expiration = expiration;
expirationTimeSpan = new TimeSpan(expiration.AddSeconds(-10).Ticks);
} }
await Cache.SetAsync( await Cache.SetAsync(
cacheKey, cacheKey,
cacheItem, cacheItem,
new DistributedCacheEntryOptions cacheOptions);
{
AbsoluteExpirationRelativeToNow = expirationTimeSpan,
});
} }
return cacheItem; return cacheItem;

8
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.BlobStoring;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -14,7 +15,10 @@ public class AliyunBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDepen
/// <returns></returns> /// <returns></returns>
public virtual string NormalizeBlobName(string blobName) public virtual string NormalizeBlobName(string blobName)
{ {
return blobName; // 不能以正斜线(/)或者反斜线(\)开头。
return blobName
.RemovePreFix("/")
.RemovePreFix("\\");
} }
/// <summary> /// <summary>

45
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<bool> DeleteAsync(BlobProviderDeleteArgs args) public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{ {
var ossClient = await GetOssClientAsync(args); using var ossClient = await GetOssClientAsync(args);
var blobName = AliyunBlobNameCalculator.Calculate(args); var blobName = AliyunBlobNameCalculator.Calculate(args);
if (await BlobExistsAsync(ossClient, args, blobName)) if (await BlobExistsAsync(ossClient, args, blobName))
@ -55,7 +55,7 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args) public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{ {
var ossClient = await GetOssClientAsync(args); using var ossClient = await GetOssClientAsync(args);
var blobName = AliyunBlobNameCalculator.Calculate(args); var blobName = AliyunBlobNameCalculator.Calculate(args);
return await BlobExistsAsync(ossClient, args, blobName); return await BlobExistsAsync(ossClient, args, blobName);
@ -63,40 +63,39 @@ public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args) public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{ {
var ossClient = await GetOssClientAsync(args); using var ossClient = await GetOssClientAsync(args);
var blobName = AliyunBlobNameCalculator.Calculate(args); var blobName = AliyunBlobNameCalculator.Calculate(args);
if (!await BlobExistsAsync(ossClient, args, blobName)) //if (!await BlobExistsAsync(ossClient, args, blobName))
{ //{
return null; // return null;
} //}
var result = await ossClient.GetObjectAsync(
new GetObjectRequest
{
Bucket = GetBucketName(args),
Key = blobName,
});
return result.Body;
// TODO: 阿里云sdk预签名不可用[2026/05/23] //var result = await ossClient.GetObjectAsync(
//var configuration = args.Configuration.GetAliyunConfiguration();
//var presignResult = ossClient.Presign(
// new GetObjectRequest // new GetObjectRequest
// { // {
// Bucket = GetBucketName(args), // Bucket = GetBucketName(args),
// Key = blobName, // 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) public override async Task SaveAsync(BlobProviderSaveArgs args)
{ {
var ossClient = await GetOssClientAsync(args); using var ossClient = await GetOssClientAsync(args);
var blobName = AliyunBlobNameCalculator.Calculate(args); var blobName = AliyunBlobNameCalculator.Calculate(args);
var configuration = args.Configuration.GetAliyunConfiguration(); var configuration = args.Configuration.GetAliyunConfiguration();

67
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 class AliyunBlobProviderConfiguration
{ {
/// <summary>
/// 默认签名版本
/// </summary>
public const string DefaultSignatureVersion = "v1"; //TODO: 阿里云 OSS SDKv2 SignerV4.ResourcePath存在缺陷, 临时使用v1签名
/// <summary>
/// 默认跳过服务器证书验证
/// </summary>
public const bool DefaultInsecureSkipVerify = false;
/// <summary>
/// 默认命名空间不存在是否创建
/// </summary>
public const bool DefaultCreateBucketIfNotExists = false;
/// <summary>
/// 默认预签名链接过期时间
/// </summary>
public const int DefaultPresignedGetExpirySeconds = 600;
/// <summary> /// <summary>
/// 命名空间 /// 命名空间
/// </summary> /// </summary>
@ -15,10 +31,18 @@ public class AliyunBlobProviderConfiguration
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, value); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.BucketName, value);
} }
/// <summary> /// <summary>
/// 签名版本(可选项:v1、v4)
/// 默认: v1
/// </summary>
public string SignatureVersion {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.SignatureVersion, DefaultSignatureVersion);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.SignatureVersion, value);
}
/// <summary>
/// 跳过服务器证书验证 /// 跳过服务器证书验证
/// </summary> /// </summary>
public bool InsecureSkipVerify { public bool InsecureSkipVerify {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, false); get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, DefaultInsecureSkipVerify);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, value); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.InsecureSkipVerify, value);
} }
/// <summary> /// <summary>
@ -26,7 +50,7 @@ public class AliyunBlobProviderConfiguration
/// </summary> /// </summary>
public bool CreateBucketIfNotExists public bool CreateBucketIfNotExists
{ {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, false); get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, DefaultCreateBucketIfNotExists);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateBucketIfNotExists, value);
} }
/// <summary> /// <summary>
@ -55,15 +79,44 @@ public class AliyunBlobProviderConfiguration
} }
} }
/// <summary>
/// Default value: 7 * 24 * 3600.
/// </summary>
public int PresignedGetExpirySeconds { public int PresignedGetExpirySeconds {
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, _defaultExpirySeconds); get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, DefaultPresignedGetExpirySeconds);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, value); set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.PresignedGetExpirySeconds, value);
} }
private int _defaultExpirySeconds = 7 * 24 * 3600; public string Endpoint {
get => _containerConfiguration.GetConfigurationOrDefault<string>(AliyunBlobProviderConfigurationNames.Endpoint, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, value);
}
public bool? UsePathStyle {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.UsePathStyle, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UsePathStyle, value);
}
public bool? UseCName {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.UseCName, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseCName, value);
}
public bool? UseDualStackEndpoint {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.UseDualStackEndpoint, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseDualStackEndpoint, value);
}
public bool? UseAccelerateEndpoint {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.UseAccelerateEndpoint, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseAccelerateEndpoint, value);
}
public bool? UseInternalEndpoint {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.UseInternalEndpoint, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseInternalEndpoint, value);
}
public bool? DisableUploadCrc64Check {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.DisableUploadCrc64Check, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DisableUploadCrc64Check, value);
}
public bool? DisableDownloadCrc64Check {
get => _containerConfiguration.GetConfigurationOrDefault<bool?>(AliyunBlobProviderConfigurationNames.DisableDownloadCrc64Check, null);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DisableDownloadCrc64Check, value);
}
private readonly BlobContainerConfiguration _containerConfiguration; private readonly BlobContainerConfiguration _containerConfiguration;

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

@ -11,6 +11,10 @@ public static class AliyunBlobProviderConfigurationNames
/// </summary> /// </summary>
public const string BucketName = "Aliyun:OSS:BucketName"; public const string BucketName = "Aliyun:OSS:BucketName";
/// <summary> /// <summary>
/// 签名版本(可选项:v1、v4)
/// </summary>
public const string SignatureVersion = "Aliyun:OSS:SignatureVersion";
/// <summary>
/// 跳过服务器证书验证 /// 跳过服务器证书验证
/// </summary> /// </summary>
public const string InsecureSkipVerify = "Aliyun:OSS:InsecureSkipVerify"; public const string InsecureSkipVerify = "Aliyun:OSS:InsecureSkipVerify";
@ -30,4 +34,12 @@ public static class AliyunBlobProviderConfigurationNames
/// 生成预签名Uri的过期时间(s) /// 生成预签名Uri的过期时间(s)
/// </summary> /// </summary>
public const string PresignedGetExpirySeconds = "Aliyun:OSS:PresignedGetExpirySeconds"; 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";
} }

47
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;
using AlibabaCloud.OSS.V2.Credentials; using AlibabaCloud.OSS.V2.Credentials;
using AlibabaCloud.OSS.V2.Transport;
using LINGYUN.Abp.Aliyun; using LINGYUN.Abp.Aliyun;
using System; using System;
using Volo.Abp.Caching; using Volo.Abp.Caching;
@ -31,10 +32,10 @@ public class OssClientFactory : AliyunClientFactory<Client, AliyunBlobProviderCo
new Configuration new Configuration
{ {
Region = regionId, Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() => SignatureVersion = AliyunBlobProviderConfiguration.DefaultSignatureVersion,
{ InsecureSkipVerify = AliyunBlobProviderConfiguration.DefaultInsecureSkipVerify,
return new Credentials(accessKeyId, accessKeySecret); CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret),
}), HttpTransport = HttpTransport.Shared,
}); });
//return new OssClient( //return new OssClient(
// regionId, // regionId,
@ -56,11 +57,18 @@ public class OssClientFactory : AliyunClientFactory<Client, AliyunBlobProviderCo
new Configuration new Configuration
{ {
Region = regionId, Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() => Endpoint = configuration.Endpoint,
{ SignatureVersion = configuration.SignatureVersion,
return new Credentials(accessKeyId, accessKeySecret);
}),
InsecureSkipVerify = configuration.InsecureSkipVerify, 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<Client, AliyunBlobProviderCo
new Configuration new Configuration
{ {
Region = regionId, Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() => SignatureVersion = AliyunBlobProviderConfiguration.DefaultSignatureVersion,
{ InsecureSkipVerify = AliyunBlobProviderConfiguration.DefaultInsecureSkipVerify,
return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration); CredentialsProvider = new StaticCredentialsProvider(accessKeyId, accessKeySecret, securityToken),
}), HttpTransport = HttpTransport.Shared,
}); });
} }
/// <summary> /// <summary>
@ -110,11 +118,18 @@ public class OssClientFactory : AliyunClientFactory<Client, AliyunBlobProviderCo
new Configuration new Configuration
{ {
Region = regionId, Region = regionId,
CredentialsProvider = new CredentialsProviderFunc(() => Endpoint = configuration.Endpoint,
{ SignatureVersion = configuration.SignatureVersion,
return new Credentials(accessKeyId, accessKeySecret, securityToken, expiration);
}),
InsecureSkipVerify = configuration.InsecureSkipVerify, 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,
}); });
} }
} }

136
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, string name,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); using var client = await CreateClientAsync();
var bucket = NormalizeContainerName(name); var bucket = NormalizeContainerName(name);
var configuration = GetBlobConfiguration(); var configuration = GetBlobConfiguration();
@ -60,7 +60,7 @@ public class AliyunBlobProvider : IBlobProvider
string name, string name,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var ossClient = await CreateClientAsync(); using var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(name); var bucket = NormalizeContainerName(name);
if (!await BucketExists(ossClient, bucket, cancellationToken)) if (!await BucketExists(ossClient, bucket, cancellationToken))
@ -81,7 +81,7 @@ public class AliyunBlobProvider : IBlobProvider
string blobName, string blobName,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var ossClient = await CreateClientAsync(); using var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName); var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName); var objectName = CalculateBlobName(blobName);
@ -111,40 +111,39 @@ public class AliyunBlobProvider : IBlobProvider
string blobName, string blobName,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var ossClient = await CreateClientAsync(); //using var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName); //var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName); //var objectName = CalculateBlobName(blobName);
if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken)) //if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken))
{ //{
return null; // return null;
} //}
var result = await ossClient.GetObjectAsync( //var result = await ossClient.GetObjectAsync(
new GetObjectRequest // new GetObjectRequest
{ // {
Bucket = bucket, // Bucket = bucket,
Key = blobName, // Key = objectName,
}); // });
return result.Body; //return result.Body;
// TODO: 阿里云sdk预签名不可用[2026/05/23] var configuration = GetBlobConfiguration();
//var configuration = GetBlobConfiguration();
//var downloadUrl = await GeneratePresignedUrlAsync( var downloadUrl = await InternalGeneratePresignedUrlAsync(
// containerName, containerName,
// blobName, blobName,
// TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
// cancellationToken: cancellationToken); cancellationToken: cancellationToken);
//if (downloadUrl.IsNullOrWhiteSpace()) if (downloadUrl.IsNullOrWhiteSpace())
//{ {
// return null; 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<string?> GeneratePresignedUrlAsync( public virtual Task<string?> GeneratePresignedUrlAsync(
@ -154,32 +153,14 @@ public class AliyunBlobProvider : IBlobProvider
bool isAttachmentContent = true, bool isAttachmentContent = true,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// TODO: 阿里云sdk预签名不可用[2026/05/23] // TODO: 阿里云SDK2.0不支持Bucket跨域配置, 不启用阿里云的预览方式
//return InternalGeneratePresignedUrlAsync(
// containerName,
// blobName,
// expiration,
// isAttachmentContent,
// cancellationToken);
return Task.FromResult<string?>(null); return Task.FromResult<string?>(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( public virtual Task CreateFolderAsync(
@ -199,7 +180,7 @@ public class AliyunBlobProvider : IBlobProvider
string? contentType = null, string? contentType = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var ossClient = await CreateClientAsync(); using var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName); var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName); var objectName = CalculateBlobName(blobName);
var configuration = GetBlobConfiguration(); var configuration = GetBlobConfiguration();
@ -241,20 +222,43 @@ public class AliyunBlobProvider : IBlobProvider
new PutBucketRequest new PutBucketRequest
{ {
Bucket = bucket, Bucket = bucket,
Acl = bucketAcl?.GetString(),
}, },
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
}
}
if (bucketAcl.HasValue) protected async virtual Task<string?> InternalGeneratePresignedUrlAsync(
{ string containerName,
await ossClient.PutBucketAclAsync( string blobName,
new PutBucketAclRequest TimeSpan expiration,
{ bool isAttachmentContent = true,
Bucket = bucket, CancellationToken cancellationToken = default)
Acl = bucketAcl.Value.GetString(), {
}, using var ossClient = await CreateClientAsync();
cancellationToken: cancellationToken); 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<bool> BucketExists( protected async virtual Task<bool> BucketExists(

12
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); 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<BlobDto> GetAsync(Guid id) public async virtual Task<BlobDto> GetAsync(Guid id)
@ -169,7 +173,11 @@ 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, stream?.Length); return new RemoteStreamContent(
stream ?? Stream.Null,
blob.Name,
blob.ContentType,
stream != null ? blob.Size : null);
} }
protected async virtual Task<string> GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true) protected async virtual Task<string> GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true)

88
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 configuration = await GetBlobConfiguration();
var downloadUrl = await GeneratePresignedUrlAsync( var downloadUrl = await InternalGeneratePresignedUrlAsync(
containerName, containerName,
blobName, blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds), TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
@ -119,11 +119,61 @@ public class TencentBlobProvider : IBlobProvider
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken); return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
} }
public async virtual Task<string?> GeneratePresignedUrlAsync( public virtual Task<string?> GeneratePresignedUrlAsync(
string containerName, string containerName,
string blobName, string blobName,
TimeSpan expiration, TimeSpan expiration,
bool isAttachmentContent = true, bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
// TODO: 腾讯云需用户开通对象处理服务以支持预览, 不启用腾讯云的预览方式.
//return InternalGeneratePresignedUrlAsync(
// containerName,
// blobName,
// expiration,
// isAttachmentContent,
// cancellationToken);
return Task.FromResult<string?>(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<string?> InternalGeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var client = await CreateClientAsync(); var client = await CreateClientAsync();
@ -161,38 +211,6 @@ public class TencentBlobProvider : IBlobProvider
return client.GenerateSignURL(preSignatureStruct); 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<CosXml> CreateClientAsync() protected async virtual Task<CosXml> CreateClientAsync()
{ {
return await CosClientFactory.CreateAsync<BlobManagementContainer>(); return await CosClientFactory.CreateAsync<BlobManagementContainer>();

Loading…
Cancel
Save