From 62aa3570518a96a35aebf864284096f8c03feecc Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 28 May 2026 09:41:32 +0800 Subject: [PATCH] feat: Optimize BlobManagement module --- Directory.Packages.props | 1 + .../Aliyun/AliyunBlobProvider.cs | 177 +++++++++++++----- ...ByKeyInput.cs => BlobDownloadByIdInput.cs} | 4 +- .../Dtos/BlobDownloadByNameInput.cs | 5 +- .../Abp/BlobManagement/IBlobAppService.cs | 8 +- .../BlobManagement/IPrivateBlobAppService.cs | 4 +- .../BlobManagement/IPublicBlobAppService.cs | 4 +- .../Integration/IBlobIntegrationService.cs | 2 +- .../Abp/BlobManagement/BlobAppService.cs | 25 ++- .../Abp/BlobManagement/BlobAppServiceBase.cs | 58 +++--- .../BlobWithoutContainerAppService.cs | 8 +- .../Integration/BlobIntegrationService.cs | 10 +- .../Abp/BlobManagement/BlobDownloadEto.cs | 8 +- .../LINGYUN/Abp/BlobManagement/Blob.cs | 42 +++-- .../Abp/BlobManagement/BlobDataSeeder.cs | 2 +- .../BlobManagement/BlobDownloadIncreaser.cs | 2 +- .../BlobDownloadKeyCacheItem.cs | 5 - .../LINGYUN/Abp/BlobManagement/BlobManager.cs | 31 +-- .../BlobManagement/BlobTempCleanupService.cs | 2 +- .../IBlobContainerRepository.cs | 1 + .../IBlobContainerRepositoryExtenssions.cs | 3 +- .../Abp/BlobManagement/IBlobProvider.cs | 4 +- .../EfCoreBlobContainerRepository.cs | 3 +- .../FileSystem/FileSystemBlobProvider.cs | 3 +- .../Abp/BlobManagement/BlobController.cs | 41 ++-- .../Integration/BlobIntegrationController.cs | 4 +- .../BlobManagement/PrivateBlobController.cs | 12 +- .../BlobManagement/PublicBlobController.cs | 12 +- .../BlobManagement/Minio/MinioBlobProvider.cs | 24 ++- .../Tencent/TencentBlobProvider.cs | 16 +- ...LY.MicroService.Applications.Single.csproj | 1 + 31 files changed, 343 insertions(+), 179 deletions(-) rename aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/{BlobDownloadByKeyInput.cs => BlobDownloadByIdInput.cs} (69%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7ef269c33..7c7d36b3d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -354,6 +354,7 @@ + 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 912320b7a..fa6af872e 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 @@ -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 GenerateDownloadUrlAsync( + public virtual Task 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(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(); - var blobConfiguration = configuration.GetAliyunConfiguration(); - return blobConfiguration; + return configuration.GetAliyunConfiguration(); } - protected async virtual Task CreateClientAsync() + protected async virtual Task 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 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 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() diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs similarity index 69% rename from aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs rename to aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByIdInput.cs index fb5da0020..4ac8d1f43 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByKeyInput.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/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; } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs index 77715bb07..06663bc67 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Dtos/BlobDownloadByNameInput.cs +++ b/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; } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs index 2d4679569..4ed63da58 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IBlobAppService.cs @@ -17,13 +17,15 @@ public interface IBlobAppService : IApplicationService Task DeleteAsync(Guid id); + Task GeneratePreviewUrlAsync(Guid id); + Task GenerateDownloadUrlAsync(Guid id); - Task DownloadAsync(BlobDownloadByKeyInput input); + Task DownloadAsync(BlobDownloadByIdInput input); - Task GetContentAsync(Guid id); + Task PreviewAsync(BlobDownloadByIdInput input); - Task GetContentByNameAsync(BlobDownloadByNameInput input); + Task DownloadByNameAsync(BlobDownloadByNameInput input); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs index 9d25f2704..7164d938a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPrivateBlobAppService.cs +++ b/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 GetContentAsync(Guid id); + Task DownloadAsync(Guid id); - Task GetContentByNameAsync(string name); + Task DownloadByNameAsync(string name); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs index dff540187..c381482c4 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/IPublicBlobAppService.cs +++ b/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 GetContentAsync(Guid id); + Task DownloadAsync(Guid id); - Task GetContentByNameAsync(string name); + Task DownloadByNameAsync(string name); Task GetAsync(Guid id); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs index 40a1fa835..ce105fec2 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application.Contracts/LINGYUN/Abp/BlobManagement/Integration/IBlobIntegrationService.cs +++ b/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 ExistsAsync(BlobFileGetByNameIntegrationDto input); - Task GetContentAsync(BlobFileGetByNameIntegrationDto input); + Task DownloadAsync(BlobFileGetByNameIntegrationDto input); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs index 689395ef4..ecafc86c1 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobAppService.cs @@ -47,11 +47,30 @@ public class BlobAppService : BlobAppServiceBase, IBlobAppService return await base.CreateFolderAsync(blobContainer, input); } - public async virtual Task GetContentByNameAsync(BlobDownloadByNameInput input) + public async virtual Task 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 PreviewAsync(BlobDownloadByIdInput input) + { + using (CurrentTenant.Change(input.TenantId ?? CurrentTenant.Id)) + { + return await base.DownloadAsync(input.Id); + } + } + + public async virtual Task 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) 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 683e6b1ce..638234cf8 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 @@ -48,43 +48,15 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService public async virtual Task GenerateDownloadUrlAsync(Guid id) { - var blob = await BlobRepository.GetAsync(id); - var blobContainer = await BlobContainerRepository.GetAsync(blob.ContainerId); - - await CheckGetPolicyAsync(blob); - - var fallbackUrlPrefix = blob.TenantId.HasValue - ? $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download/t/{blob.TenantId:N}" - : $"/api/{BlobManagementRemoteServiceConsts.ModuleName}/blobs/download"; - - var downloadUrl = await BlobManager.GenerateDownloadUrlAsync( - blobContainer, - blob, - fallbackUrlPrefix); - - return downloadUrl; + return await GenerateDownloadUrlAsync(id, "download"); } - public async virtual Task DownloadAsync(BlobDownloadByKeyInput input) + public async virtual Task 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 GetContentAsync(Guid id) + public async virtual Task DownloadAsync(Guid id) { var blob = await BlobRepository.GetAsync(id); @@ -185,7 +157,7 @@ public abstract class BlobAppServiceBase : BlobManagementApplicationService return ObjectMapper.Map(blob); } - protected async virtual Task GetContentByNameAsync(BlobContainer blobContainer, string name) + protected async virtual Task 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 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 FindBlobByNameAsync(BlobContainer blobContainer, string name) { var blobName = HttpUtility.UrlDecode(name); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs index 66ed740d8..b4de50123 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/BlobWithoutContainerAppService.cs @@ -42,11 +42,11 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase return await base.CreateFolderAsync(blobContainer, input); } - public async virtual Task GetContentByNameAsync(string name) + public async virtual Task 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> GetListAsync(BlobGetPagedListWithoutContainerInput input) @@ -67,7 +67,7 @@ public abstract class BlobWithoutContainerAppService : BlobAppServiceBase protected async virtual Task GetOrCreateContainer() { - var blobContainer = await BlobContainerRepository.FindByNameAsync(ContainerName) + var blobContainer = await BlobManager.FindContainerAsync(ContainerName) ?? await BlobManager.CreateContainerAsync(ContainerName); return blobContainer; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs index 367da5ba9..b1c1cf24a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Application/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationService.cs @@ -28,7 +28,7 @@ public class BlobIntegrationService : BlobAppServiceBase, IBlobIntegrationServic public async virtual Task 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 GetContentAsync(BlobFileGetByNameIntegrationDto input) + public async virtual Task 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) { diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs index 03d5d2d0a..f5fa5f2fb 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain.Shared/LINGYUN/Abp/BlobManagement/BlobDownloadEto.cs +++ b/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; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs index 51258d7be..162938e81 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/Blob.cs +++ b/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, 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, 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, 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); + } + } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs index e83516959..703e0ac6c 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDataSeeder.cs +++ b/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); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs index bcf2da457..ca074afce 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadIncreaser.cs +++ b/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; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs index 7c001efa9..2f4914c91 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobDownloadKeyCacheItem.cs @@ -16,9 +16,4 @@ public class BlobDownloadKeyCacheItem BlobId = blobId; Url = url; } - - public static string CalculateCacheKey(Guid blobId, string blobName) - { - return $"{blobId:N}{blobName.RemovePreFix("/")}"; - } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs index 68df23011..bf2c0a00d 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobManager.cs @@ -62,11 +62,16 @@ public class BlobManager : DomainService _settingProvider = settingProvider; } - public async virtual Task CreateContainerAsync(string name) + public async virtual Task FindContainerAsync(string name) { Check.NotNullOrWhiteSpace(name, nameof(name)); - if (await _blobContainerRepository.FindByNameAsync(name) != null) + return await _blobContainerRepository.FindByNameAsync(GetBlobProvider(), name); + } + + public async virtual Task 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 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; diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs index c34de069f..8daa9c837 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/BlobTempCleanupService.cs +++ b/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."); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs index f80d6d228..1dc197e48 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepository.cs @@ -14,6 +14,7 @@ public interface IBlobContainerRepository : IBasicRepository FindByNameAsync( + string provider, string name, CancellationToken cancellationToken = default); diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs index b5795e01f..e967db5e0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobContainerRepositoryExtenssions.cs +++ b/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 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}!") diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs index 01a012279..61066f83b 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Domain/LINGYUN/Abp/BlobManagement/IBlobProvider.cs @@ -1,4 +1,5 @@ using System; +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 GenerateDownloadUrlAsync( + Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default); Task CreateFolderAsync( diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs index 145b304fe..aca9be141 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.EntityFrameworkCore/LINGYUN/Abp/BlobManagement/EntityFrameworkCore/EfCoreBlobContainerRepository.cs @@ -30,11 +30,12 @@ public class EfCoreBlobContainerRepository : EfCoreRepository 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)); } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs index 8fcbc8b0f..097e4138b 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.FileSystem/LINGYUN/Abp/BlobManagement/FileSystem/FileSystemBlobProvider.cs @@ -99,10 +99,11 @@ public class FileSystemBlobProvider : IBlobProvider return Task.FromResult(File.OpenRead(fileName)); } - public virtual Task GenerateDownloadUrlAsync( + public virtual Task GeneratePresignedUrlAsync( string containerName, string blobName, TimeSpan expiration, + bool isAttachmentContent = true, CancellationToken cancellationToken = default) { // 本地文件系统无需生成下载链接 diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs index 696f7d391..4cff719d0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/BlobController.cs @@ -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 GetContentAsync(Guid id) + [HttpGet("download/{id}")] + [HttpGet("download/t/{tenantId}/{id}")] + public virtual Task DownloadAsync(BlobDownloadByIdInput input) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(input); + } + + [HttpGet("preview/{id}")] + [HttpGet("preview/t/{tenantId}/{id}")] + public async virtual Task 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 GetContentByNameAsync(BlobDownloadByNameInput input) + [HttpGet("download/by-name/{name}")] + [HttpGet("download/by-name/t/{tenantId}/{name}")] + public virtual Task 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 GenerateDownloadUrlAsync(Guid id) { return _service.GenerateDownloadUrlAsync(id); } - [HttpGet] - [Route("download/{Key}")] - [Route("download/t/{TenantId}/{Key}")] - public virtual Task DownloadAsync(BlobDownloadByKeyInput input) + [HttpGet("generate/preview-url/{id}")] + public virtual Task GeneratePreviewUrlAsync(Guid id) { - return _service.DownloadAsync(input); + return _service.GeneratePreviewUrlAsync(id); } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs index dac00f68f..dc6b4efa0 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/Integration/BlobIntegrationController.cs +++ b/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 GetContentAsync(BlobFileGetByNameIntegrationDto input) + public virtual Task DownloadAsync(BlobFileGetByNameIntegrationDto input) { - return _service.GetContentAsync(input); + return _service.DownloadAsync(input); } } diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs index f8ec336a7..20a3b4c1d 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PrivateBlobController.cs +++ b/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 GetContentAsync(Guid id) + [HttpGet("download/{id}")] + public virtual Task DownloadAsync(Guid id) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(id); } - [HttpGet("content/{name}")] - public virtual Task GetContentByNameAsync(string name) + [HttpGet("download/by-name/{name}")] + public virtual Task DownloadByNameAsync(string name) { - return _service.GetContentByNameAsync(name); + return _service.DownloadByNameAsync(name); } [HttpGet("{id}")] diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs index aca0a128f..ab4610536 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.HttpApi/LINGYUN/Abp/BlobManagement/PublicBlobController.cs +++ b/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 GetContentAsync(Guid id) + [HttpGet("download/{id}")] + public virtual Task DownloadAsync(Guid id) { - return _service.GetContentAsync(id); + return _service.DownloadAsync(id); } - [HttpGet("content/{name}")] - public virtual Task GetContentByNameAsync(string name) + [HttpGet("download/by-name/{name}")] + public virtual Task DownloadByNameAsync(string name) { - return _service.GetContentByNameAsync(name); + return _service.DownloadByNameAsync(name); } [HttpGet("{id}")] diff --git a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs index 69414c0c9..a3922136a 100644 --- a/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs +++ b/aspnet-core/modules/blob-management/LINGYUN.Abp.BlobManagement.Minio/LINGYUN/Abp/BlobManagement/Minio/MinioBlobProvider.cs @@ -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 GenerateDownloadUrlAsync( + public async virtual Task 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())); + .WithExpiry(expiration.TotalSeconds.To()); + + 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() + { + { "response-content-disposition", disposition }, + }); + + return await client.PresignedGetObjectAsync(presignedGetObjectArgs); } public virtual Task CreateFolderAsync( 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 671525b8d..acdac8f47 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 @@ -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 GenerateDownloadUrlAsync( + public async virtual Task 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 + { + { "response-content-disposition", disposition }, + }; return client.GenerateSignURL(preSignatureStruct); } diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj index e04cb7fee..e238f4cc1 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj +++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj @@ -6,6 +6,7 @@ net10.0 enable LY.MicroService.Applications.Single + A47DB958-CC3F-430B-A18A-57AA535BAB49 enable 10.2.0