Browse Source

feat: Optimize BlobManagement module

pull/1489/head
colin 4 months ago
parent
commit
62aa357051
  1. 1
      Directory.Packages.props
  2. 177
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs
  3. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs
  4. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs
  5. 8
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs
  6. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs
  7. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs
  8. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs
  9. 25
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs
  10. 58
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs
  11. 8
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs
  12. 10
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs
  13. 8
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs
  14. 42
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs
  15. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs
  16. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs
  17. 5
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs
  18. 31
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs
  19. 2
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs
  20. 1
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs
  21. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs
  22. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs
  23. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs
  24. 3
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs
  25. 41
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs
  26. 4
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs
  27. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs
  28. 12
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs
  29. 24
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs
  30. 16
      aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs
  31. 1
      aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj

1
Directory.Packages.props

@ -354,6 +354,7 @@
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageVersion Include="Magicodes.IE.Excel" Version="2.8.2" />
<PackageVersion Include="Markdig" Version="0.44.0" />
<PackageVersion Include="MimeCheck" Version="1.0.0" />
<PackageVersion Include="MiniExcel" Version="1.43.0" />
<PackageVersion Include="MiniWord" Version="0.9.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />

177
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Aliyun/LINGYUN/Abp/BlobManagement/Aliyun/AliyunBlobProvider.cs

