From 0b525e74d45fe1dfccf17919864c280159fd375c Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Tue, 9 Mar 2021 22:09:40 +0800 Subject: [PATCH] Optimize the OSS management interface and interface --- aspnet-core/LINGYUN.MicroService.All.sln | 23 +- aspnet-core/LINGYUN.MicroService.Platform.sln | 35 +- .../LINGYUN.Abp.BlobStoring.Aliyun.csproj | 2 +- .../BlobStoring/Aliyun/IOssClientFactory.cs | 5 + .../BlobStoring/Aliyun/OssClientFactory.cs | 14 +- .../LINGYUN.Abp.FileManagement.Aliyun.csproj | 13 + .../Aliyun/AbpFileManagementAliyunModule.cs | 17 + .../Aliyun/AliyunOssContainer.cs | 369 +++++++++++++ .../Aliyun/AliyunOssContainerFactory.cs | 26 + .../README.md | 15 + .../BulkDeleteOssObjectInput.cs | 15 + .../FileManagement/CreateOssObjectInput.cs | 19 + .../FileManagement/GetOssContainersInput.cs | 10 + .../Abp/FileManagement/GetOssObjectInput.cs | 15 + .../Abp/FileManagement/GetOssObjectsInput.cs | 13 + .../Abp/FileManagement/GetStaticFileInput.cs | 16 + .../FileManagement/IOssContainerAppService.cs | 18 + .../FileManagement/IOssObjectAppService.cs | 16 + .../FileManagement/IStaticFilesAppService.cs | 11 + .../Abp/FileManagement/OssContainerDto.cs | 14 + .../FileManagement/OssContainersResultDto.cs | 13 + .../Abp/FileManagement/OssObjectDto.cs | 16 + .../Abp/FileManagement/OssObjectsResultDto.cs | 15 + ...GYUN.Abp.FileManagement.Application.csproj | 4 + .../AbpFileManagementApplicationModule.cs | 13 +- ...eManagementApplicationAutoMapperProfile.cs | 17 + .../FileManagement/FileSystemAppService.cs | 10 +- .../FileManagement/OssContainerAppService.cs | 67 +++ .../Abp/FileManagement/OssObjectAppService.cs | 100 ++++ .../FileManagement/StaticFilesAppService.cs | 31 ++ .../AbpFileManagementDomainSharedModule.cs | 6 + .../FileManagementErrorCodes.cs | 19 + .../Localization/Resources/en.json | 6 + .../Localization/Resources/zh-Hans.json | 6 + .../Settings/AbpFileManagementSettingNames.cs | 4 + .../System/IO/StreamExtensions.cs | 12 + .../LINGYUN.Abp.FileManagement.Domain.csproj | 3 +- ...ainer.cs => AbpFileManagementContainer.cs} | 2 +- .../FileManagement/BulkDeleteObjectRequest.cs | 26 + .../FileManagement/CreateOssObjectRequest.cs | 35 ++ .../FileManagement/GetOssContainersRequest.cs | 18 + .../GetOssContainersResponse.cs | 28 + .../Abp/FileManagement/GetOssObjectRequest.cs | 31 ++ .../FileManagement/GetOssObjectsRequest.cs | 32 ++ .../FileManagement/GetOssObjectsResponse.cs | 33 ++ .../Abp/FileManagement/IOssContainer.cs | 72 +++ .../FileManagement/IOssContainerExtensions.cs | 79 +++ .../FileManagement/IOssContainerFactory.cs | 18 + .../Abp/FileManagement/OssContainer.cs | 31 ++ .../LINGYUN/Abp/FileManagement/OssObject.cs | 50 ++ .../Abp/FileManagement/OssObjectComparer.cs | 27 + ...ileManagement.FileSystem.ImageSharp.csproj | 16 + ...ileManagementFileSystemImageSharpModule.cs | 16 + .../ImageSharpFileSystemOssObjectProcesser.cs | 118 ++++ .../README.md | 15 + ...NGYUN.Abp.FileManagement.FileSystem.csproj | 17 + .../AbpFileManagementFileSystemModule.cs | 17 + .../FileSystem/FileSystemOssContainer.cs | 512 ++++++++++++++++++ .../FileSystemOssContainerFactory.cs | 46 ++ .../FileSystem/FileSystemOssObjectContext.cs | 32 ++ .../FileSystem/FileSystemOssOptions.cs | 19 + .../FileSystemOssOptionsExtensions.cs | 15 + .../FileSystem/FileSystemOssProviderArgs.cs | 18 + ...FileSystemOssObjectProcesserContributor.cs | 10 + .../NoneFileSystemOssObjectProcesser.cs | 15 + .../README.md | 15 + .../FileManagement/OssContainerController.cs | 56 ++ .../Abp/FileManagement/OssObjectController.cs | 81 +++ .../FileManagement/StaticFilesController.cs | 87 +++ .../FileManagement/UploadOssObjectInput.cs | 12 + .../LINGYUN.ApiGateway.Host/event-bus-cap.db | Bin 40960 -> 40960 bytes .../AppPlatformHttpApiHostModule.cs | 34 +- .../DomainTenantResolveContributor.cs | 40 ++ .../HeaderTenantResolveContributor.cs | 50 ++ .../LINGYUN.Platform.HttpApi.Host.csproj | 3 + .../TenantConfigurationProvider.cs | 66 +++ .../TenantResolver.cs | 48 ++ vueJs/src/api/oss-manager.ts | 129 +++++ .../directives/el-table-lazy-load/index.ts | 17 + vueJs/src/directives/index.ts | 1 + .../components/OssObjectProfile.vue | 162 ++++++ vueJs/src/views/oss-management/index.vue | 476 ++++++++++++++++ 82 files changed, 3600 insertions(+), 33 deletions(-) create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN.Abp.FileManagement.Aliyun.csproj create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AbpFileManagementAliyunModule.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainer.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainerFactory.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/README.md create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/BulkDeleteOssObjectInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/CreateOssObjectInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssContainersInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectsInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetStaticFileInput.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssContainerAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssObjectAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IStaticFilesAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainerDto.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainersResultDto.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectDto.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectsResultDto.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileManagementApplicationAutoMapperProfile.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssContainerAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssObjectAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/StaticFilesAppService.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/FileManagementErrorCodes.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/System/IO/StreamExtensions.cs rename aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/{FileSystemContainer.cs => AbpFileManagementContainer.cs} (75%) create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/BulkDeleteObjectRequest.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/CreateOssObjectRequest.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersRequest.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersResponse.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectRequest.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsRequest.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsResponse.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainer.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerExtensions.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerFactory.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssContainer.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObject.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObjectComparer.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp.csproj create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/AbpFileManagementFileSystemImageSharpModule.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/ImageSharpFileSystemOssObjectProcesser.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/README.md create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN.Abp.FileManagement.FileSystem.csproj create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/AbpFileManagementFileSystemModule.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainer.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainerFactory.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssObjectContext.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptions.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptionsExtensions.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssProviderArgs.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/IFileSystemOssObjectProcesserContributor.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/NoneFileSystemOssObjectProcesser.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/README.md create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssContainerController.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssObjectController.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/StaticFilesController.cs create mode 100644 aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/UploadOssObjectInput.cs create mode 100644 aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/DomainTenantResolveContributor.cs create mode 100644 aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/HeaderTenantResolveContributor.cs create mode 100644 aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantConfigurationProvider.cs create mode 100644 aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantResolver.cs create mode 100644 vueJs/src/api/oss-manager.ts create mode 100644 vueJs/src/directives/el-table-lazy-load/index.ts create mode 100644 vueJs/src/views/oss-management/components/OssObjectProfile.vue create mode 100644 vueJs/src/views/oss-management/index.vue diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 420af0943..51fbc136b 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -269,10 +269,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Tencent", "modu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.Sms", "modules\common\LINGYUN.Abp.Notifications.Sms\LINGYUN.Abp.Notifications.Sms.csproj", "{8C3312E7-F51E-4780-A893-CE0E0B80B579}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Aliyun.SettingManagement", "modules\cloud-aliyun\LINGYUN.Abp.Aliyun.SettingManagement\LINGYUN.Abp.Aliyun.SettingManagement.csproj", "{FE0F0889-C4AF-43C5-B851-B8CCC873BA2C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Aliyun.SettingManagement", "modules\cloud-aliyun\LINGYUN.Abp.Aliyun.SettingManagement\LINGYUN.Abp.Aliyun.SettingManagement.csproj", "{FE0F0889-C4AF-43C5-B851-B8CCC873BA2C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Aliyun.Tests", "tests\LINGYUN.Abp.Aliyun.Tests\LINGYUN.Abp.Aliyun.Tests.csproj", "{B86EBB6F-A27F-4277-8265-937951A9DCB0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.FileManagement.Aliyun", "modules\file-management\LINGYUN.Abp.FileManagement.Aliyun\LINGYUN.Abp.FileManagement.Aliyun.csproj", "{35B17218-9FB6-439E-AF73-9A1454BC923C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.FileManagement.FileSystem", "modules\file-management\LINGYUN.Abp.FileManagement.FileSystem\LINGYUN.Abp.FileManagement.FileSystem.csproj", "{D5036D3F-1C53-47EE-BA50-AD290AE062D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.FileManagement.FileSystem.ImageSharp", "modules\file-management\LINGYUN.Abp.FileManagement.FileSystem.ImageSharp\LINGYUN.Abp.FileManagement.FileSystem.ImageSharp.csproj", "{3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -719,6 +725,18 @@ Global {B86EBB6F-A27F-4277-8265-937951A9DCB0}.Debug|Any CPU.Build.0 = Debug|Any CPU {B86EBB6F-A27F-4277-8265-937951A9DCB0}.Release|Any CPU.ActiveCfg = Release|Any CPU {B86EBB6F-A27F-4277-8265-937951A9DCB0}.Release|Any CPU.Build.0 = Release|Any CPU + {35B17218-9FB6-439E-AF73-9A1454BC923C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35B17218-9FB6-439E-AF73-9A1454BC923C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35B17218-9FB6-439E-AF73-9A1454BC923C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35B17218-9FB6-439E-AF73-9A1454BC923C}.Release|Any CPU.Build.0 = Release|Any CPU + {D5036D3F-1C53-47EE-BA50-AD290AE062D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5036D3F-1C53-47EE-BA50-AD290AE062D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5036D3F-1C53-47EE-BA50-AD290AE062D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5036D3F-1C53-47EE-BA50-AD290AE062D7}.Release|Any CPU.Build.0 = Release|Any CPU + {3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -856,6 +874,9 @@ Global {8C3312E7-F51E-4780-A893-CE0E0B80B579} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E} {FE0F0889-C4AF-43C5-B851-B8CCC873BA2C} = {14CDBAD1-10C8-464A-B445-1F727C988010} {B86EBB6F-A27F-4277-8265-937951A9DCB0} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} + {35B17218-9FB6-439E-AF73-9A1454BC923C} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6} + {D5036D3F-1C53-47EE-BA50-AD290AE062D7} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6} + {3E5EBCEC-78C9-4A1A-BF04-A216AA6A921F} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/LINGYUN.MicroService.Platform.sln b/aspnet-core/LINGYUN.MicroService.Platform.sln index 4d6f304f3..205483c61 100644 --- a/aspnet-core/LINGYUN.MicroService.Platform.sln +++ b/aspnet-core/LINGYUN.MicroService.Platform.sln @@ -45,9 +45,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TestsBase", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.EntityFrameworkCore.Tests\LINGYUN.Abp.EntityFrameworkCore.Tests.csproj", "{B2C0271C-3FE6-4C45-B162-4DE00E542A55}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Platform.EntityFrameworkCore.Tests", "tests\LINGYUN.Platform.EntityFrameworkCore.Tests\LINGYUN.Platform.EntityFrameworkCore.Tests.csproj", "{263C49A9-34B9-449B-ABBC-D328210C023D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Platform.EntityFrameworkCore.Tests", "tests\LINGYUN.Platform.EntityFrameworkCore.Tests\LINGYUN.Platform.EntityFrameworkCore.Tests.csproj", "{263C49A9-34B9-449B-ABBC-D328210C023D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Platform.Domain.Tests", "tests\LINGYUN.Platform.Domain.Tests\LINGYUN.Platform.Domain.Tests.csproj", "{C60A06F2-0F4C-483F-BE2B-B103F0D726CE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Platform.Domain.Tests", "tests\LINGYUN.Platform.Domain.Tests\LINGYUN.Platform.Domain.Tests.csproj", "{C60A06F2-0F4C-483F-BE2B-B103F0D726CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.FileManagement.Aliyun", "modules\file-management\LINGYUN.Abp.FileManagement.Aliyun\LINGYUN.Abp.FileManagement.Aliyun.csproj", "{104EDC09-0CEA-4AB0-BFF5-B009D4679419}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{265D5E44-682B-49BC-984A-BDD8CA45E60E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Aliyun", "modules\cloud-aliyun\LINGYUN.Abp.Aliyun\LINGYUN.Abp.Aliyun.csproj", "{8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BlobStoring.Aliyun", "modules\common\LINGYUN.Abp.BlobStoring.Aliyun\LINGYUN.Abp.BlobStoring.Aliyun.csproj", "{95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.FileManagement.FileSystem", "modules\file-management\LINGYUN.Abp.FileManagement.FileSystem\LINGYUN.Abp.FileManagement.FileSystem.csproj", "{B5569DCD-445E-445B-87E7-D8D4E03F0F76}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -123,6 +133,22 @@ Global {C60A06F2-0F4C-483F-BE2B-B103F0D726CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {C60A06F2-0F4C-483F-BE2B-B103F0D726CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C60A06F2-0F4C-483F-BE2B-B103F0D726CE}.Release|Any CPU.Build.0 = Release|Any CPU + {104EDC09-0CEA-4AB0-BFF5-B009D4679419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {104EDC09-0CEA-4AB0-BFF5-B009D4679419}.Debug|Any CPU.Build.0 = Debug|Any CPU + {104EDC09-0CEA-4AB0-BFF5-B009D4679419}.Release|Any CPU.ActiveCfg = Release|Any CPU + {104EDC09-0CEA-4AB0-BFF5-B009D4679419}.Release|Any CPU.Build.0 = Release|Any CPU + {8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A}.Release|Any CPU.Build.0 = Release|Any CPU + {95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0}.Release|Any CPU.Build.0 = Release|Any CPU + {B5569DCD-445E-445B-87E7-D8D4E03F0F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5569DCD-445E-445B-87E7-D8D4E03F0F76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5569DCD-445E-445B-87E7-D8D4E03F0F76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5569DCD-445E-445B-87E7-D8D4E03F0F76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -148,6 +174,11 @@ Global {B2C0271C-3FE6-4C45-B162-4DE00E542A55} = {CCEFF583-4EEE-433F-8568-9E64166B41FE} {263C49A9-34B9-449B-ABBC-D328210C023D} = {CCEFF583-4EEE-433F-8568-9E64166B41FE} {C60A06F2-0F4C-483F-BE2B-B103F0D726CE} = {CCEFF583-4EEE-433F-8568-9E64166B41FE} + {104EDC09-0CEA-4AB0-BFF5-B009D4679419} = {C7D0EB39-3418-4A7C-AD94-FAB76F023E88} + {265D5E44-682B-49BC-984A-BDD8CA45E60E} = {15BDA03E-DE8E-46E4-96A8-CA3F2872E812} + {8A393F7F-85A2-48ED-9B56-9CEFF3BDE34A} = {265D5E44-682B-49BC-984A-BDD8CA45E60E} + {95E0D070-ACFB-40DF-A4EC-FC75EA5AF6B0} = {265D5E44-682B-49BC-984A-BDD8CA45E60E} + {B5569DCD-445E-445B-87E7-D8D4E03F0F76} = {C7D0EB39-3418-4A7C-AD94-FAB76F023E88} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03D3B66F-8926-4C00-B7AB-A21761EC859E} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj index 23161cf58..f8624e1da 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj +++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN.Abp.BlobStoring.Aliyun.csproj @@ -9,7 +9,7 @@ - + diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs index 6299df543..4ec2aea01 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/IOssClientFactory.cs @@ -5,6 +5,11 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun { public interface IOssClientFactory { + /// + /// 构建Oss客户端 + /// + /// + Task CreateAsync(); /// /// 通过配置信息构建Oss客户端调用 /// diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs index aed9f2321..df8fd6c80 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs +++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Aliyun/LINGYUN/Abp/BlobStoring/Aliyun/OssClientFactory.cs @@ -1,5 +1,7 @@ using Aliyun.OSS; using LINGYUN.Abp.Aliyun; +using System.Threading.Tasks; +using Volo.Abp.BlobStoring; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Settings; @@ -8,11 +10,21 @@ namespace LINGYUN.Abp.BlobStoring.Aliyun { public class OssClientFactory : AliyunClientFactory, IOssClientFactory, ITransientDependency { + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } public OssClientFactory( - ISettingProvider settingProvider, + ISettingProvider settingProvider, + IBlobContainerConfigurationProvider configurationProvider, IDistributedCache cache) : base(settingProvider, cache) { + ConfigurationProvider = configurationProvider; + } + + public virtual async Task CreateAsync() + { + var configuration = ConfigurationProvider.Get(); + + return await CreateAsync(configuration.GetAliyunConfiguration()); } /// /// 普通方式构建Oss客户端 diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN.Abp.FileManagement.Aliyun.csproj b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN.Abp.FileManagement.Aliyun.csproj new file mode 100644 index 000000000..4ed385f46 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN.Abp.FileManagement.Aliyun.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AbpFileManagementAliyunModule.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AbpFileManagementAliyunModule.cs new file mode 100644 index 000000000..14c2d9d12 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AbpFileManagementAliyunModule.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.BlobStoring.Aliyun; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.FileManagement.Aliyun +{ + [DependsOn( + typeof(AbpBlobStoringAliyunModule), + typeof(AbpFileManagementDomainModule))] + public class AbpFileManagementAliyunModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTransient(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainer.cs new file mode 100644 index 000000000..fbb56243e --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainer.cs @@ -0,0 +1,369 @@ +using Aliyun.OSS; +using LINGYUN.Abp.BlobStoring.Aliyun; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.FileManagement.Aliyun +{ + /// + /// Oss容器的阿里云实现 + /// + internal class AliyunOssContainer : IOssContainer + { + protected ICurrentTenant CurrentTenant { get; } + protected IOssClientFactory OssClientFactory { get; } + public AliyunOssContainer( + ICurrentTenant currentTenant, + IOssClientFactory ossClientFactory) + { + CurrentTenant = currentTenant; + OssClientFactory = ossClientFactory; + } + public virtual async Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) + { + var ossClient = await CreateClientAsync(); + + var path = GetBasePath(request.Path); + var aliyunRequest = new DeleteObjectsRequest(request.Bucket, request.Objects.Select(x => x += path).ToList()); + + ossClient.DeleteObjects(aliyunRequest); + } + + public virtual async Task CreateAsync(string name) + { + var ossClient = await CreateClientAsync(); + + if (BucketExists(ossClient, name)) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerAlreadyExists); + } + + var bucket = ossClient.CreateBucket(name); + + return new OssContainer( + bucket.Name, + bucket.CreationDate, + 0L, + bucket.CreationDate, + new Dictionary + { + { "Id", bucket.Owner?.Id }, + { "DisplayName", bucket.Owner?.DisplayName } + }); + } + + public virtual async Task CreateObjectAsync(CreateOssObjectRequest request) + { + var ossClient = await CreateClientAsync(); + + var objectPath = GetBasePath(request.Path); + + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + if (ObjectExists(ossClient, request.Bucket, objectName)) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectAlreadyExists); + } + + // 当一个对象名称是以 / 结尾时,不论该对象是否存有数据,都以目录的形式存在 + // 详情见:https://help.aliyun.com/document_detail/31910.html + if (objectName.EndsWith("/") && + request.Content.IsNullOrEmpty()) + { + var emptyStream = new MemoryStream(); + var emptyData = System.Text.Encoding.UTF8.GetBytes(""); + await emptyStream.WriteAsync(emptyData, 0, emptyData.Length); + request.SetContent(emptyStream); + } + + // 没有bucket则创建 + if (!BucketExists(ossClient, request.Bucket)) + { + ossClient.CreateBucket(request.Bucket); + } + + var aliyunObjectRequest = new PutObjectRequest(request.Bucket, objectName, request.Content) + { + Metadata = new ObjectMetadata() + }; + if (request.ExpirationTime.HasValue) + { + aliyunObjectRequest.Metadata.ExpirationTime = DateTime.Now.Add(request.ExpirationTime.Value); + } + + var aliyunObject = ossClient.PutObject(aliyunObjectRequest); + + var ossObject = new OssObject( + !objectPath.IsNullOrWhiteSpace() + ? objectName.Replace(objectPath, "") + : objectName, + objectPath, + DateTime.Now, + aliyunObject.ContentLength, + DateTime.Now, + aliyunObject.ResponseMetadata, + objectName.EndsWith("/") // 名称结尾是 / 符号的则为目录:https://help.aliyun.com/document_detail/31910.html + ) + { + FullName = objectName + }; + + if (!Equals(request.Content, Stream.Null)) + { + request.Content.Seek(0, SeekOrigin.Begin); + ossObject.SetContent(request.Content); + } + + return ossObject; + } + + public virtual async Task DeleteAsync(string name) + { + var ossClient = await CreateClientAsync(); + + if (BucketExists(ossClient, name)) + { + ossClient.DeleteBucket(name); + } + } + + public virtual async Task DeleteObjectAsync(GetOssObjectRequest request) + { + var ossClient = await CreateClientAsync(); + + var objectPath = GetBasePath(request.Path); + + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + if (BucketExists(ossClient, request.Bucket) && + ObjectExists(ossClient, request.Bucket, objectName)) + { + var objectListing = ossClient.ListObjects(request.Bucket, objectName); + if (objectListing.CommonPrefixes.Any() || + objectListing.ObjectSummaries.Any()) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectDeleteWithNotEmpty); + // throw new ObjectDeleteWithNotEmptyException("00201", $"Can't not delete oss object {request.Object}, because it is not empty!"); + } + ossClient.DeleteObject(request.Bucket, objectName); + } + } + + public virtual async Task ExistsAsync(string name) + { + var ossClient = await CreateClientAsync(); + + return BucketExists(ossClient, name); + } + + public virtual async Task GetAsync(string name) + { + var ossClient = await CreateClientAsync(); + if (!BucketExists(ossClient, name)) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); + // throw new ContainerNotFoundException($"Can't not found container {name} in aliyun blob storing"); + } + var bucket = ossClient.GetBucketInfo(name); + + return new OssContainer( + bucket.Bucket.Name, + bucket.Bucket.CreationDate, + 0L, + bucket.Bucket.CreationDate, + new Dictionary + { + { "Id", bucket.Bucket.Owner?.Id }, + { "DisplayName", bucket.Bucket.Owner?.DisplayName } + }); + } + + public virtual async Task GetObjectAsync(GetOssObjectRequest request) + { + var ossClient = await CreateClientAsync(); + if (!BucketExists(ossClient, request.Bucket)) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); + // throw new ContainerNotFoundException($"Can't not found container {request.Bucket} in aliyun blob storing"); + } + + var objectPath = GetBasePath(request.Path); + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + if (!ObjectExists(ossClient, request.Bucket, objectName)) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectNotFound); + // throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with aliyun blob storing"); + } + + var aliyunOssObjectRequest = new GetObjectRequest(request.Bucket, objectName, request.Process); + var aliyunOssObject = ossClient.GetObject(aliyunOssObjectRequest); + var ossObject = new OssObject( + !objectPath.IsNullOrWhiteSpace() + ? aliyunOssObject.Key.Replace(objectPath, "") + : aliyunOssObject.Key, + request.Path, + aliyunOssObject.Metadata.LastModified, + aliyunOssObject.Metadata.ContentLength, + aliyunOssObject.Metadata.LastModified, + aliyunOssObject.Metadata.UserMetadata, + aliyunOssObject.Key.EndsWith("/")) + { + FullName = aliyunOssObject.Key + }; + + if (aliyunOssObject.IsSetResponseStream()) + { + var memoryStream = new MemoryStream(); + await aliyunOssObject.Content.CopyToAsync(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + ossObject.SetContent(memoryStream); + } + + return ossObject; + } + + public virtual async Task GetListAsync(GetOssContainersRequest request) + { + var ossClient = await CreateClientAsync(); + + var aliyunRequest = new ListBucketsRequest + { + Marker = request.Marker, + Prefix = request.Prefix, + MaxKeys = request.MaxKeys + }; + var bucketsResponse = ossClient.ListBuckets(aliyunRequest); + + return new GetOssContainersResponse( + bucketsResponse.Prefix, + bucketsResponse.Marker, + bucketsResponse.NextMaker, + bucketsResponse.MaxKeys ?? 0, + bucketsResponse.Buckets + .Select(x => new OssContainer( + x.Name, + x.CreationDate, + 0L, + x.CreationDate, + new Dictionary + { + { "Id", x.Owner?.Id }, + { "DisplayName", x.Owner?.DisplayName } + })) + .ToList()); + } + + public virtual async Task GetObjectsAsync(GetOssObjectsRequest request) + { + + var ossClient = await CreateClientAsync(); + + var objectPath = GetBasePath(request.Prefix); + var marker = !objectPath.IsNullOrWhiteSpace() && !request.Marker.IsNullOrWhiteSpace() + ? request.Marker.Replace(objectPath, "") + : request.Marker; + var aliyunRequest = new ListObjectsRequest(request.BucketName) + { + Marker = !marker.IsNullOrWhiteSpace() ? objectPath + marker : marker, + Prefix = objectPath, + MaxKeys = request.MaxKeys, + EncodingType = request.EncodingType, + Delimiter = request.Delimiter + }; + var objectsResponse = ossClient.ListObjects(aliyunRequest); + + var ossObjects = objectsResponse.ObjectSummaries + .Where(x => !x.Key.Equals(objectsResponse.Prefix))// 过滤当前的目录返回值 + .Select(x => new OssObject( + !objectPath.IsNullOrWhiteSpace() && !x.Key.Equals(objectPath) + ? x.Key.Replace(objectPath, "") + : x.Key, // 去除目录名称 + request.Prefix, + x.LastModified, + x.Size, + x.LastModified, + new Dictionary + { + { "Id", x.Owner?.Id }, + { "DisplayName", x.Owner?.DisplayName } + }, + x.Key.EndsWith("/")) + { + FullName = x.Key + }) + .ToList(); + // 当 Delimiter 为 / 时, objectsResponse.CommonPrefixes 可用于代表层级目录 + if (objectsResponse.CommonPrefixes.Any()) + { + ossObjects.InsertRange(0, + objectsResponse.CommonPrefixes + .Select(x => new OssObject( + x.Replace(objectPath, ""), + request.Prefix, + null, + 0L, + null, + null, + true))); + } + // 排序 + // TODO: 是否需要客户端来排序 + ossObjects.Sort(new OssObjectComparer()); + + return new GetOssObjectsResponse( + objectsResponse.BucketName, + request.Prefix, + marker, + !objectPath.IsNullOrWhiteSpace() && !objectsResponse.NextMarker.IsNullOrWhiteSpace() + ? objectsResponse.NextMarker.Replace(objectPath, "") + : objectsResponse.NextMarker, + objectsResponse.Delimiter, + objectsResponse.MaxKeys, + ossObjects); + } + + protected virtual string GetBasePath(string path) + { + string objectPath = ""; + if (CurrentTenant.Id == null) + { + objectPath += "host/"; + } + else + { + objectPath += "tenants/" + CurrentTenant.Id.Value.ToString("D"); + } + + objectPath += path ?? ""; + + return objectPath.EnsureEndsWith('/'); + } + + protected virtual bool BucketExists(IOss client, string bucketName) + { + return client.DoesBucketExist(bucketName); + } + + protected virtual bool ObjectExists(IOss client, string bucketName, string objectName) + { + return client.DoesObjectExist(bucketName, objectName); + } + + protected virtual async Task CreateClientAsync() + { + return await OssClientFactory.CreateAsync(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainerFactory.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainerFactory.cs new file mode 100644 index 000000000..4c17c9c7e --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/LINGYUN/Abp/FileManagement/Aliyun/AliyunOssContainerFactory.cs @@ -0,0 +1,26 @@ +using LINGYUN.Abp.BlobStoring.Aliyun; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.FileManagement.Aliyun +{ + public class AliyunOssContainerFactory : IOssContainerFactory + { + protected ICurrentTenant CurrentTenant { get; } + protected IOssClientFactory OssClientFactory { get; } + + public AliyunOssContainerFactory( + ICurrentTenant currentTenant, + IOssClientFactory ossClientFactory) + { + CurrentTenant = currentTenant; + OssClientFactory = ossClientFactory; + } + + public IOssContainer Create() + { + return new AliyunOssContainer( + CurrentTenant, + OssClientFactory); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/README.md b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/README.md new file mode 100644 index 000000000..70ccb62d9 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Aliyun/README.md @@ -0,0 +1,15 @@ +# LINGYUN.Abp.FileManagement.Aliyun + +阿里云oss容器接口 + +## 配置使用 + +模块按需引用 + +```csharp +[DependsOn(typeof(AbpFileManagementAliyunModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/BulkDeleteOssObjectInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/BulkDeleteOssObjectInput.cs new file mode 100644 index 000000000..0d940c3ef --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/BulkDeleteOssObjectInput.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.FileManagement +{ + public class BulkDeleteOssObjectInput + { + [Required] + public string Bucket { get; set; } + + public string Path { get; set; } + + [Required] + public string[] Objects { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/CreateOssObjectInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/CreateOssObjectInput.cs new file mode 100644 index 000000000..eee24f5b7 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/CreateOssObjectInput.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace LINGYUN.Abp.FileManagement +{ + public class CreateOssObjectInput + { + public string Bucket { get; set; } + public string Path { get; set; } + public string Object { get; set; } + public Stream Content { get; set; } + public TimeSpan? ExpirationTime { get; set; } + + public void SetContent(Stream content) + { + Content = content; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssContainersInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssContainersInput.cs new file mode 100644 index 000000000..3b97ca6fa --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssContainersInput.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssContainersInput : PagedAndSortedResultRequestDto + { + public string Prefix { get; set; } + public string Marker { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectInput.cs new file mode 100644 index 000000000..25d12f78b --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectInput.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssObjectInput + { + [Required] + public string Bucket { get; set; } + + public string Path { get; set; } + + [Required] + public string Object { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectsInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectsInput.cs new file mode 100644 index 000000000..d8d1b226b --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetOssObjectsInput.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssObjectsInput : PagedAndSortedResultRequestDto + { + public string Bucket { get; set; } + public string Prefix { get; set; } + public string Delimiter { get; set; } + public string Marker { get; set; } + public string EncodingType { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetStaticFileInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetStaticFileInput.cs new file mode 100644 index 000000000..71f925c03 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/GetStaticFileInput.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetStaticFileInput + { + [Required] + public string Name { get; set; } + + public string Path { get; set; } + + public string Bucket { get; set; } + + public string Process { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssContainerAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssContainerAppService.cs new file mode 100644 index 000000000..cfc17f6e2 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssContainerAppService.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.FileManagement +{ + public interface IOssContainerAppService: IApplicationService + { + Task CreateAsync(string name); + + Task GetAsync(string name); + + Task DeleteAsync(string name); + + Task GetListAsync(GetOssContainersInput input); + + Task GetObjectListAsync(GetOssObjectsInput input); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssObjectAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssObjectAppService.cs new file mode 100644 index 000000000..5c192a0b0 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IOssObjectAppService.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.FileManagement +{ + public interface IOssObjectAppService : IApplicationService + { + Task CreateAsync(CreateOssObjectInput input); + + Task GetAsync(GetOssObjectInput input); + + Task DeleteAsync(GetOssObjectInput input); + + Task BulkDeleteAsync(BulkDeleteOssObjectInput input); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IStaticFilesAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IStaticFilesAppService.cs new file mode 100644 index 000000000..0a1fc06bf --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/IStaticFilesAppService.cs @@ -0,0 +1,11 @@ +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.FileManagement +{ + public interface IStaticFilesAppService: IApplicationService + { + Task GetAsync(GetStaticFileInput input); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainerDto.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainerDto.cs new file mode 100644 index 000000000..b7f82356c --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainerDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssContainerDto + { + public string Name { get; set; } + public long Size { get; set; } + public DateTime CreationDate { get; set; } + public DateTime? LastModifiedDate { get; set; } + public IDictionary Metadata { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainersResultDto.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainersResultDto.cs new file mode 100644 index 000000000..6617ee478 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssContainersResultDto.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssContainersResultDto + { + public string Prefix { get; set; } + public string Marker { get; set; } + public string NextMarker { get; set; } + public int MaxKeys { get; set; } + public List Containers { get; set; } = new List(); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectDto.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectDto.cs new file mode 100644 index 000000000..dbacaef6a --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssObjectDto + { + public bool IsFolder { get; set; } + public string Path { get; set; } + public string Name { get; set; } + public long Size { get; set; } + public DateTime? CreationDate { get; set; } + public DateTime? LastModifiedDate { get; set; } + public IDictionary Metadata { get; set; } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectsResultDto.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectsResultDto.cs new file mode 100644 index 000000000..0d8cc9c44 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application.Contracts/LINGYUN/Abp/FileManagement/OssObjectsResultDto.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssObjectsResultDto + { + public string Bucket { get; set; } + public string Prefix { get; set; } + public string Delimiter { get; set; } + public string Marker { get; set; } + public string NextMarker { get; set; } + public int MaxKeys { get; set; } + public List Objects { get; set; } = new List(); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN.Abp.FileManagement.Application.csproj b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN.Abp.FileManagement.Application.csproj index 6b654a444..aeea6658a 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN.Abp.FileManagement.Application.csproj +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN.Abp.FileManagement.Application.csproj @@ -7,6 +7,10 @@ + + + + diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/AbpFileManagementApplicationModule.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/AbpFileManagementApplicationModule.cs index 4d52281a1..93e5186bb 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/AbpFileManagementApplicationModule.cs +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/AbpFileManagementApplicationModule.cs @@ -1,12 +1,23 @@ -using Volo.Abp.Modularity; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; namespace LINGYUN.Abp.FileManagement { [DependsOn( + typeof(AbpAutoMapperModule), typeof(AbpFileManagementDomainModule), typeof(AbpFileManagementApplicationContractsModule))] public class AbpFileManagementApplicationModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + Configure(options => + { + options.AddProfile(validate: true); + }); + } } } diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileManagementApplicationAutoMapperProfile.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileManagementApplicationAutoMapperProfile.cs new file mode 100644 index 000000000..8bb61e63d --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileManagementApplicationAutoMapperProfile.cs @@ -0,0 +1,17 @@ +using AutoMapper; + +namespace LINGYUN.Abp.FileManagement +{ + public class FileManagementApplicationAutoMapperProfile : Profile + { + public FileManagementApplicationAutoMapperProfile() + { + CreateMap(); + CreateMap() + .ForMember(dto => dto.Path, map => map.MapFrom(src => src.Prefix)); + + CreateMap(); + CreateMap(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileSystemAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileSystemAppService.cs index ed08e336b..a6ee61d96 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileSystemAppService.cs +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/FileSystemAppService.cs @@ -16,10 +16,10 @@ namespace LINGYUN.Abp.FileManagement [Authorize(AbpFileManagementPermissions.FileSystem.Default)] public class FileSystemAppService : FileManagementApplicationServiceBase, IFileSystemAppService { - protected IBlobContainer BlobContainer { get; } + protected IBlobContainer BlobContainer { get; } protected IBlobContainerConfigurationProvider BlobContainerConfigurationProvider { get; } public FileSystemAppService( - IBlobContainer blobContainer, + IBlobContainer blobContainer, IBlobContainerConfigurationProvider blobContainerConfigurationProvider) { BlobContainer = blobContainer; @@ -373,7 +373,7 @@ namespace LINGYUN.Abp.FileManagement blobPath = Path.Combine(blobPath, "tenants", CurrentTenant.Id.Value.ToString("D")); } // 去除完整路径中的容器根目录 - var containerName = BlobContainerNameAttribute.GetContainerName(); + var containerName = BlobContainerNameAttribute.GetContainerName(); if (path.Contains(containerName)) { blobPath = Path.Combine(blobPath, containerName); @@ -421,7 +421,7 @@ namespace LINGYUN.Abp.FileManagement { blobPath = Path.Combine(blobPath, "tenants", CurrentTenant.Id.Value.ToString("D")); } - var containerName = BlobContainerNameAttribute.GetContainerName(); + var containerName = BlobContainerNameAttribute.GetContainerName(); blobPath = Path.Combine(blobPath, containerName); @@ -436,7 +436,7 @@ namespace LINGYUN.Abp.FileManagement protected virtual FileSystemBlobProviderConfiguration GetFileSystemBlobProviderConfiguration() { var blobConfiguration = BlobContainerConfigurationProvider - .Get(); + .Get(); return blobConfiguration.GetFileSystemConfiguration(); } diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssContainerAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssContainerAppService.cs new file mode 100644 index 000000000..0ac053e16 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssContainerAppService.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssContainerAppService : FileManagementApplicationServiceBase, IOssContainerAppService + { + protected IOssContainerFactory OssContainerFactory { get; } + + public OssContainerAppService( + IOssContainerFactory ossContainerFactory) + { + OssContainerFactory = ossContainerFactory; + } + + public virtual async Task CreateAsync(string name) + { + var oss = CreateOssContainer(); + var container = await oss.CreateAsync(name); + + return ObjectMapper.Map(container); + } + + public virtual async Task DeleteAsync(string name) + { + var oss = CreateOssContainer(); + + await oss.DeleteAsync(name); + } + + public virtual async Task GetAsync(string name) + { + var oss = CreateOssContainer(); + var container = await oss.GetAsync(name); + + return ObjectMapper.Map(container); + } + + public virtual async Task GetListAsync(GetOssContainersInput input) + { + var oss = CreateOssContainer(); + + var containerResponse = await oss.GetListAsync( + input.Prefix, input.Marker, input.MaxResultCount); + + return ObjectMapper.Map(containerResponse); + } + + public virtual async Task GetObjectListAsync(GetOssObjectsInput input) + { + var oss = CreateOssContainer(); + + var ossObjectResponse = await oss.GetObjectsAsync( + input.Bucket, input.Prefix, input.Marker, + input.Delimiter, input.EncodingType, + input.MaxResultCount); + + return ObjectMapper.Map(ossObjectResponse); + } + + protected virtual IOssContainer CreateOssContainer() + { + return OssContainerFactory.Create(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssObjectAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssObjectAppService.cs new file mode 100644 index 000000000..0e94ef924 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/OssObjectAppService.cs @@ -0,0 +1,100 @@ +using LINGYUN.Abp.FileManagement.Settings; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.IO; +using Volo.Abp.Settings; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssObjectAppService : FileManagementApplicationServiceBase, IOssObjectAppService + { + protected IOssContainerFactory OssContainerFactory { get; } + + public OssObjectAppService( + IOssContainerFactory ossContainerFactory) + { + OssContainerFactory = ossContainerFactory; + } + + public virtual async Task CreateAsync(CreateOssObjectInput input) + { + if (!input.Content.IsNullOrEmpty()) + { + // 检查文件大小 + var fileSizeLimited = await SettingProvider + .GetAsync( + AbpFileManagementSettingNames.FileLimitLength, + AbpFileManagementSettingNames.DefaultFileLimitLength); + if (fileSizeLimited * 1024 * 1024 < input.Content.Length) + { + ThrowValidationException(L["UploadFileSizeBeyondLimit", fileSizeLimited], nameof(input.Content)); + } + + // 文件扩展名 + var fileExtensionName = FileHelper.GetExtension(input.Object); + var fileAllowExtension = await SettingProvider.GetOrNullAsync(AbpFileManagementSettingNames.AllowFileExtensions); + // 检查文件扩展名 + if (!fileAllowExtension.Split(',') + .Any(fe => fe.Equals(fileExtensionName, StringComparison.CurrentCultureIgnoreCase))) + { + ThrowValidationException(L["NotAllowedFileExtensionName", fileExtensionName], "FileName"); + } + } + + var oss = CreateOssContainer(); + + var createOssObjectRequest = new CreateOssObjectRequest( + input.Bucket, + input.Object, + input.Content, + input.Path, + input.ExpirationTime); + var ossObject = await oss.CreateObjectAsync(createOssObjectRequest); + + return ObjectMapper.Map(ossObject); + } + + public virtual async Task BulkDeleteAsync(BulkDeleteOssObjectInput input) + { + var oss = CreateOssContainer(); + + await oss.BulkDeleteObjectsAsync(input.Bucket, input.Objects, input.Path); + } + + public virtual async Task DeleteAsync(GetOssObjectInput input) + { + var oss = CreateOssContainer(); + + await oss.DeleteObjectAsync(input.Bucket, input.Object, input.Path); + } + + public virtual async Task GetAsync(GetOssObjectInput input) + { + var oss = CreateOssContainer(); + + var ossObject = await oss.GetObjectAsync(input.Bucket, input.Object, input.Path); + + return ObjectMapper.Map(ossObject); + } + + protected virtual IOssContainer CreateOssContainer() + { + return OssContainerFactory.Create(); + } + + private static void ThrowValidationException(string message, string memberName) + { + throw new AbpValidationException(message, + new List + { + new ValidationResult(message, new[] {memberName}) + }); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/StaticFilesAppService.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/StaticFilesAppService.cs new file mode 100644 index 000000000..d5d3427e3 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Application/LINGYUN/Abp/FileManagement/StaticFilesAppService.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Threading.Tasks; +using System.Web; + +namespace LINGYUN.Abp.FileManagement +{ + public class StaticFilesAppService : FileManagementApplicationServiceBase, IStaticFilesAppService + { + protected IOssContainerFactory OssContainerFactory { get; } + + public StaticFilesAppService( + IOssContainerFactory ossContainerFactory) + { + OssContainerFactory = ossContainerFactory; + } + + public virtual async Task GetAsync(GetStaticFileInput input) + { + var ossObjectRequest = new GetOssObjectRequest( + HttpUtility.UrlDecode(input.Bucket), // 需要处理特殊字符 + HttpUtility.UrlDecode(input.Name), + HttpUtility.UrlDecode(input.Path), + HttpUtility.UrlDecode(input.Process)); + + var ossContainer = OssContainerFactory.Create(); + var ossObject = await ossContainer.GetObjectAsync(ossObjectRequest); + + return ossObject.Content; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/AbpFileManagementDomainSharedModule.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/AbpFileManagementDomainSharedModule.cs index fe39e6a5e..a95cf50f7 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/AbpFileManagementDomainSharedModule.cs +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/AbpFileManagementDomainSharedModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.FileManagement.Localization; using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Abp.Validation; using Volo.Abp.Validation.Localization; @@ -25,6 +26,11 @@ namespace LINGYUN.Abp.FileManagement typeof(AbpValidationResource) ).AddVirtualJson("/LINGYUN/Abp/FileManagement/Localization/Resources"); }); + + Configure(options => + { + options.MapCodeNamespace(FileManagementErrorCodes.Namespace, typeof(AbpFileManagementResource)); + }); } } } diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/FileManagementErrorCodes.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/FileManagementErrorCodes.cs new file mode 100644 index 000000000..f746ac847 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/FileManagementErrorCodes.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LINGYUN.Abp.FileManagement +{ + public static class FileManagementErrorCodes + { + public const string Namespace = "Abp.FileManagement"; + + public const string ContainerDeleteWithNotEmpty = Namespace + ":010001"; + public const string ContainerAlreadyExists = Namespace + ":010402"; + public const string ContainerNotFound = Namespace + ":010404"; + + public const string ObjectDeleteWithNotEmpty = Namespace + ":020001"; + public const string ObjectAlreadyExists = Namespace + ":020402"; + public const string ObjectNotFound = Namespace + ":020404"; + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/en.json b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/en.json index f6fc9c7a4..593dd067b 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/en.json +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/en.json @@ -1,6 +1,12 @@ { "culture": "en", "texts": { + "Abp.FileManagement:010001": "Cannot delete a container that has more than one object!", + "Abp.FileManagement:010402": "The container name already exists!", + "Abp.FileManagement:010404": "The queried container could not be found!", + "Abp.FileManagement:020001": "An object that has more than one child cannot be deleted!", + "Abp.FileManagement:020402": "The object name already exists!", + "Abp.FileManagement:020404": "The queried object could not be found!", "Permission:FileManagement": "File management", "Permission:FileSystem": "File system", "Permission:FileManager": "Files", diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/zh-Hans.json index 8f3fb55e2..f33ccb294 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Localization/Resources/zh-Hans.json @@ -1,6 +1,12 @@ { "culture": "zh-Hans", "texts": { + "Abp.FileManagement:010001": "不能删除存在多个对象的容器!", + "Abp.FileManagement:010402": "容器名称已经存在!", + "Abp.FileManagement:010404": "未能找到查询的容器!", + "Abp.FileManagement:020001": "不能删除存在多个子级的对象!", + "Abp.FileManagement:020402": "对象名称已经存在!", + "Abp.FileManagement:020404": "未能找到查询的对象!", "Permission:FileManagement": "文件管理", "Permission:FileSystem": "文件系统", "Permission:FileManager": "文件", diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Settings/AbpFileManagementSettingNames.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Settings/AbpFileManagementSettingNames.cs index 18d24cf18..56b5527da 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Settings/AbpFileManagementSettingNames.cs +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/LINGYUN/Abp/FileManagement/Settings/AbpFileManagementSettingNames.cs @@ -4,6 +4,10 @@ { public const string GroupName = "Abp.FileManagement"; /// + /// 下载分包大小 + /// + public const string DownloadPackageSize = GroupName + ".DownloadPackageSize"; + /// /// 文件限制长度 /// public const string FileLimitLength = GroupName + ".FileLimitLength"; diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/System/IO/StreamExtensions.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/System/IO/StreamExtensions.cs new file mode 100644 index 000000000..1e18d5ffd --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain.Shared/System/IO/StreamExtensions.cs @@ -0,0 +1,12 @@ +namespace System.IO +{ + public static class StreamExtensions + { + public static bool IsNullOrEmpty( + this Stream stream) + { + return stream == null || + Equals(stream, Stream.Null); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN.Abp.FileManagement.Domain.csproj b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN.Abp.FileManagement.Domain.csproj index c7a67d189..2065cb5f0 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN.Abp.FileManagement.Domain.csproj +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN.Abp.FileManagement.Domain.csproj @@ -1,9 +1,10 @@ - + netstandard2.0 + diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/FileSystemContainer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/AbpFileManagementContainer.cs similarity index 75% rename from aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/FileSystemContainer.cs rename to aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/AbpFileManagementContainer.cs index 42c643e8f..d57280dd6 100644 --- a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/FileSystemContainer.cs +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/AbpFileManagementContainer.cs @@ -3,7 +3,7 @@ namespace LINGYUN.Abp.FileManagement { [BlobContainerName("abp-file-management")] - public class FileSystemContainer + public class AbpFileManagementContainer { } } diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/BulkDeleteObjectRequest.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/BulkDeleteObjectRequest.cs new file mode 100644 index 000000000..84faba84b --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/BulkDeleteObjectRequest.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp; + +namespace LINGYUN.Abp.FileManagement +{ + public class BulkDeleteObjectRequest + { + public string Bucket { get; } + public string Path { get; } + public ICollection Objects { get; } + + public BulkDeleteObjectRequest( + [NotNull] string bucket, + ICollection objects, + string path = "") + { + Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); + Check.NotNullOrEmpty(objects, nameof(objects)); + + Bucket = bucket; + Objects = objects; + Path = path; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/CreateOssObjectRequest.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/CreateOssObjectRequest.cs new file mode 100644 index 000000000..c05b1e78c --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/CreateOssObjectRequest.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; +using System; +using System.IO; +using Volo.Abp; + +namespace LINGYUN.Abp.FileManagement +{ + public class CreateOssObjectRequest + { + public string Bucket { get; } + public string Path { get; } + public string Object { get; } + public Stream Content { get; private set; } + public TimeSpan? ExpirationTime { get; } + public CreateOssObjectRequest( + [NotNull] string bucket, + [NotNull] string @object, + [CanBeNull] Stream content, + [CanBeNull] string path = null, + [CanBeNull] TimeSpan? expirationTime = null) + { + Bucket = Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); + Object = Check.NotNullOrWhiteSpace(@object, nameof(@object)); + + Path = path; + Content = content; + ExpirationTime = expirationTime; + } + + public void SetContent(Stream content) + { + Content = content; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersRequest.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersRequest.cs new file mode 100644 index 000000000..c57a24e0e --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersRequest.cs @@ -0,0 +1,18 @@ +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssContainersRequest + { + public string Prefix { get; } + public string Marker { get; } + public int? MaxKeys { get; } + public GetOssContainersRequest( + string prefix = null, + string marker = null, + int? maxKeys = 10) + { + Prefix = prefix; + Marker = marker; + MaxKeys = maxKeys; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersResponse.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersResponse.cs new file mode 100644 index 000000000..ef7e5257a --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssContainersResponse.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssContainersResponse + { + public string Prefix { get; } + public string Marker { get; } + public string NextMarker { get; } + public int MaxKeys { get; } + public List Containers { get; } + + public GetOssContainersResponse( + string prefix, + string marker, + string nextMarker, + int maxKeys, + List containers) + { + Prefix = prefix; + Marker = marker; + NextMarker = nextMarker; + MaxKeys = maxKeys; + + Containers = containers; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectRequest.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectRequest.cs new file mode 100644 index 000000000..ae86b7491 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectRequest.cs @@ -0,0 +1,31 @@ +using JetBrains.Annotations; +using Volo.Abp; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssObjectRequest + { + public string Bucket { get; } + public string Path { get; } + public string Object { get; } + /// + /// 需要处理文件的参数 + /// + public string Process { get; } + + public GetOssObjectRequest( + [NotNull] string bucket, + [NotNull] string @object, + [CanBeNull] string path = "", + [CanBeNull] string process = "") + { + Check.NotNullOrWhiteSpace(bucket, nameof(bucket)); + Check.NotNullOrWhiteSpace(@object, nameof(@object)); + + Bucket = bucket; + Object = @object; + Path = path; + Process = process; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsRequest.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsRequest.cs new file mode 100644 index 000000000..2ec2f88eb --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsRequest.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Volo.Abp; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssObjectsRequest + { + public string BucketName { get; } + public string Prefix { get; } + public string Delimiter { get; } + public string Marker { get; } + public string EncodingType { get; } + public int? MaxKeys { get; } + public GetOssObjectsRequest( + [NotNull] string bucketName, + string prefix = null, + string marker = null, + string delimiter = null, + string encodingType = null, + int maxKeys = 10) + { + Check.NotNullOrWhiteSpace(bucketName, nameof(bucketName)); + + BucketName = bucketName; + Prefix = prefix; + Marker = marker; + Delimiter = delimiter; + EncodingType = encodingType; + MaxKeys = maxKeys; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsResponse.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsResponse.cs new file mode 100644 index 000000000..2cdb7444e --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/GetOssObjectsResponse.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class GetOssObjectsResponse + { + public string Bucket { get; } + public string Prefix { get; } + public string Delimiter { get; } + public string Marker { get; } + public string NextMarker { get; } + public int MaxKeys { get; } + public List Objects { get; } + public GetOssObjectsResponse( + string bucket, + string prefix, + string marker, + string nextMarker, + string delimiter, + int maxKeys, + List ossObjects) + { + Bucket = bucket; + Prefix = prefix; + Marker = marker; + NextMarker = nextMarker; + Delimiter = delimiter; + MaxKeys = maxKeys; + + Objects = ossObjects; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainer.cs new file mode 100644 index 000000000..21c996b6d --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainer.cs @@ -0,0 +1,72 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.FileManagement +{ + /// + /// Oss容器 + /// + public interface IOssContainer + { + /// + /// 创建容器 + /// + /// + /// + Task CreateAsync(string name); + /// + /// 创建Oss对象 + /// + /// + /// + Task CreateObjectAsync(CreateOssObjectRequest request); + /// + /// 获取容器信息 + /// + /// + /// + Task GetAsync(string name); + /// + /// 获取Oss对象信息 + /// + /// + /// + Task GetObjectAsync(GetOssObjectRequest request); + /// + /// 删除容器 + /// + /// + /// + Task DeleteAsync(string name); + /// + /// 删除Oss对象 + /// + /// + /// + Task DeleteObjectAsync(GetOssObjectRequest request); + /// + /// 批量删除Oss对象 + /// + /// + /// + Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request); + /// + /// 容器是否存在 + /// + /// + /// + Task ExistsAsync(string name); + /// + /// 获取容器列表 + /// + /// + /// + Task GetListAsync(GetOssContainersRequest request); + /// + /// 获取对象列表 + /// + /// + /// + Task GetObjectsAsync(GetOssObjectsRequest request); + // Task> GetObjectsAsync(string name, string prefix = null, string marker = null, string delimiter = null, int maxResultCount = 10); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerExtensions.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerExtensions.cs new file mode 100644 index 000000000..cf631031b --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerExtensions.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.FileManagement +{ + public static class IOssContainerExtensions + { + /// + /// 如果不存在容器则创建 + /// + /// + /// + /// 返回容器信息 + public static async Task CreateIfNotExistsAsync( + this IOssContainer ossContainer, + string name) + { + if (! await ossContainer.ExistsAsync(name)) + { + await ossContainer.CreateAsync(name); + } + + return await ossContainer.GetAsync(name); + } + + public static async Task DeleteObjectAsync( + this IOssContainer ossContainer, + string bucket, + string @object, + string path = "") + { + await ossContainer.DeleteObjectAsync( + new GetOssObjectRequest(bucket, @object, path)); + } + + public static async Task BulkDeleteObjectsAsync( + this IOssContainer ossContainer, + string bucketName, + ICollection objectNames, + string path = "") + { + await ossContainer.BulkDeleteObjectsAsync( + new BulkDeleteObjectRequest(bucketName, objectNames, path)); + } + + public static async Task GetListAsync( + this IOssContainer ossContainer, + string prefix = null, + string marker = null, + int maxResultCount = 10) + { + return await ossContainer.GetListAsync( + new GetOssContainersRequest(prefix, marker, maxResultCount)); + } + + public static async Task GetObjectAsync( + this IOssContainer ossContainer, + string bucket, + string @object, + string path = "") + { + return await ossContainer.GetObjectAsync( + new GetOssObjectRequest(bucket, @object, path)); + } + + public static async Task GetObjectsAsync( + this IOssContainer ossContainer, + string name, + string prefix = null, + string marker = null, + string delimiter = null, + string encodingType = null, + int maxResultCount = 10) + { + return await ossContainer.GetObjectsAsync( + new GetOssObjectsRequest(name, prefix, marker, delimiter, encodingType, maxResultCount)); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerFactory.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerFactory.cs new file mode 100644 index 000000000..56472c8fb --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/IOssContainerFactory.cs @@ -0,0 +1,18 @@ +namespace LINGYUN.Abp.FileManagement +{ + /// + /// Oss容器构建工厂 + /// + public interface IOssContainerFactory + { + IOssContainer Create(); + } + + /// + /// Oss容器构建工厂 + /// + public interface IOssContainerFactory + { + IOssContainer Create(TConfiguration configuration); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssContainer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssContainer.cs new file mode 100644 index 000000000..6bf1439e8 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssContainer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + /// + /// 描述了一个容器的状态信息 + /// + public class OssContainer + { + public string Name { get; } + public long Size { get; } + public DateTime CreationDate { get; } + public DateTime? LastModifiedDate { get; } + public IDictionary Metadata { get; } + + public OssContainer( + string name, + DateTime creationDate, + long size = 0, + DateTime? lastModifiedDate = null, + IDictionary metadata = null) + { + Name = name; + CreationDate = creationDate; + LastModifiedDate = lastModifiedDate; + Size = size; + Metadata = metadata ?? new Dictionary(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObject.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObject.cs new file mode 100644 index 000000000..1ffe5a212 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObject.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace LINGYUN.Abp.FileManagement +{ + /// + /// 描述了一个对象的状态信息 + /// + public class OssObject + { + private Stream _content; + + public bool IsFolder { get; } + public string Name { get; } + public string FullName { get; set; } + public string Prefix { get; } + public long Size { get; } + public Stream Content => _content; + public DateTime? CreationDate { get; } + public DateTime? LastModifiedDate { get; } + public IDictionary Metadata { get; } + public OssObject( + string name, + string prefix, + DateTime? creationDate = null, + long size = 0, + DateTime? lastModifiedDate = null, + IDictionary metadata = null, + bool isFolder = false) + { + Name = name; + Prefix = prefix; + CreationDate = creationDate; + LastModifiedDate = lastModifiedDate; + Size = size; + IsFolder = isFolder; + Metadata = metadata ?? new Dictionary(); + } + + public void SetContent(Stream stream) + { + _content = stream; + if (!_content.IsNullOrEmpty()) + { + _content.Seek(0, SeekOrigin.Begin); + } + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObjectComparer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObjectComparer.cs new file mode 100644 index 000000000..07f75d919 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.Domain/LINGYUN/Abp/FileManagement/OssObjectComparer.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement +{ + public class OssObjectComparer : IComparer + { + public virtual int Compare(OssObject x, OssObject y) + { + if (x.IsFolder && y.IsFolder) + { + return x.Name.CompareTo(y.Name); + } + + if (x.IsFolder && !y.IsFolder) + { + return -1; + } + + if (!x.IsFolder && y.IsFolder) + { + return 1; + } + + return x.Name.CompareTo(y.Name); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp.csproj b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp.csproj new file mode 100644 index 000000000..b529046bc --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1 + + + + + + + + + + + + diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/AbpFileManagementFileSystemImageSharpModule.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/AbpFileManagementFileSystemImageSharpModule.cs new file mode 100644 index 000000000..78a790eaa --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/AbpFileManagementFileSystemImageSharpModule.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.FileManagement.FileSystem.ImageSharp +{ + [DependsOn(typeof(AbpFileManagementFileSystemModule))] + public class AbpFileManagementFileSystemImageSharpModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AddProcesser(new ImageSharpProcesserContributor()); + }); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/ImageSharpFileSystemOssObjectProcesser.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/ImageSharpFileSystemOssObjectProcesser.cs new file mode 100644 index 000000000..e34c7474d --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/LINGYUN/Abp/FileManagement/FileSystem/ImageSharp/ImageSharpFileSystemOssObjectProcesser.cs @@ -0,0 +1,118 @@ +using SixLabors.Fonts; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.FileManagement.FileSystem.ImageSharp +{ + public class ImageSharpProcesserContributor : IFileSystemOssObjectProcesserContributor + { + protected static readonly string[] ImageTypes = new string[] + { + "6677",// bmp + "7173",// gif + "13780",// png + "255216"// jpg + }; + + public virtual async Task ProcessAsync(FileSystemOssObjectContext context) + { + var bytes = await context.OssObject.Content.GetAllBytesAsync(); + + if (IsImage(bytes)) + { + var args = context.Process.Split(','); + if (DrawGraphics(bytes, args, out Stream content)) + { + context.SetContent(content); + } + } + } + + protected virtual bool DrawGraphics(byte[] fileBytes, string[] args, out Stream content) + { + using var image = Image.Load(fileBytes, out IImageFormat format); + + // 大小 + var width = GetInt32Prarm(args, "w_"); + var height = GetInt32Prarm(args, "h_"); + if (!width.IsNullOrWhiteSpace() && + !height.IsNullOrWhiteSpace()) + { + image.Mutate(x => x.Resize(int.Parse(width), int.Parse(height))); + } + + // 水印 + //var txt = GetString(args, "t_"); + //if (!txt.IsNullOrWhiteSpace()) + //{ + // FontCollection fonts = new FontCollection(); + // FontFamily fontfamily = fonts.Install("本地字体.TTF"); + // var font = new Font(fontfamily, 20, FontStyle.Bold); + // var size = TextMeasurer.Measure(txt, new RendererOptions(font)); + + // image.Mutate(x => x.DrawText(txt, font, Color.WhiteSmoke, + // new PointF(image.Width - size.Width - 3, image.Height - size.Height - 3))); + //} + + // TODO: 其他处理参数及现有的优化 + + var imageStream = new MemoryStream(); + var encoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(format); + image.Save(imageStream, encoder); + imageStream.Seek(0, SeekOrigin.Begin); + + content = imageStream; + return true; + } + + private static bool IsImage(byte[] fileBytes) + { + if (fileBytes.IsNullOrEmpty()) + { + return false; + } + + string fileclass = ""; + for (int i = 0; i < 2; i++) + { + fileclass += fileBytes[i].ToString(); + } + + return ImageTypes.Any(type => type.Equals(fileclass)); + } + + private static string GetString(string[] args, string key) + { + if (!args.Any()) + { + return null; + } + + return args + .Where(arg => arg.StartsWith(key)) + .Select(arg => arg.Substring(key.Length)) + .FirstOrDefault(); + } + + private static string GetInt32Prarm(string[] args, string key) + { + if (!args.Any()) + { + return null; + } + + return args + .Where(arg => arg.StartsWith(key)) + .Select(arg => arg.Substring(key.Length)) + .FirstOrDefault(arg => int.TryParse(arg, out _)); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/README.md b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/README.md new file mode 100644 index 000000000..46c3c2686 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem.ImageSharp/README.md @@ -0,0 +1,15 @@ +# LINGYUN.Abp.FileManagement.FileSystem.ImageSharp + +本地文件系统oss对象ImageSharp图形处理接口 + +## 配置使用 + +模块按需引用 + +```csharp +[DependsOn(typeof(AbpFileManagementFileSystemImageSharpModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN.Abp.FileManagement.FileSystem.csproj b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN.Abp.FileManagement.FileSystem.csproj new file mode 100644 index 000000000..dfea42079 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN.Abp.FileManagement.FileSystem.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + 8.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/AbpFileManagementFileSystemModule.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/AbpFileManagementFileSystemModule.cs new file mode 100644 index 000000000..76421c779 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/AbpFileManagementFileSystemModule.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BlobStoring.FileSystem; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + [DependsOn( + typeof(AbpBlobStoringFileSystemModule), + typeof(AbpFileManagementDomainModule))] + public class AbpFileManagementFileSystemModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTransient(); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainer.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainer.cs new file mode 100644 index 000000000..58c0e2f79 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainer.cs @@ -0,0 +1,512 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.FileSystem; +using Volo.Abp.MultiTenancy; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.IO; +using Volo.Abp; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + /// + /// Oss容器的本地文件系统实现 + /// + internal class FileSystemOssContainer : IOssContainer + { + protected ICurrentTenant CurrentTenant { get; } + protected IHostEnvironment Environment { get; } + protected IBlobFilePathCalculator FilePathCalculator { get; } + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } + protected IServiceProvider ServiceProvider { get; } + protected FileSystemOssOptions Options { get; } + + public FileSystemOssContainer( + ICurrentTenant currentTenant, + IHostEnvironment environment, + IServiceProvider serviceProvider, + IBlobFilePathCalculator blobFilePathCalculator, + IBlobContainerConfigurationProvider configurationProvider, + IOptions options) + { + CurrentTenant = currentTenant; + Environment = environment; + ServiceProvider = serviceProvider; + FilePathCalculator = blobFilePathCalculator; + ConfigurationProvider = configurationProvider; + Options = options.Value; + } + + public virtual Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) + { + var filesPath = request.Objects.Select(x => CalculateFilePath(request.Bucket, x)); + + foreach (var file in filesPath) + { + if (File.Exists(file)) + { + File.Delete(file); + } + } + + return Task.CompletedTask; + } + + public virtual Task CreateAsync(string name) + { + var filePath = CalculateFilePath(name); + if (!Directory.Exists(filePath)) + { + Directory.CreateDirectory(filePath); + } + + var directoryInfo = new DirectoryInfo(filePath); + var container = new OssContainer( + directoryInfo.Name, + directoryInfo.CreationTime, + 0L, + directoryInfo.LastWriteTime, + new Dictionary + { + { "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }); + + return Task.FromResult(container); + } + + public virtual async Task CreateObjectAsync(CreateOssObjectRequest request) + { + var objectPath = !request.Path.IsNullOrWhiteSpace() + ? request.Path.EnsureEndsWith('/') + : ""; + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + var filePath = CalculateFilePath(request.Bucket, objectName); + if (!request.Content.IsNullOrEmpty()) + { + if (File.Exists(filePath)) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectAlreadyExists); + // throw new OssObjectAlreadyExistsException($"Can't not put object {objectName} in container {request.Bucket}, Because a file with the same name already exists in the directory!"); + } + + DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath)); + + using (var fileStream = File.Open(filePath, FileMode.CreateNew, FileAccess.Write)) + { + await request.Content.CopyToAsync(fileStream); + + await fileStream.FlushAsync(); + } + var fileInfo = new FileInfo(filePath); + var ossObject = new OssObject( + fileInfo.Name, + objectPath, + fileInfo.CreationTime, + fileInfo.Length, + fileInfo.LastWriteTime, + new Dictionary + { + { "IsReadOnly", fileInfo.IsReadOnly.ToString() }, + { "LastAccessTime", fileInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }) + { + FullName = fileInfo.FullName.Replace(Environment.ContentRootPath, "") + }; + ossObject.SetContent(request.Content); + + return ossObject; + } + else + { + if (Directory.Exists(filePath)) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectAlreadyExists); + // throw new OssObjectAlreadyExistsException($"Can't not put object {objectName} in container {request.Bucket}, Because a file with the same name already exists in the directory!"); + } + Directory.CreateDirectory(filePath); + var directoryInfo = new DirectoryInfo(filePath); + + var ossObject = new OssObject( + directoryInfo.Name.EnsureEndsWith('/'), + objectPath, + directoryInfo.CreationTime, + 0L, + directoryInfo.LastWriteTime, + new Dictionary + { + { "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }, + true) + { + FullName = directoryInfo.FullName.Replace(Environment.ContentRootPath, "") + }; + ossObject.SetContent(request.Content); + + return ossObject; + } + } + + public virtual Task DeleteAsync(string name) + { + var filePath = CalculateFilePath(name); + if (!Directory.Exists(filePath)) + { + // 非空目录无法删除 + if (Directory.GetFiles(filePath).Length > 0) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerDeleteWithNotEmpty); + // throw new ContainerDeleteWithNotEmptyException("00101", $"Can't not delete container {name}, because it is not empty!"); + } + Directory.Delete(filePath); + } + + return Task.CompletedTask; + } + + public virtual Task DeleteObjectAsync(GetOssObjectRequest request) + { + var objectName = request.Path.IsNullOrWhiteSpace() + ? request.Object + : request.Path.EnsureEndsWith('/') + request.Object; + var filePath = CalculateFilePath(request.Bucket, objectName); + if (!File.Exists(filePath)) + { + File.Delete(filePath); + } + else if (Directory.Exists(filePath)) + { + if (Directory.GetFiles(filePath).Length > 0) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectDeleteWithNotEmpty); + } + Directory.Delete(filePath); + } + + return Task.CompletedTask; + } + + public virtual Task ExistsAsync(string name) + { + var filePath = CalculateFilePath(name); + + return Task.FromResult(Directory.Exists(filePath)); + } + + public virtual Task GetAsync(string name) + { + var filePath = CalculateFilePath(name); + if (!Directory.Exists(filePath)) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); + // throw new ContainerNotFoundException($"Can't not found container {name} in file system"); + } + + var directoryInfo = new DirectoryInfo(filePath); + var container = new OssContainer( + directoryInfo.Name, + directoryInfo.CreationTime, + 0L, + directoryInfo.LastWriteTime, + new Dictionary + { + { "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }); + + return Task.FromResult(container); + } + + public virtual async Task GetObjectAsync(GetOssObjectRequest request) + { + var objectPath = !request.Path.IsNullOrWhiteSpace() + ? request.Path.EnsureEndsWith('/') + : ""; + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + var filePath = CalculateFilePath(request.Bucket, objectName); + if (!File.Exists(filePath)) + { + if (!Directory.Exists(filePath)) + { + throw new BusinessException(code: FileManagementErrorCodes.ObjectNotFound); + // throw new ContainerNotFoundException($"Can't not found object {objectName} in container {request.Bucket} with file system"); + } + var directoryInfo = new DirectoryInfo(filePath); + var ossObject = new OssObject( + directoryInfo.Name.EnsureEndsWith('/'), + objectPath, + directoryInfo.CreationTime, + 0L, + directoryInfo.LastWriteTime, + new Dictionary + { + { "LastAccessTime", directoryInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }, + true) + { + FullName = directoryInfo.FullName.Replace(Environment.ContentRootPath, "") + }; + return ossObject; + } + else + { + var fileInfo = new FileInfo(filePath); + var ossObject = new OssObject( + fileInfo.Name, + objectPath, + fileInfo.CreationTime, + fileInfo.Length, + fileInfo.LastWriteTime, + new Dictionary + { + { "IsReadOnly", fileInfo.IsReadOnly.ToString() }, + { "LastAccessTime", fileInfo.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }) + { + FullName = fileInfo.FullName.Replace(Environment.ContentRootPath, "") + }; + using (var fileStream = File.OpenRead(filePath)) + { + var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream); + ossObject.SetContent(memoryStream); + + if (!request.Process.IsNullOrWhiteSpace()) + { + using var serviceScope = ServiceProvider.CreateScope(); + var context = new FileSystemOssObjectContext(request.Process, ossObject, serviceScope.ServiceProvider); + foreach (var processer in Options.Processers) + { + await processer.ProcessAsync(context); + + if (context.Handled) + { + ossObject.SetContent(context.Content); + break; + } + } + } + } + + return ossObject; + } + } + + public virtual Task GetListAsync(GetOssContainersRequest request) + { + // 不传递Bucket 检索根目录的Bucket + var filePath = CalculateFilePath(null); + + // 获取根目录 + var directories = Directory.GetDirectories(filePath, request.Prefix ?? "*.*"); + + // 排序目录 + Array.Sort(directories, delegate (string x, string y) + { + return x.CompareTo(y); + }); + + var spiltDirectories = directories; + // 计算标记的位置进行截断 + if (!request.Marker.IsNullOrWhiteSpace()) + { + var markIndex = directories.FindIndex(x => x.EndsWith(request.Marker)); + if (markIndex < 0) + { + directories = new string[0]; + } + else + { + var markDirectories = new string[directories.Length - markIndex]; + Array.Copy(directories, markIndex, markDirectories, 0, markDirectories.Length); + directories = markDirectories; + } + } + // 需要截断最大的容器集合 + if (request.MaxKeys.HasValue) + { + spiltDirectories = directories.Take(request.MaxKeys ?? directories.Length).ToArray(); + } + var nextDirectory = spiltDirectories.Length < directories.Length ? directories[spiltDirectories.Length] : ""; + if (!nextDirectory.IsNullOrWhiteSpace()) + { + // 下一个标记的目录名称 + + nextDirectory = new DirectoryInfo(nextDirectory).Name; + } + // 容器对应的目录信息集合 + var directoryInfos = spiltDirectories.Select(x => new DirectoryInfo(x)); + // 返回Oss容器描述集合 + var response = new GetOssContainersResponse( + request.Prefix, + request.Marker, + nextDirectory, + directories.Length, + directoryInfos.Select(x => new OssContainer( + x.Name, + x.CreationTime, + 0L, + x.LastWriteTime, + new Dictionary + { + { "LastAccessTime", x.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + })) + .ToList()); + + return Task.FromResult(response); + } + + public virtual Task GetObjectsAsync(GetOssObjectsRequest request) + { + // 先定位检索的目录 + var filePath = CalculateFilePath(request.BucketName, request.Prefix); + if (!Directory.Exists(filePath)) + { + throw new BusinessException(code: FileManagementErrorCodes.ContainerNotFound); + // throw new ContainerNotFoundException($"Can't not found container {request.BucketName} in file system"); + } + // 目录也属于Oss对象,需要抽象的文件系统集合来存储 + var fileSystemNames = Directory.GetFileSystemEntries(filePath); + int maxFilesCount = fileSystemNames.Length; + + // 排序所有文件与目录 + Array.Sort(fileSystemNames, delegate (string x, string y) + { + // 检索的是文件系统名称 + // 需要判断是否为文件夹进行升序排序 + // 参考 OssObjectComparer + + var xFolder = Directory.Exists(x); + var yFolder = Directory.Exists(y); + + if (xFolder && yFolder) + { + return x.CompareTo(y); + } + + if (xFolder && !yFolder) + { + return -1; + } + + if (!xFolder && yFolder) + { + return 1; + } + + return x.CompareTo(y); + }); + + // 需要计算从哪个位置截断 + int markIndex = 0; + if (!request.Marker.IsNullOrWhiteSpace()) + { + markIndex = fileSystemNames.FindIndex(x => x.EndsWith(request.Marker)); + if (markIndex < 0) + { + markIndex = 0; + } + } + + // 需要截断Oss对象列表 + var copyFileSystemNames = fileSystemNames; + if (markIndex > 0) + { + copyFileSystemNames = fileSystemNames[(markIndex+1)..]; + } + // 截取指定数量的Oss对象 + int maxResultCount = request.MaxKeys ?? 10; + // Oss对象信息集合 + var fileSystems = copyFileSystemNames + .Take(maxResultCount) + .Select(file => + { + if (File.Exists(file)) + { + return new FileInfo(file); + } + return new DirectoryInfo(file); + }) + .ToArray(); + + // 计算下一页起始标记文件/目录名称 + var nextMarkerIndex = fileSystemNames.FindIndex(x => x.EndsWith(fileSystems[fileSystems.Length - 1].Name)); + string nextMarker = ""; + if (nextMarkerIndex >=0 && nextMarkerIndex + 1 < fileSystemNames.Length) + { + nextMarker = fileSystemNames[nextMarkerIndex + 1]; + nextMarker = File.Exists(nextMarker) + ? new FileInfo(nextMarker).Name + : new DirectoryInfo(nextMarker).Name.EnsureEndsWith('/'); + } + // 返回Oss对象描述集合 + var response = new GetOssObjectsResponse( + request.BucketName, + request.Prefix, + request.Marker, + nextMarker, + "/", // 文件系统目录分隔符 + fileSystemNames.Length, + fileSystems.Select(x => new OssObject( + (x is DirectoryInfo) ? x.Name.EnsureEndsWith('/') : x.Name, + request.Prefix, + x.CreationTime, + (x as FileInfo)?.Length ?? 0L, + x.LastWriteTime, + new Dictionary + { + { "LastAccessTime", x.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss") } + }, + x is DirectoryInfo) + { + FullName = x.FullName.Replace(Environment.ContentRootPath, "") + }) + .ToList()); + + return Task.FromResult(response); + } + + protected virtual FileSystemBlobProviderConfiguration GetFileSystemConfiguration() + { + var configuration = ConfigurationProvider.Get(); + var fileSystemConfiguration = configuration.GetFileSystemConfiguration(); + return fileSystemConfiguration; + } + + protected virtual string CalculateFilePath(string bucketName, string blobName = "") + { + var fileSystemConfiguration = GetFileSystemConfiguration(); + var blobPath = fileSystemConfiguration.BasePath; + + if (CurrentTenant.Id == null) + { + blobPath = Path.Combine(blobPath, "host"); + } + else + { + blobPath = Path.Combine(blobPath, "tenants", CurrentTenant.Id.Value.ToString("D")); + } + + if (fileSystemConfiguration.AppendContainerNameToBasePath && + !bucketName.IsNullOrWhiteSpace()) + { + blobPath = Path.Combine(blobPath, bucketName); + } + if (!blobName.IsNullOrWhiteSpace()) + { + blobPath = Path.Combine(blobPath, blobName); + } + + return blobPath; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainerFactory.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainerFactory.cs new file mode 100644 index 000000000..7943486d9 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssContainerFactory.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.FileSystem; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public class FileSystemOssContainerFactory : IOssContainerFactory + { + protected ICurrentTenant CurrentTenant { get; } + protected IHostEnvironment Environment { get; } + protected IServiceProvider ServiceProvider { get; } + protected IBlobFilePathCalculator FilePathCalculator { get; } + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } + protected IOptions Options { get; } + + public FileSystemOssContainerFactory( + ICurrentTenant currentTenant, + IHostEnvironment environment, + IServiceProvider serviceProvider, + IBlobFilePathCalculator blobFilePathCalculator, + IBlobContainerConfigurationProvider configurationProvider, + IOptions options) + { + Environment = environment; + CurrentTenant = currentTenant; + ServiceProvider = serviceProvider; + FilePathCalculator = blobFilePathCalculator; + ConfigurationProvider = configurationProvider; + Options = options; + } + + public IOssContainer Create() + { + return new FileSystemOssContainer( + CurrentTenant, + Environment, + ServiceProvider, + FilePathCalculator, + ConfigurationProvider, + Options); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssObjectContext.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssObjectContext.cs new file mode 100644 index 000000000..0cf679f0e --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssObjectContext.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public class FileSystemOssObjectContext : IServiceProviderAccessor + { + public string Process { get; } + public OssObject OssObject { get; } + public bool Handled { get; private set; } + public Stream Content { get; private set; } + public IServiceProvider ServiceProvider { get; } + + public FileSystemOssObjectContext( + string process, + OssObject ossObject, + IServiceProvider serviceProvider) + { + Process = process; + OssObject = ossObject; + ServiceProvider = serviceProvider; + } + + public void SetContent(Stream content) + { + Content = content; + Content.Seek(0, SeekOrigin.Begin); + Handled = true; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptions.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptions.cs new file mode 100644 index 000000000..7acd79069 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptions.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public class FileSystemOssOptions + { + [NotNull] + public List Processers { get; } + + public FileSystemOssOptions() + { + Processers = new List + { + new NoneFileSystemOssObjectProcesser() + }; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptionsExtensions.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptionsExtensions.cs new file mode 100644 index 000000000..0355abbf3 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssOptionsExtensions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public static class FileSystemOssOptionsExtensions + { + public static void AddProcesser( + this FileSystemOssOptions options, + TProcesserContributor contributor) + where TProcesserContributor : IFileSystemOssObjectProcesserContributor + { + options.Processers.InsertBefore((x) => x is NoneFileSystemOssObjectProcesser, contributor); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssProviderArgs.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssProviderArgs.cs new file mode 100644 index 000000000..c668145e7 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/FileSystemOssProviderArgs.cs @@ -0,0 +1,18 @@ +using System.Threading; +using Volo.Abp.BlobStoring; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public class FileSystemOssProviderArgs : BlobProviderArgs + { + public FileSystemOssProviderArgs( + string containerName, + BlobContainerConfiguration configuration, + string blobName, + CancellationToken cancellationToken = default) + : base(containerName, configuration, blobName, cancellationToken) + { + + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/IFileSystemOssObjectProcesserContributor.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/IFileSystemOssObjectProcesserContributor.cs new file mode 100644 index 000000000..0691634a5 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/IFileSystemOssObjectProcesserContributor.cs @@ -0,0 +1,10 @@ +using System.IO; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public interface IFileSystemOssObjectProcesserContributor + { + Task ProcessAsync(FileSystemOssObjectContext context); + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/NoneFileSystemOssObjectProcesser.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/NoneFileSystemOssObjectProcesser.cs new file mode 100644 index 000000000..954f79105 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/LINGYUN/Abp/FileManagement/FileSystem/NoneFileSystemOssObjectProcesser.cs @@ -0,0 +1,15 @@ +using System.IO; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.FileManagement.FileSystem +{ + public class NoneFileSystemOssObjectProcesser : IFileSystemOssObjectProcesserContributor + { + public Task ProcessAsync(FileSystemOssObjectContext context) + { + context.SetContent(context.OssObject.Content); + + return Task.CompletedTask; + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/README.md b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/README.md new file mode 100644 index 000000000..0d2276e8c --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.FileSystem/README.md @@ -0,0 +1,15 @@ +# LINGYUN.Abp.FileManagement.FileSystem + +本地文件系统oss容器接口 + +## 配置使用 + +模块按需引用 + +```csharp +[DependsOn(typeof(AbpFileManagementFileSystemModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssContainerController.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssContainerController.cs new file mode 100644 index 000000000..7809a2ade --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssContainerController.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.FileManagement +{ + [RemoteService(Name = "AbpFileManagement")] + [Area("file-management")] + [Route("api/file-management/containes")] + public class OssContainerController : AbpController, IOssContainerAppService + { + protected IOssContainerAppService OssContainerAppService { get; } + + public OssContainerController( + IOssContainerAppService ossContainerAppService) + { + OssContainerAppService = ossContainerAppService; + } + + [HttpPost] + [Route("{name}")] + public virtual async Task CreateAsync(string name) + { + return await OssContainerAppService.CreateAsync(name); + } + + [HttpDelete] + [Route("{name}")] + public virtual async Task DeleteAsync(string name) + { + await OssContainerAppService.DeleteAsync(name); + } + + [HttpGet] + [Route("{name}")] + public virtual async Task GetAsync(string name) + { + return await OssContainerAppService.GetAsync(name); + } + + [HttpGet] + public virtual async Task GetListAsync(GetOssContainersInput input) + { + return await OssContainerAppService.GetListAsync(input); + } + + [HttpGet] + [Route("objects")] + public virtual async Task GetObjectListAsync(GetOssObjectsInput input) + { + return await OssContainerAppService.GetObjectListAsync(input); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssObjectController.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssObjectController.cs new file mode 100644 index 000000000..f803316d5 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/OssObjectController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.FileManagement +{ + [RemoteService(Name = "AbpFileManagement")] + [Area("file-management")] + [Route("api/file-management/objects")] + public class OssObjectController : AbpController, IOssObjectAppService + { + protected IOssObjectAppService OssObjectAppService { get; } + + public OssObjectController( + IOssObjectAppService ossObjectAppService) + { + OssObjectAppService = ossObjectAppService; + } + + [HttpPost] + public virtual async Task CreateAsync(CreateOssObjectInput input) + { + return await OssObjectAppService.CreateAsync(input); + } + + [HttpPost] + [Route("upload")] + public virtual async Task UploadAsync([FromForm] UploadOssObjectInput input) + { + var createOssObjectInput = new CreateOssObjectInput + { + Bucket = input.Bucket, + Path = input.Path, + Object = input.Name + }; + if (input.File != null) + { + //if (input.File.Length <= 0) + //{ + // ThrowValidationException(L["FileNotBeNullOrEmpty"], "File"); + //} + createOssObjectInput.Object = input.File.FileName; + createOssObjectInput.Content = input.File.OpenReadStream(); + } + + return await OssObjectAppService.CreateAsync(createOssObjectInput); + } + + [HttpDelete] + [Route("bulk-delete")] + public virtual async Task BulkDeleteAsync(BulkDeleteOssObjectInput input) + { + await OssObjectAppService.BulkDeleteAsync(input); + } + + [HttpDelete] + public virtual async Task DeleteAsync(GetOssObjectInput input) + { + await OssObjectAppService.DeleteAsync(input); + } + + [HttpGet] + public virtual async Task GetAsync(GetOssObjectInput input) + { + return await OssObjectAppService.GetAsync(input); + } + + private static void ThrowValidationException(string message, string memberName) + { + throw new AbpValidationException(message, + new List + { + new ValidationResult(message, new[] {memberName}) + }); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/StaticFilesController.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/StaticFilesController.cs new file mode 100644 index 000000000..5d1284471 --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/StaticFilesController.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Http; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.FileManagement +{ + [RemoteService(Name = "AbpFileManagement")] + [Area("file-management")] + [Route("api/files/static")] + public class StaticFilesController : AbpController + { + private readonly IOssObjectAppService _ossObjectAppService; + private readonly IStaticFilesAppService _staticFilesAppServic; + + public StaticFilesController( + IOssObjectAppService ossObjectAppService, + IStaticFilesAppService staticFilesAppServic) + { + _ossObjectAppService = ossObjectAppService; + _staticFilesAppServic = staticFilesAppServic; + } + + [HttpPost] + [Route("{bucket}")] + [Route("{bucket}/{path}")] + public virtual async Task UploadAsync(string bucket, string path, [FromForm] IFormFile file) + { + if (file == null || file.Length <= 0) + { + ThrowValidationException(L["FileNotBeNullOrEmpty"], "File"); + } + + var createOssObjectInput = new CreateOssObjectInput + { + Bucket = bucket, + Path = path, + Object = file.FileName, + Content = file.OpenReadStream() + }; + + return await _ossObjectAppService.CreateAsync(createOssObjectInput); + } + + [HttpGet] + [Route("{bucket}/{name}")] + [Route("{bucket}/{name}/{process}")] + [Route("{bucket}/p/{path}/{name}")] + [Route("{bucket}/p/{path}/{name}/{process}")] + public virtual async Task GetAsync(string bucket, string path, string name, string process) + { + var input = new GetStaticFileInput + { + Bucket = bucket, + Name = name, + Path = path, + Process = process + }; + var fileStream = await _staticFilesAppServic.GetAsync(input); + + if (fileStream.IsNullOrEmpty()) + { + return NotFound(); + } + + return File( + fileStream, + MimeTypes.GetByExtension(Path.GetExtension(input.Name)) + ); + } + + private static void ThrowValidationException(string message, string memberName) + { + throw new AbpValidationException(message, + new List + { + new ValidationResult(message, new[] {memberName}) + }); + } + } +} diff --git a/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/UploadOssObjectInput.cs b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/UploadOssObjectInput.cs new file mode 100644 index 000000000..856e44c5a --- /dev/null +++ b/aspnet-core/modules/file-management/LINGYUN.Abp.FileManagement.HttpApi/LINGYUN/Abp/FileManagement/UploadOssObjectInput.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Http; + +namespace LINGYUN.Abp.FileManagement +{ + public class UploadOssObjectInput + { + public string Bucket { get; set; } + public string Path { get; set; } + public string Name { get; set; } + public IFormFile File { get; set; } + } +} diff --git a/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db b/aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db index d9036542137ba37f7f6a785c0ba2090ed4fcba46..87d7d4360c7dda52c45684dbe6779f18a6b41ab6 100644 GIT binary patch delta 2562 zcmeH}yH8V59LG;D^t#Ev-Z%Ny`9b;d0)O8wUxS76xDj5xTi*VoXdi4KYz8jnl6qE{s;aGGU>wP;84%Gw~u>nF~JdIn-0Inx&549&iQ_C4#4Bc%}HRns=s~H8;UDH&H- zC@!$TiU+%eQh_IuFhH}f3S?PUzI}dv`tlj`3G#^y=E}FPktHa%kccuKMI2Mbg0sSW zkb7L-KugdxR0a)%P@)KiI4Y?@NlJ(Xl$#Is_UiTh`hN4yXcu~?3+-;z2hb8J{Vbsr zi9yGb=RzZFpi41mggu71e$pS9O`Sk4I38j~#DkC`s$$Hwv_0tR_q}M)c{5W12`366 zA+i3yC&g&eR(5F8(5KX%w8?m8hDwqi^=5q1Q3FMg4pHk8^C%Tea5R`S0PYX_e5|iU zqc+$AVM&=nN>V<+wzEUn*vH()HW?2iAs|CNZ0mqT4Mfif(!{XCl3_-a^sxa*zp<$= z!KntoS&yJE gS`jZDB_dfn@Pgh>&wmOUEsDLruvDVcU;OgL(4$OT8qdFi{o$w-n zv<@@ZyoKjObuS2lDG_12{U!Om!CEr+TB^S#~N#op7Bh$hNNBnN}FSExv};($0HKuA5nAu*BQ0IJkKfJ;wsLV{DoAsj%01H9SYtaoh=hy&Cs%bNFQ zyq|eLzB8-0+|^s|`hl(!Hx3@_ISA~u)6$qMKY#=ajhujpox*ePHm{%ZVkrHe$WfPP(D+^*!^ zr{V<%fx>dM-bi_<%CafdCyRxC*MtDq)nST+h6|xVW@*X6a00s;56+sdrn-W0vxZ z-w$o3D8XpSIsOrFd{u2IV$WFXbMFqqVW8C?zg+w2`jwCvL_0AEk0{(Kg@C>=VQC); zX{{Xt!8LT?qDv7jjN=rhnx|f#adVE7|F!w~xfyrFadIE-a^JntbsTqkm-0W3?oU*Wx?XBDAH>aImoG}&GvXjTb<8jG2Jvd-E`2M?JrX9?b z^AHYh*UnEn-8k4c_|e?*_6l)a9Q5Ph#m%ot^UuR>b*OL7$+@FWVIcpF?Hl`S)cf3# z#?wnYrPTP8`oc=-#JyykVtBRXxU<&3=MLjMF_PuBal<%6 z@WcKmS(X}IQVA_M?Vx7F6NYd_bo00Qz15=CsXh3$`qR5n-|1==F8)z0=iMVtajWm8 z!f5_Z?y7raqkfOsJP(73 z=eFW8S^-NsT<=N4K$;9)sWF_EY#lW4gZ9M0mQUsCqQsIOl!Ur&+?2#F!r)yGaJ(p@C+5ebS8Y!66Rqka6wIq zZlI0sv9H4LSvuULW{gTuVwcV;PF!@^2^qRjmlWQX3?#{&K!nI{<%QX+R62Ijw;co$ z6-cD$ifwcc-@($mYT-aLZWPvVD|FH)vIUo#&<01O2TCz%lEktQ&f2xXx|JPpMB!x? JQ%hQX@fRYa_Q?PM diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.cs b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.cs index 56eb4b6a4..937513ffa 100644 --- a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.cs +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/AppPlatformHttpApiHostModule.cs @@ -3,6 +3,9 @@ using LINGYUN.Abp.EventBus.CAP; using LINGYUN.Abp.ExceptionHandling; using LINGYUN.Abp.ExceptionHandling.Emailing; using LINGYUN.Abp.FileManagement; +using LINGYUN.Abp.FileManagement.Aliyun; +using LINGYUN.Abp.FileManagement.FileSystem; +using LINGYUN.Abp.FileManagement.FileSystem.ImageSharp; using LINGYUN.Abp.MultiTenancy.DbFinder; using LINGYUN.Abp.Notifications; using LINGYUN.Platform.EntityFrameworkCore; @@ -16,11 +19,14 @@ using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using StackExchange.Redis; using System; using System.IO; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Unicode; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.MultiTenancy; @@ -35,7 +41,10 @@ using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Http.Client.IdentityModel.Web; using Volo.Abp.Identity; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; @@ -46,15 +55,13 @@ using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; -using Volo.Abp.Http.Client.IdentityModel.Web; -using Volo.Abp.Json; -using Volo.Abp.Json.SystemTextJson; -using System.Text.Encodings.Web; -using System.Text.Unicode; namespace LINGYUN.Platform { [DependsOn( + // typeof(AbpFileManagementAliyunModule), + typeof(AbpFileManagementFileSystemModule), // 本地文件系统提供者模块 + typeof(AbpFileManagementFileSystemImageSharpModule), // 本地文件系统图形处理模块 typeof(AbpFileManagementApplicationModule), typeof(AbpFileManagementHttpApiModule), typeof(PlatformApplicationModule), @@ -210,19 +217,6 @@ namespace LINGYUN.Platform options.IsEnabled = true; }); - var tenantResolveCfg = configuration.GetSection("App:Domains"); - if (tenantResolveCfg.Exists()) - { - Configure(options => - { - var domains = tenantResolveCfg.Get(); - foreach (var domain in domains) - { - options.AddDomainTenantResolver(domain); - } - }); - } - Configure(options => { options.ApplicationName = "Platform"; @@ -313,6 +307,8 @@ namespace LINGYUN.Platform app.UseVirtualFiles(); // 本地化 app.UseAbpRequestLocalization(); + // 多租户 + app.UseMultiTenancy(); //路由 app.UseRouting(); // 认证 @@ -322,8 +318,6 @@ namespace LINGYUN.Platform app.UseJwtTokenMiddleware(); // 授权 app.UseAuthorization(); - // 多租户 - app.UseMultiTenancy(); // Swagger app.UseSwagger(); // Swagger可视化界面 diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/DomainTenantResolveContributor.cs b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/DomainTenantResolveContributor.cs new file mode 100644 index 000000000..aad2cda78 --- /dev/null +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/DomainTenantResolveContributor.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Text.Formatting; + +namespace LINGYUN.Platform +{ + public class DomainTenantResolveContributor : HttpTenantResolveContributorBase + { + public const string ContributorName = "Domain"; + + public override string Name => ContributorName; + + private static readonly string[] ProtocolPrefixes = { "http://", "https://" }; + + private readonly string _domainFormat; + + public DomainTenantResolveContributor(string domainFormat) + { + _domainFormat = domainFormat.RemovePreFix(ProtocolPrefixes); + } + + protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) + { + if (!httpContext.Request.Host.HasValue) + { + return Task.FromResult(null); + } + + var hostName = httpContext.Request.Host.Value.RemovePreFix(ProtocolPrefixes); + var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); + + context.Handled = true; + + return Task.FromResult(extractResult.IsMatch ? extractResult.Matches[0].Value : null); + } + } +} diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/HeaderTenantResolveContributor.cs b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/HeaderTenantResolveContributor.cs new file mode 100644 index 000000000..51e73802c --- /dev/null +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/HeaderTenantResolveContributor.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Platform +{ + public class HeaderTenantResolveContributor : HttpTenantResolveContributorBase + { + public const string ContributorName = "Header"; + + public override string Name => ContributorName; + + protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) + { + if (httpContext.Request.Headers.IsNullOrEmpty()) + { + return Task.FromResult((string)null); + } + + var tenantIdKey = context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey; + + var tenantIdHeader = httpContext.Request.Headers[tenantIdKey]; + if (tenantIdHeader == string.Empty || tenantIdHeader.Count < 1) + { + return Task.FromResult((string)null); + } + + if (tenantIdHeader.Count > 1) + { + Log(context, $"HTTP request includes more than one {tenantIdKey} header value. First one will be used. All of them: {tenantIdHeader.JoinAsString(", ")}"); + } + + return Task.FromResult(tenantIdHeader.First()); + } + + protected virtual void Log(ITenantResolveContext context, string text) + { + context + .ServiceProvider + .GetRequiredService>() + .LogWarning(text); + } + } +} diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj index 42351bc48..5c203167a 100644 --- a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj @@ -54,7 +54,10 @@ + + + diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantConfigurationProvider.cs b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantConfigurationProvider.cs new file mode 100644 index 000000000..a4c25c5b1 --- /dev/null +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantConfigurationProvider.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Platform +{ + [Dependency(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient, ReplaceServices = true)] + [ExposeServices(typeof(ITenantConfigurationProvider))] + public class TenantConfigurationProvider : ITenantConfigurationProvider + { + protected virtual ITenantResolver TenantResolver { get; } + protected virtual ITenantStore TenantStore { get; } + protected virtual ITenantResolveResultAccessor TenantResolveResultAccessor { get; } + + public TenantConfigurationProvider( + ITenantResolver tenantResolver, + ITenantStore tenantStore, + ITenantResolveResultAccessor tenantResolveResultAccessor) + { + TenantResolver = tenantResolver; + TenantStore = tenantStore; + TenantResolveResultAccessor = tenantResolveResultAccessor; + } + + public virtual async Task GetAsync(bool saveResolveResult = false) + { + var resolveResult = await TenantResolver.ResolveTenantIdOrNameAsync(); + + if (saveResolveResult) + { + TenantResolveResultAccessor.Result = resolveResult; + } + + TenantConfiguration tenant = null; + if (resolveResult.TenantIdOrName != null) + { + tenant = await FindTenantAsync(resolveResult.TenantIdOrName); + + if (tenant == null) + { + throw new BusinessException( + code: "Volo.AbpIo.MultiTenancy:010001", + message: "Tenant not found!", + details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName + ); + } + } + + return tenant; + } + + protected virtual async Task FindTenantAsync(string tenantIdOrName) + { + if (Guid.TryParse(tenantIdOrName, out var parsedTenantId)) + { + return await TenantStore.FindAsync(parsedTenantId); + } + else + { + return await TenantStore.FindAsync(tenantIdOrName); + } + } + } +} diff --git a/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantResolver.cs b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantResolver.cs new file mode 100644 index 000000000..26ed3c1d4 --- /dev/null +++ b/aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/TenantResolver.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Platform +{ + [Dependency(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient, ReplaceServices = true)] + [ExposeServices(typeof(ITenantResolver))] + public class TenantResolver : ITenantResolver + { + private readonly IServiceProvider _serviceProvider; + private readonly AbpTenantResolveOptions _options; + + public TenantResolver(IOptions options, IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _options = options.Value; + } + + public virtual async Task ResolveTenantIdOrNameAsync() + { + var result = new TenantResolveResult(); + + using (var serviceScope = _serviceProvider.CreateScope()) + { + var context = new TenantResolveContext(serviceScope.ServiceProvider); + + foreach (var tenantResolver in _options.TenantResolvers) + { + await tenantResolver.ResolveAsync(context); + + result.AppliedResolvers.Add(tenantResolver.Name); + + if (context.Handled || context.TenantIdOrName != null) + { + result.TenantIdOrName = context.TenantIdOrName; + break; + } + } + } + + return result; + } + } +} diff --git a/vueJs/src/api/oss-manager.ts b/vueJs/src/api/oss-manager.ts new file mode 100644 index 000000000..517e9d9c2 --- /dev/null +++ b/vueJs/src/api/oss-manager.ts @@ -0,0 +1,129 @@ +import ApiService from './serviceBase' +import { PagedAndSortedResultRequestDto } from './types' +import { urlStringify } from '@/utils/index' + +const serviceUrl = process.env.VUE_APP_BASE_API +const containerUrl = '/api/file-management/containes' +const objectUrl = '/api/file-management/objects' +export const objectUploadUrl = objectUrl + '/upload' +export const staticUrl = '/api/files/static/' + +export default class OssManager { + public static createBucket(name: string) { + const _url = containerUrl + '/' + name + return ApiService.Post(_url, null, serviceUrl) + } + + public static getBucket(name: string) { + const _url = containerUrl + '/' + name + return ApiService.Get(_url, serviceUrl) + } + + public static getBuckets(input: GetOssContainerRequest) { + const _url = containerUrl + '?' + urlStringify(input) + return ApiService.Get(_url, serviceUrl) + } + + public static deleteBucket(name: string) { + const _url = containerUrl + '/' + name + return ApiService.Delete(_url, serviceUrl) + } + + public static createFolder(bucket: string, name: string, path: string = "") { + const input = { + bucket: bucket, + object: name, + path: path + } + return ApiService.Post(objectUrl, input, serviceUrl) + } + + public static getObjects(input: GetOssObjectRequest) { + const _url = containerUrl + '/objects?' + urlStringify(input) + return ApiService.Get(_url, serviceUrl) + } + + public static getObject(bucket: string, object: string, path: string = "") { + let _url = objectUrl + '?bucket=' + bucket + '&object=' + object + if (path) { + _url += '&path=' + path + } + return ApiService.Get(_url, serviceUrl) + } + + public static getObjectData(bucket: string, object: string, path: string = "") { + let _url = staticUrl + bucket + '/' + if (path) { + // 某些情况下要对 / 编码 + _url += '/p/' + path.replace('/', '%2F') + if (_url.endsWith('%2F')) { + _url = _url.substring(0, _url.length - 3) + '/' + } + } + _url += object + return ApiService.HttpRequest({ + url: _url, + baseURL: serviceUrl, + method: 'GET', + responseType: 'blob' + }) + } + + public static deleteObject(bucket: string, object: string, path: string = "") { + let _url = objectUrl + '?bucket=' + bucket + '&object=' + object + if (path) { + _url += '&path=' + path + } + return ApiService.Delete(_url, serviceUrl) + } +} + +export class GetOssContainerRequest extends PagedAndSortedResultRequestDto { + prefix?: string + marker?: string +} + +export class GetOssObjectRequest extends PagedAndSortedResultRequestDto { + bucket!: string + prefix?: string + delimiter?: string = '/' + marker?: string + encodingType?: string + maxResultCount = 100 +} + +export class OssContainer { + name!: string + size!: number + creationDate?: Date + lastModifiedDate?: Date + metadata?: {[key: string]: string} +} + +export class OssContainerResultList { + prefix?: string + marker?: string + nextMarker?: string + maxKeys?: number + containers!: OssContainer[] +} + +export class OssObject { + name!: string + path?: string + size!: number + isFolder!: boolean + creationDate?: Date + lastModifiedDate?: Date + metadata?: {[key: string]: string} +} + +export class OssObjectResultList { + bucket!: string + prefix?: string + delimiter?: string + marker?: string + nextMarker?: string + maxKeys?: number + objects!: OssObject[] +} diff --git a/vueJs/src/directives/el-table-lazy-load/index.ts b/vueJs/src/directives/el-table-lazy-load/index.ts new file mode 100644 index 000000000..dd2b8b48f --- /dev/null +++ b/vueJs/src/directives/el-table-lazy-load/index.ts @@ -0,0 +1,17 @@ +import { DirectiveOptions } from 'vue' + +export const elTableLazyLoad: DirectiveOptions = { + inserted(el, binding) { + const selectWrap = el.querySelector('.el-table__body-wrapper') + if (selectWrap) { + selectWrap.addEventListener('scroll', function() { + console.log('onscroll directive') + let sign = 0 + const scrollDistance = selectWrap.scrollHeight - selectWrap.scrollTop - selectWrap.clientHeight + if (scrollDistance <= sign) { + binding.value() + } + }) + } + } +} diff --git a/vueJs/src/directives/index.ts b/vueJs/src/directives/index.ts index 1a8582333..3f2582c2c 100644 --- a/vueJs/src/directives/index.ts +++ b/vueJs/src/directives/index.ts @@ -1,4 +1,5 @@ export * from './clipboard' export * from './el-draggable-dialog' +export * from './el-table-lazy-load' export * from './permission' export * from './waves' diff --git a/vueJs/src/views/oss-management/components/OssObjectProfile.vue b/vueJs/src/views/oss-management/components/OssObjectProfile.vue new file mode 100644 index 000000000..5ab7611a6 --- /dev/null +++ b/vueJs/src/views/oss-management/components/OssObjectProfile.vue @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vueJs/src/views/oss-management/index.vue b/vueJs/src/views/oss-management/index.vue new file mode 100644 index 000000000..1dd052413 --- /dev/null +++ b/vueJs/src/views/oss-management/index.vue @@ -0,0 +1,476 @@ + + + + + + + + + onBreadCrumbClick(event, index)" + > + {{ fileRoot }} + + + + + + + + + + + + {{ row.name }} + + + + + {{ row.creationDate | dateTimeFilter }} + + + + + {{ row.lastModifiedDate | dateTimeFilter }} + + + + + {{ row.isFolder ? $t('fileSystem.folder') : '标准存储' }} + + + + + {{ row.size | fileSystemSizeFilter }} + + + + + + + + + + + + + + + + + +