@ -1,4 +1,5 @@
using Aliyun.OSS;
using AlibabaCloud.OSS.V2;
using AlibabaCloud.OSS.V2.Models;
using LINGYUN.Abp.BlobStoring.Aliyun;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -50,23 +51,29 @@ public class AliyunBlobProvider : IBlobProvider
{
var client = await CreateClientAsync();
var bucket = NormalizeContainerName(name);
var configuration = GetBlobConfiguration();
CreateBucketIfNotExists(client, bucket);
await CreateBucketIfNotExists(client, bucket, configuration.CreateBucketAcl, cancellationToken);
}
public async virtual Task DeleteContainerAsync(
string name,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(name);
if (!BucketExists(client, bucket))
if (!await BucketExists(ossClient, bucket, cancellationToken))
{
return;
}
client.DeleteBucket(bucket);
await ossClient.DeleteBucketAsync(
new DeleteBucketRequest
{
Bucket = bucket,
},
cancellationToken: cancellationToken);
}
public async virtual Task DeleteBlobAsync(
@ -74,13 +81,19 @@ public class AliyunBlobProvider : IBlobProvider
string blobName,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
if (ObjectExists(client, bucket, objectName))
if (await ObjectExists(ossClient, bucket, objectName, cancellationToken))
{
client.DeleteObject(bucket, objectName);
await ossClient.DeleteObjectAsync(
new DeleteObjectRequest
{
Bucket = bucket,
Key = objectName,
},
cancellationToken: cancellationToken);
}
}
@ -98,39 +111,75 @@ public class AliyunBlobProvider : IBlobProvider
string blobName,
CancellationToken cancellationToken = default)
{
var configuration = GetBlobConfiguration();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
var downloadUrl = await GenerateDownloadUrlAsync(
containerName,
blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
cancellationToken);
if (downloadUrl.IsNullOrWhiteSpace())
if (!await ObjectExists(ossClient, bucket, objectName, cancellationToken))
{
return null;
}
var httpClient = HttpClientFactory.CreateAliyunHttpClient();
var result = await ossClient.GetObjectAsync(
new GetObjectRequest
{
Bucket = bucket,
Key = blobName,
});
return result.Body;
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
// TODO: 阿里云sdk预签名不可用[2026/05/23]
//var configuration = GetBlobConfiguration();
//var downloadUrl = await GeneratePresignedUrlAsync(
// containerName,
// blobName,
// TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
// cancellationToken: cancellationToken);
//if (downloadUrl.IsNullOrWhiteSpace())
//{
// return null;
//}
//var httpClient = HttpClientFactory.CreateAliyunHttpClient();
//return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public async virtual Task<string?> GenerateDownloadUrlAsync(
public virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
if (!ObjectExists(client, bucket, objectName))
{
return null;
}
return client.GeneratePresignedUri(bucket, objectName, Clock.Now.Add(expiration)).ToString();
// TODO: 阿里云sdk预签名不可用[2026/05/23]
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(
@ -150,49 +199,79 @@ public class AliyunBlobProvider : IBlobProvider
string? contentType = null,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
var ossClient = await CreateClientAsync();
var bucket = NormalizeContainerName(containerName);
var objectName = CalculateBlobName(blobName);
var configuration = GetBlobConfiguration();
CreateBucketIfNotExists(client, bucket);
var objectMetadata = new ObjectMetadata();
if (!contentType.IsNullOrWhiteSpace())
{
objectMetadata.ContentType = contentType;
}
client.PutObject(bucket, objectName, content, objectMetadata);
await CreateBucketIfNotExists(ossClient, bucket, configuration.CreateBucketAcl, cancellationToken);
await ossClient.PutObjectAsync(
new PutObjectRequest
{
Bucket = bucket,
Key = objectName,
Body = content,
ContentType = contentType,
},
cancellationToken: cancellationToken);
}
protected virtual AliyunBlobProviderConfiguration GetBlobConfiguration()
{
var configuration = ConfigurationProvider.Get<BlobManagementContainer>();
var blobConfiguration = configuration.GetAliyunConfiguration();
return blobConfiguration;
return configuration.GetAliyunConfiguration();
}
protected async virtual Task<IOss> CreateClientAsync()
protected async virtual Task<Client> CreateClientAsync()
{
return await OssClientFactory.CreateAsync();
var configuration = GetBlobConfiguration();
return await OssClientFactory.CreateAsync(configuration);
}
protected virtual void CreateBucketIfNotExists(IOss oss, string bucket)
protected async virtual Task CreateBucketIfNotExists(
Client ossClient,
string bucket,
BucketAclType? bucketAcl = null,
CancellationToken cancellationToken = default)
{
if (!BucketExists(oss, bucket))
if (!await BucketExists(ossClient, bucket, cancellationToken))
{
oss.CreateBucket(bucket);
await ossClient.PutBucketAsync(
new PutBucketRequest
{
Bucket = bucket,
},
cancellationToken: cancellationToken);
if (bucketAcl.HasValue)
{
await ossClient.PutBucketAclAsync(
new PutBucketAclRequest
{
Bucket = bucket,
Acl = bucketAcl.Value.GetString(),
},
cancellationToken: cancellationToken);
}
}
}
protected virtual bool BucketExists(IOss oss, string bucket)
protected async virtual Task<bool> BucketExists(
Client ossClient,
string bucket,
CancellationToken cancellationToken = default)
{
return oss.DoesBucketExist(bucket);
return await ossClient.IsBucketExistAsync(bucket, cancellationToken);
}
protected virtual bool ObjectExists(IOss oss, string bucket, string objectName)
protected async virtual Task<bool> ObjectExists(
Client ossClient,
string bucket,
string objectName,
CancellationToken cancellationToken = default)
{
return oss.DoesObjectExist(bucket, objectName);
return await ossClient.IsObjectExistAsync(bucket, objectName, cancellationToken: cancellationToken);
}
protected virtual string GetPrefixPath()

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs → aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs

@ -3,10 +3,10 @@ using System.ComponentModel.DataAnnotations;
namespace LINGYUN.Abp.BlobManagement.Dtos;
public class BlobDownloadByKeyInput
public class BlobDownloadByIdInput
{
public Guid? TenantId { get; set; }
[Required]
public string Key { get; set; }
public Guid Id { get; set; }
}

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs

@ -1,10 +1,13 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.BlobManagement.Dtos;
public class BlobDownloadByNameInput
{
public Guid? TenantId { get; set; }
[Required]
[DynamicStringLength(typeof(BlobContainerConsts), nameof(BlobContainerConsts.MaxNameLength))]
public string ContainerName { get; set; }

8
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs

@ -17,13 +17,15 @@ public interface IBlobAppService : IApplicationService
Task DeleteAsync(Guid id);
Task<string> GeneratePreviewUrlAsync(Guid id);
Task<string> GenerateDownloadUrlAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByKeyInput input);
Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input);
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input);
Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input);
Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input);
Task<BlobDto> GetAsync(Guid id);

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs

@ -17,9 +17,9 @@ public interface IPrivateBlobAppService : IApplicationService
Task DeleteAsync(Guid id);
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(string name);
Task<IRemoteStreamContent> DownloadByNameAsync(string name);
Task<BlobDto> GetAsync(Guid id);

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs

@ -9,9 +9,9 @@ namespace LINGYUN.Abp.BlobManagement;
public interface IPublicBlobAppService : IApplicationService
{
Task<IRemoteStreamContent> GetContentAsync(Guid id);
Task<IRemoteStreamContent> DownloadAsync(Guid id);
Task<IRemoteStreamContent> GetContentByNameAsync(string name);
Task<IRemoteStreamContent> DownloadByNameAsync(string name);
Task<BlobDto> GetAsync(Guid id);

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs

@ -16,5 +16,5 @@ public interface IBlobIntegrationService : IApplicationService
Task<bool> ExistsAsync(BlobFileGetByNameIntegrationDto input);
Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input);
Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input);
}

25
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs

@ -47,11 +47,30 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService
return await base.CreateFolderAsync(blobContainer, input);
}
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input)
public async virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
return await base.DownloadAsync(input.Id);
}
}
public async virtual Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input)
{
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
return await base.DownloadAsync(input.Id);
}
}
public async virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input)
{
using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id))
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
return await base.GetContentByNameAsync(blobContainer, input.BlobName);
return await base.DownloadByNameAsync(blobContainer, input.BlobName);
}
}
protected async override Task CheckGetPolicyAsync(string containerName, Blob blob)

58
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppServiceBase.cs

@ -48,43 +48,15 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
public async virtual Task<string> GenerateDownloadUrlAsync(Guid id)
{
var blob = await BlobRepository.GetAsync(id);
var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId);
await CheckGetPolicyAsync(blob);
var fallbackUrlPrefix = blob.TenantId.HasValue
? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download/t/{blob.TenantId:N}"
: $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download";
var downloadUrl = await BlobManager.GenerateDownloadUrlAsync(
blobContainer,
blob,
fallbackUrlPrefix);
return downloadUrl;
return await GenerateDownloadUrlAsync(id, "download");
}
public async virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByKeyInput input)
public async virtual Task<string> GeneratePreviewUrlAsync(Guid id)
{
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);
}
return await GenerateDownloadUrlAsync(id, "preview", isAttachmentContent: false);
}
public async virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
public async virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
var blob = await BlobRepository.GetAsync(id);
@ -185,7 +157,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
return ObjectMapper.Map<Blob, BlobDto>(blob);
}
protected async virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobContainer blobContainer, string name)
protected async virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobContainer blobContainer, string name)
{
var blob = await FindBlobByNameAsync(blobContainer, name)
?? throw new BusinessException(
@ -200,6 +172,26 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService
return new RemoteStreamContent(stream ?? Stream.Null, blob.Name, blob.ContentType, stream?.Length);
}
protected async virtual Task<string> GenerateDownloadUrlAsync(Guid id, string method, bool isAttachmentContent = true)
{
var blob = await BlobRepository.GetAsync(id);
var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId);
await CheckGetPolicyAsync(blob);
var fallbackDownloadUrl = blob.TenantId.HasValue
? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/t/{blob.TenantId:N}/{blob.Id:N}"
: $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/{method}/{blob.Id:N}";
var downloadUrl = await BlobManager.GenerateDownloadUrlAsync(
blobContainer,
blob,
fallbackDownloadUrl,
isAttachmentContent);
return downloadUrl;
}
protected async virtual Task<Blob?> FindBlobByNameAsync(BlobContainer blobContainer, string name)
{
var blobName = HttpUtility.UrlDecode(name);

8
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs

@ -42,11 +42,11 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
return await base.CreateFolderAsync(blobContainer, input);
}
public async virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
public async virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), ContainerName);
return await base.GetContentByNameAsync(blobContainer, name);
return await base.DownloadByNameAsync(blobContainer, name);
}
public async virtual Task<PagedResultDto<BlobDto>> GetListAsync(BlobGetPagedListWithoutContainerInput input)
@ -67,7 +67,7 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase
protected async virtual Task<BlobContainer> GetOrCreateContainer()
{
var blobContainer = await BlobContainerRepository.FindByNameAsync(ContainerName)
var blobContainer = await BlobManager.FindContainerAsync(ContainerName)
?? await BlobManager.CreateContainerAsync(ContainerName);
return blobContainer;

10
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs

@ -28,7 +28,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
public async virtual Task<BlobDto> CreateAsync(BlobFileCreateIntegrationDto input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
// Example: /a/b/c/d/f.txt
var blobName = HttpUtility.UrlDecode(input.BlobName);
@ -63,7 +63,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
public async virtual Task DeleteAsync(BlobFileGetByNameIntegrationDto input)
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
var blob = await FindBlobByNameAsync(blobContainer, input.BlobName)
?? throw new BusinessException(
@ -90,12 +90,12 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic
}
}
public async virtual Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input)
public async virtual Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input)
{
try
{
var blobContainer = await BlobContainerRepository.GetByNameAsync(input.ContainerName);
return await GetContentByNameAsync(blobContainer, input.BlobName);
var blobContainer = await BlobContainerRepository.GetByNameAsync(BlobManager.GetBlobProvider(), input.ContainerName);
return await DownloadByNameAsync(blobContainer, input.BlobName);
}
catch (Exception ex)
{

8
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs

@ -6,6 +6,7 @@ namespace LINGYUN.Abp.BlobManagement;
public class BlobDownloadEto : IMultiTenant
{
public Guid? TenantId { get; set; }
public string Provider { get; set; }
public string ContainerName { get; set; }
public string FullName { get; set; }
public BlobDownloadEto()
@ -13,8 +14,13 @@ public class BlobDownloadEto : IMultiTenant
}
public BlobDownloadEto(string containerName, string fullName, Guid? tenantId = null)
public BlobDownloadEto(
string provider,
string containerName,
string fullName,
Guid? tenantId = null)
{
Provider = provider;
ContainerName = containerName;
FullName = fullName;
TenantId = tenantId;

42
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
@ -67,6 +68,14 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
this.SetDefaultsForExtraProperties();
}
public void MoveTo(Blob parentBlob)
{
ValidateSubBlob(parentBlob, Name);
ParentId = parentBlob.Id;
parentBlob.Blobs.Add(this);
parentBlob.CalculateSize();
}
public void SetFullName(string fullName)
{
FullName = Check.NotNullOrWhiteSpace(fullName, nameof(fullName), BlobConsts.MaxFullNameLength);
@ -99,20 +108,7 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
BlobType type,
long? size = null)
{
if (Type != BlobType.Folder)
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NonFolderChildBlob,
"Sub-files/directories cannot be added to non-directory types!");
}
if (HasBlob(name))
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NameAlreadyExists,
$"There is already a file/directory named {name} in the current directory!")
.WithData("Name", name);
}
ValidateSubBlob(this, name);
var blob = new Blob(
id,
@ -183,4 +179,22 @@ public class Blob : AuditedAggregateRoot<Guid>, IMultiTenant
return blobPath.EnsureEndsWith('/') + blobName.RemovePreFix("/");
}
public static void ValidateSubBlob(Blob parentBlob, string subBlobName)
{
if (parentBlob.Type != BlobType.Folder)
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NonFolderChildBlob,
"Sub-files/directories cannot be added to non-directory types!");
}
if (parentBlob.HasBlob(subBlobName))
{
throw new BusinessException(
BlobManagementErrorCodes.Blob.NameAlreadyExists,
$"There is already a file/directory named {subBlobName} in the current directory!")
.WithData("Name", subBlobName);
}
}
}

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs

@ -41,7 +41,7 @@ public class BlobDataSeeder : IBlobDataSeeder, ITransientDependency
{
Logger.LogInformation("Seeding container {containerName}...", containerName);
if (await BlobContainerRepository.FindByNameAsync(containerName) == null)
if (await BlobManager.FindContainerAsync(containerName) == null)
{
await BlobManager.CreateContainerAsync(containerName);
}

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs

@ -37,7 +37,7 @@ public class BlobDownloadIncreaser :
return;
}
var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.ContainerName);
var blobContainer = await _blobContainerRepository.FindByNameAsync(eventData.Provider, eventData.ContainerName);
if (blobContainer == null)
{
return;

5
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs

@ -16,9 +16,4 @@ public class BlobDownloadKeyCacheItem
BlobId = blobId;
Url = url;
}
public static string CalculateCacheKey(Guid blobId, string blobName)
{
return $"{blobId:N}{blobName.RemovePreFix("/")}";
}
}

31
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs

@ -62,11 +62,16 @@ public class BlobManager : DomainService
_settingProvider = settingProvider;
}
public async virtual Task<BlobContainer> CreateContainerAsync(string name)
public async virtual Task<BlobContainer?> FindContainerAsync(string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
if (await _blobContainerRepository.FindByNameAsync(name) != null)
return await _blobContainerRepository.FindByNameAsync(GetBlobProvider(), name);
}
public async virtual Task<BlobContainer> CreateContainerAsync(string name)
{
if (await FindContainerAsync(name) != null)
{
throw new BusinessException(
BlobManagementErrorCodes.Container.NameAlreadyExists,
@ -81,7 +86,7 @@ public class BlobManager : DomainService
await _blobProvider.CreateContainerAsync(blobContainer.Name, GetCancellationToken());
blobContainer.SetProvider(_blobProvider.Name);
blobContainer.SetProvider(GetBlobProvider());
await _blobContainerRepository.InsertAsync(blobContainer);
@ -153,7 +158,7 @@ public class BlobManager : DomainService
{
await _blobProvider.CreateFolderAsync(blobContainer.Name, blob.FullName, GetCancellationToken());
blob.SetProvider(_blobProvider.Name);
blob.SetProvider(GetBlobProvider());
}
await _blobRepository.InsertAsync(blob);
@ -227,7 +232,7 @@ public class BlobManager : DomainService
contentType,
GetCancellationToken());
blob.SetProvider(_blobProvider.Name);
blob.SetProvider(GetBlobProvider());
blob.SetFileInfo(contentType, stream.Length);
return blob;
@ -292,7 +297,7 @@ public class BlobManager : DomainService
}
else
{
blobContainer = await _blobContainerRepository.GetByNameAsync(containerIdOrName);
blobContainer = await _blobContainerRepository.GetByNameAsync(GetBlobProvider(), containerIdOrName);
}
if (parentId.HasValue)
{
@ -338,9 +343,10 @@ public class BlobManager : DomainService
public async virtual Task<string> GenerateDownloadUrlAsync(
BlobContainer blobContainer,
Blob blob,
string fallbackUrlPrefix)
string fallbackDownloadUrl,
bool isAttachmentContent = true)
{
var cacheKey = BlobDownloadKeyCacheItem.CalculateCacheKey(blob.Id, blob.FullName);
var cacheKey = $"{fallbackDownloadUrl.ToMd5()}";
var cacheItem = await _blobDownloadKeyCache.GetAsync(cacheKey);
if (cacheItem == null)
{
@ -348,16 +354,17 @@ public class BlobManager : DomainService
BlobManagementSettingNames.GenerateDownloadUrlExpirySeconds,
BlobManagementSettingNames.DefaultGenerateDownloadUrlExpirySeconds);
var downloadUrl = await _blobProvider.GenerateDownloadUrlAsync(
var downloadUrl = await _blobProvider.GeneratePresignedUrlAsync(
blobContainer.Name,
blob.FullName,
TimeSpan.FromSeconds(downloadUrlExpirySeconds),
isAttachmentContent,
GetCancellationToken());
if (downloadUrl.IsNullOrWhiteSpace())
{
// 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url前缀
downloadUrl = fallbackUrlPrefix.EnsureEndsWith('/') + cacheKey;
// 特殊对象存储提供者(FileSystem)无法生成下载链接, 回退指定的请求Url
downloadUrl = fallbackDownloadUrl;
}
cacheItem = new BlobDownloadKeyCacheItem(
@ -403,7 +410,7 @@ public class BlobManager : DomainService
downloadCacheItem.DownloadCount += 1;
await _blobDownloadCache.SetAsync(downloadCacheKey, downloadCacheItem);
await _distributedEventBus.PublishAsync(new BlobDownloadEto(containerName, blobName, CurrentTenant.Id));
await _distributedEventBus.PublishAsync(new BlobDownloadEto(GetBlobProvider(), containerName, blobName, CurrentTenant.Id));
}
return blobStream;

2
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs

@ -50,7 +50,7 @@ public class BlobTempCleanupService : ITransientDependency
try
{
var tempBlobContainer = await BlobContainerRepository.FindByNameAsync("temp");
var tempBlobContainer = await BlobManager.FindContainerAsync("temp");
if (tempBlobContainer == null)
{
Logger.LogInformation($"No temp container found.");

1
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs

@ -14,6 +14,7 @@ public interface IBlobContainerRepository : IBasicRepository<BlobContainer, Guid
CancellationToken cancellationToken = default);
Task<BlobContainer?> FindByNameAsync(
string provider,
string name,
CancellationToken cancellationToken = default);

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs

@ -8,10 +8,11 @@ public static class IBlobContainerRepositoryExtenssions
{
public async static Task<BlobContainer> GetByNameAsync(
this IBlobContainerRepository repository,
string provider,
string name,
CancellationToken cancellationToken = default)
{
return await repository.FindByNameAsync(name, cancellationToken)
return await repository.FindByNameAsync(provider, name, cancellationToken)
?? throw new BusinessException(
BlobManagementErrorCodes.Container.NameNotFound,
$"There is no blob container named {name}!")

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -22,10 +23,11 @@ public interface IBlobProvider
string blobName,
CancellationToken cancellationToken = default);
Task<string?> GenerateDownloadUrlAsync(
Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default);
Task CreateFolderAsync(

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs

@ -30,11 +30,12 @@ public class EfCoreBlobContainerRepository : EfCoreRepository<IBlobManagementDbC
}
public async virtual Task<BlobContainer?> FindByNameAsync(
string provider,
string name,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(x => x.Name == name)
.Where(x => x.Provider == provider && x.Name == name)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}

3
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs

@ -99,10 +99,11 @@ public class FileSystemBlobProvider : IBlobProvider
return Task.FromResult<Stream?>(File.OpenRead(fileName));
}
public virtual Task<string?> GenerateDownloadUrlAsync(
public virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
// 本地文件系统无需生成下载链接

41
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.BlobManagement.Dtos;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using System;
using System.Threading.Tasks;
using Volo.Abp;
@ -50,16 +51,34 @@ public class BlobController : BlobControllerBase, IBlobAppService
return _service.DeleteAsync(id);
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
[HttpGet("download/t/{tenantId}/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(BlobDownloadByIdInput input)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(input);
}
[HttpGet("preview/{id}")]
[HttpGet("preview/t/{tenantId}/{id}")]
public async virtual Task<IRemoteStreamContent> PreviewAsync(BlobDownloadByIdInput input)
{
var content = await _service.PreviewAsync(input);
if (!content.FileName.IsNullOrWhiteSpace())
{
var contentDisposition = new ContentDispositionHeaderValue("inline");
contentDisposition.SetHttpFileName(content.FileName);
HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
}
return content;
}
[HttpGet("content")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(BlobDownloadByNameInput input)
[HttpGet("download/by-name/{name}")]
[HttpGet("download/by-name/t/{tenantId}/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(BlobDownloadByNameInput input)
{
return _service.GetContentByNameAsync(input);
return _service.DownloadByNameAsync(input);
}
[HttpGet("{id}")]
@ -74,17 +93,15 @@ public class BlobController : BlobControllerBase, IBlobAppService
return _service.GetListAsync(input);
}
[HttpGet("{id}/generate-url")]
[HttpGet("generate/download-url/{id}")]
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)
[HttpGet("generate/preview-url/{id}")]
public virtual Task<string> GeneratePreviewUrlAsync(Guid id)
{
return _service.DownloadAsync(input);
return _service.GeneratePreviewUrlAsync(id);
}
}

4
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs

@ -40,8 +40,8 @@ public class BlobIntegrationController : AbpControllerBase, IBlobIntegrationServ
}
[HttpGet("download")]
public virtual Task<IRemoteStreamContent> GetContentAsync(BlobFileGetByNameIntegrationDto input)
public virtual Task<IRemoteStreamContent> DownloadAsync(BlobFileGetByNameIntegrationDto input)
{
return _service.GetContentAsync(input);
return _service.DownloadAsync(input);
}
}

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs

@ -50,16 +50,16 @@ public class PrivateBlobController : BlobControllerBase, IPrivateBlobAppService
return _service.DeleteAsync(id);
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(id);
}
[HttpGet("content/{name}")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
[HttpGet("download/by-name/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
return _service.GetContentByNameAsync(name);
return _service.DownloadByNameAsync(name);
}
[HttpGet("{id}")]

12
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs

@ -22,16 +22,16 @@ public class PublicBlobController : BlobControllerBase, IPublicBlobAppService
_service = service;
}
[HttpGet("uid/{id}/content")]
public virtual Task<IRemoteStreamContent> GetContentAsync(Guid id)
[HttpGet("download/{id}")]
public virtual Task<IRemoteStreamContent> DownloadAsync(Guid id)
{
return _service.GetContentAsync(id);
return _service.DownloadAsync(id);
}
[HttpGet("content/{name}")]
public virtual Task<IRemoteStreamContent> GetContentByNameAsync(string name)
[HttpGet("download/by-name/{name}")]
public virtual Task<IRemoteStreamContent> DownloadByNameAsync(string name)
{
return _service.GetContentByNameAsync(name);
return _service.DownloadByNameAsync(name);
}
[HttpGet("{id}")]

24
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs

@ -4,6 +4,7 @@ using Minio;
using Minio.DataModel.Args;
using Minio.Exceptions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
@ -108,11 +109,11 @@ public class MinioBlobProvider : IBlobProvider
{
var configuration = GetBlobConfiguration();
var downloadUrl = await GenerateDownloadUrlAsync(
var downloadUrl = await GeneratePresignedUrlAsync(
containerName,
blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
cancellationToken);
cancellationToken: cancellationToken);
if (downloadUrl.IsNullOrWhiteSpace())
{
return null;
@ -123,10 +124,11 @@ public class MinioBlobProvider : IBlobProvider
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public async virtual Task<string?> GenerateDownloadUrlAsync(
public async virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
var client = GetMinioClient();
@ -138,11 +140,21 @@ public class MinioBlobProvider : IBlobProvider
return null;
}
return await client.PresignedGetObjectAsync(
new PresignedGetObjectArgs()
var presignedGetObjectArgs = new PresignedGetObjectArgs()
.WithBucket(bucket)
.WithObject(objectName)
.WithExpiry(expiration.TotalSeconds.To<int>()));
.WithExpiry(expiration.TotalSeconds.To<int>());
var fileName = Path.GetFileName(blobName);
var type = isAttachmentContent ? "attachment" : "inline";
var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " +
$"filename*=UTF-8''{Uri.EscapeDataString(fileName)}";
presignedGetObjectArgs.WithHeaders(new Dictionary<string, string>()
{
{ "response-content-disposition", disposition },
});
return await client.PresignedGetObjectAsync(presignedGetObjectArgs);
}
public virtual Task CreateFolderAsync(

16
aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Tencent/LINGYUN/Abp/BlobManagement/Tencent/TencentBlobProvider.cs

@ -6,6 +6,7 @@ using LINGYUN.Abp.BlobStoring.Tencent;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
@ -103,11 +104,11 @@ public class TencentBlobProvider : IBlobProvider
{
var configuration = await GetBlobConfiguration();
var downloadUrl = await GenerateDownloadUrlAsync(
var downloadUrl = await GeneratePresignedUrlAsync(
containerName,
blobName,
TimeSpan.FromSeconds(configuration.PresignedGetExpirySeconds),
cancellationToken);
cancellationToken: cancellationToken);
if (downloadUrl.IsNullOrWhiteSpace())
{
return null;
@ -118,10 +119,11 @@ public class TencentBlobProvider : IBlobProvider
return await httpClient.GetStreamAsync(downloadUrl, cancellationToken);
}
public async virtual Task<string?> GenerateDownloadUrlAsync(
public async virtual Task<string?> GeneratePresignedUrlAsync(
string containerName,
string blobName,
TimeSpan expiration,
bool isAttachmentContent = true,
CancellationToken cancellationToken = default)
{
var client = await CreateClientAsync();
@ -147,6 +149,14 @@ public class TencentBlobProvider : IBlobProvider
headers = null, //签名中需要校验的 header
queryParameters = null //签名中需要校验的 URL 中请求参数
};
var fileName = Path.GetFileName(blobName);
var type = isAttachmentContent ? "attachment" : "inline";
var disposition = $"{type}; filename=\"{Uri.EscapeDataString(fileName)}\"; " +
$"filename*=UTF-8''{Uri.EscapeDataString(fileName)}";
preSignatureStruct.queryParameters = new Dictionary<string, string>
{
{ "response-content-disposition", disposition },
};
return client.GenerateSignURL(preSignatureStruct);
}

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

@ -6,6 +6,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>LY.MicroService.Applications.Single</RootNamespace>
<UserSecretsId>A47DB958-CC3F-430B-A18A-57AA535BAB49</UserSecretsId>
<ImplicitUsings>enable</ImplicitUsings>
<Version>10.2.0</Version>
</PropertyGroup>

Loading…
Cancel
Save