From f65bcd2c37c8b6e3c54bb168def2f120986e05bc Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 24 Oct 2024 13:41:10 +0800 Subject: [PATCH] feat(oss-management): add oss module Minio implementation --- aspnet-core/LINGYUN.MicroService.All.sln | 25 +- .../LINGYUN.MicroService.SingleProject.sln | 17 +- .../Aliyun/AliyunOssContainer.cs | 6 +- .../AbpOssManagementContainer.cs | 2 +- .../Abp/OssManagement/IOssContainer.cs | 4 + .../OssObjectAcknowledgeHandler.cs | 4 +- .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + .../LINGYUN.Abp.OssManagement.Minio.csproj | 24 + .../Minio/AbpOssManagementMinioModule.cs | 23 + .../OssManagement/Minio/MinioOssContainer.cs | 558 ++++++++++++++++++ .../Minio/MinioOssContainerFactory.cs | 44 ++ .../LINGYUN.Abp.OssManagement.Minio/README.md | 19 + .../OssManagement/Nexus/NexusOssContainer.cs | 6 +- .../Tencent/TencentOssContainer.cs | 7 +- ...LY.MicroService.Applications.Single.csproj | 1 + ...rviceApplicationsSingleModule.Configure.cs | 13 +- .../MicroServiceApplicationsSingleModule.cs | 10 +- .../appsettings.Development.json | 8 + ...GYUN.Abp.OssManagement.Domain.Tests.csproj | 23 + .../AbpOssManagementDomainTestBase.cs | 6 + .../AbpOssManagementDomainTestsModule.cs | 11 + .../Abp/OssManagement/OssContainer_Tests.cs | 248 ++++++++ ...NGYUN.Abp.OssManagement.Minio.Tests.csproj | 23 + .../Minio/AbpOssManagementMinioTestBase.cs | 6 + .../Minio/AbpOssManagementMinioTestsModule.cs | 43 ++ .../Minio/MinioOssContainer_Tests.cs | 4 + 27 files changed, 1140 insertions(+), 28 deletions(-) create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xml create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xsd create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs create mode 100644 aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/README.md create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN.Abp.OssManagement.Domain.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestsModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/OssContainer_Tests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN.Abp.OssManagement.Minio.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestsModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer_Tests.cs diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 8c1778367..373a884f8 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -758,9 +758,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenIddict.AspN EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Notifications", "modules\identity\LINGYUN.Abp.Identity.Notifications\LINGYUN.Abp.Identity.Notifications.csproj", "{54BBA043-317B-4A4F-B583-513D08BC25A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Work.Handlers\LINGYUN.Abp.WeChat.Work.Handlers.csproj", "{79FA2CBA-2904-4D80-A217-DCC0A38F93C4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Work.Handlers\LINGYUN.Abp.WeChat.Work.Handlers.csproj", "{79FA2CBA-2904-4D80-A217-DCC0A38F93C4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Official.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Official.Handlers\LINGYUN.Abp.WeChat.Official.Handlers.csproj", "{E469F047-6AD0-4D2B-9900-46358DA3BC30}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Official.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Official.Handlers\LINGYUN.Abp.WeChat.Official.Handlers.csproj", "{E469F047-6AD0-4D2B-9900-46358DA3BC30}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.Minio", "modules\oss-management\LINGYUN.Abp.OssManagement.Minio\LINGYUN.Abp.OssManagement.Minio.csproj", "{EB9F1905-1798-4766-8347-A8D2A9DBFAED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Minio.Tests", "tests\LINGYUN.Abp.OssManagement.Minio.Tests\LINGYUN.Abp.OssManagement.Minio.Tests.csproj", "{CCE5C620-E17A-4EB1-A17A-9F90311B197D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Domain.Tests", "tests\LINGYUN.Abp.OssManagement.Domain.Tests\LINGYUN.Abp.OssManagement.Domain.Tests.csproj", "{F2AD691B-71B9-4B86-95BC-E020DA6B1E4A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1952,6 +1958,18 @@ Global {E469F047-6AD0-4D2B-9900-46358DA3BC30}.Debug|Any CPU.Build.0 = Debug|Any CPU {E469F047-6AD0-4D2B-9900-46358DA3BC30}.Release|Any CPU.ActiveCfg = Release|Any CPU {E469F047-6AD0-4D2B-9900-46358DA3BC30}.Release|Any CPU.Build.0 = Release|Any CPU + {EB9F1905-1798-4766-8347-A8D2A9DBFAED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB9F1905-1798-4766-8347-A8D2A9DBFAED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB9F1905-1798-4766-8347-A8D2A9DBFAED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB9F1905-1798-4766-8347-A8D2A9DBFAED}.Release|Any CPU.Build.0 = Release|Any CPU + {CCE5C620-E17A-4EB1-A17A-9F90311B197D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCE5C620-E17A-4EB1-A17A-9F90311B197D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCE5C620-E17A-4EB1-A17A-9F90311B197D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCE5C620-E17A-4EB1-A17A-9F90311B197D}.Release|Any CPU.Build.0 = Release|Any CPU + {F2AD691B-71B9-4B86-95BC-E020DA6B1E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2AD691B-71B9-4B86-95BC-E020DA6B1E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2AD691B-71B9-4B86-95BC-E020DA6B1E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2AD691B-71B9-4B86-95BC-E020DA6B1E4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2320,6 +2338,9 @@ Global {54BBA043-317B-4A4F-B583-513D08BC25A7} = {52B5D4F7-237B-4E0A-A167-68442164F70A} {79FA2CBA-2904-4D80-A217-DCC0A38F93C4} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21} {E469F047-6AD0-4D2B-9900-46358DA3BC30} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21} + {EB9F1905-1798-4766-8347-A8D2A9DBFAED} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6} + {CCE5C620-E17A-4EB1-A17A-9F90311B197D} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} + {F2AD691B-71B9-4B86-95BC-E020DA6B1E4A} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.sln b/aspnet-core/LINGYUN.MicroService.SingleProject.sln index 09f561a76..d184946ab 100644 --- a/aspnet-core/LINGYUN.MicroService.SingleProject.sln +++ b/aspnet-core/LINGYUN.MicroService.SingleProject.sln @@ -587,15 +587,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Demo.HttpApi", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "exporter", "exporter", "{4A2CF141-F32D-45A0-8665-B3705667A6D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Application.Contracts", "framework\exporter\LINGYUN.Abp.Exporter.Application.Contracts\LINGYUN.Abp.Exporter.Application.Contracts.csproj", "{A3924A79-1ADC-458D-8764-3958297BDEB0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Exporter.Application.Contracts", "framework\exporter\LINGYUN.Abp.Exporter.Application.Contracts\LINGYUN.Abp.Exporter.Application.Contracts.csproj", "{A3924A79-1ADC-458D-8764-3958297BDEB0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Application", "framework\exporter\LINGYUN.Abp.Exporter.Application\LINGYUN.Abp.Exporter.Application.csproj", "{38A933EB-82F1-42A6-ABF3-F55975B4078E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Exporter.Application", "framework\exporter\LINGYUN.Abp.Exporter.Application\LINGYUN.Abp.Exporter.Application.csproj", "{38A933EB-82F1-42A6-ABF3-F55975B4078E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Core", "framework\exporter\LINGYUN.Abp.Exporter.Core\LINGYUN.Abp.Exporter.Core.csproj", "{03C9FFB2-E9A3-4CCC-A6B1-8B248BFBCB65}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Exporter.Core", "framework\exporter\LINGYUN.Abp.Exporter.Core\LINGYUN.Abp.Exporter.Core.csproj", "{03C9FFB2-E9A3-4CCC-A6B1-8B248BFBCB65}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.MiniExcel", "framework\exporter\LINGYUN.Abp.Exporter.MiniExcel\LINGYUN.Abp.Exporter.MiniExcel.csproj", "{CBC64BD6-297B-48F6-A3BA-0DBB4B0F5A69}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Exporter.MiniExcel", "framework\exporter\LINGYUN.Abp.Exporter.MiniExcel\LINGYUN.Abp.Exporter.MiniExcel.csproj", "{CBC64BD6-297B-48F6-A3BA-0DBB4B0F5A69}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.MagicodesIE.Excel", "framework\exporter\LINGYUN.Abp.Exporter.MagicodesIE.Excel\LINGYUN.Abp.Exporter.MagicodesIE.Excel.csproj", "{319428B9-CE7F-4027-92FA-6311C4CE95FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Exporter.MagicodesIE.Excel", "framework\exporter\LINGYUN.Abp.Exporter.MagicodesIE.Excel\LINGYUN.Abp.Exporter.MagicodesIE.Excel.csproj", "{319428B9-CE7F-4027-92FA-6311C4CE95FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.Minio", "modules\oss-management\LINGYUN.Abp.OssManagement.Minio\LINGYUN.Abp.OssManagement.Minio.csproj", "{9AE3E97E-8846-4315-9546-FF97E97FD49F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1575,6 +1577,10 @@ Global {319428B9-CE7F-4027-92FA-6311C4CE95FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {319428B9-CE7F-4027-92FA-6311C4CE95FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {319428B9-CE7F-4027-92FA-6311C4CE95FB}.Release|Any CPU.Build.0 = Release|Any CPU + {9AE3E97E-8846-4315-9546-FF97E97FD49F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AE3E97E-8846-4315-9546-FF97E97FD49F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AE3E97E-8846-4315-9546-FF97E97FD49F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AE3E97E-8846-4315-9546-FF97E97FD49F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1866,6 +1872,7 @@ Global {03C9FFB2-E9A3-4CCC-A6B1-8B248BFBCB65} = {4A2CF141-F32D-45A0-8665-B3705667A6D2} {CBC64BD6-297B-48F6-A3BA-0DBB4B0F5A69} = {4A2CF141-F32D-45A0-8665-B3705667A6D2} {319428B9-CE7F-4027-92FA-6311C4CE95FB} = {4A2CF141-F32D-45A0-8665-B3705667A6D2} + {9AE3E97E-8846-4315-9546-FF97E97FD49F} = {3AD66E47-B667-40D1-AE61-F5EC186241F7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs index 99687ff14..ae96376ca 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs @@ -130,10 +130,12 @@ public async virtual Task DeleteAsync(string name) // 阿里云oss在控制台设置即可,无需改变 var ossClient = await CreateClientAsync(); - if (BucketExists(ossClient, name)) + if (!BucketExists(ossClient, name)) { - ossClient.DeleteBucket(name); + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); } + + ossClient.DeleteBucket(name); } public async virtual Task ExpireAsync(ExprieOssObjectRequest request) diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementContainer.cs index cf9c14891..afbcc9547 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementContainer.cs @@ -2,7 +2,7 @@ namespace LINGYUN.Abp.OssManagement; -[BlobContainerName("abp-oss-management")] +[BlobContainerName("abp-blobs")] public class AbpOssManagementContainer { } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssContainer.cs index a3a2994bd..5f5169a1b 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssContainer.cs @@ -36,6 +36,10 @@ public interface IOssContainer /// /// /// + /// + /// When the bucket does not exist, an exception is thrown with the error code is + /// + /// Task DeleteAsync(string name); /// /// 删除Oss对象 diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs index 4ecc1b761..964708cc4 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs @@ -22,9 +22,9 @@ public async virtual Task HandleEventAsync(OssObjectAcknowledgeEto eventData) var ossContainer = _containerFactory.Create(); var tempPath = HttpUtility.UrlDecode(GetTempPath(eventData.TempPath)); - var tempObkect = HttpUtility.UrlDecode(GetTempObject(eventData.TempPath)); + var tempObject = HttpUtility.UrlDecode(GetTempObject(eventData.TempPath)); - var ossObject = await ossContainer.GetObjectAsync(Bucket, tempObkect, tempPath, createPathIsNotExists: true); + var ossObject = await ossContainer.GetObjectAsync(Bucket, tempObject, tempPath, createPathIsNotExists: true); using (ossObject.Content) { diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xml b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xsd b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj new file mode 100644 index 000000000..4b84a0544 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN.Abp.OssManagement.Minio.csproj @@ -0,0 +1,24 @@ + + + + + + + net8.0 + LINGYUN.Abp.OssManagement.Minio + LINGYUN.Abp.OssManagement.Minio + false + false + false + + + + + + + + + + + + diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs new file mode 100644 index 000000000..141764965 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.BlobStoring.Minio; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.OssManagement.Minio; + +[DependsOn( + typeof(AbpBlobStoringMinioModule), + typeof(AbpOssManagementDomainModule))] +public class AbpOssManagementMinioModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTransient(); + + context.Services.AddTransient(provider => + provider + .GetRequiredService() + .Create() + .As()); + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs new file mode 100644 index 000000000..90b76d769 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer.cs @@ -0,0 +1,558 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Minio; +using Minio.DataModel.Args; +using Minio.DataModel.ILM; +using Minio.Exceptions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.Minio; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.OssManagement.Minio; + +/// +/// Oss容器的Minio实现 +/// +public class MinioOssContainer : IOssContainer, IOssObjectExpireor +{ + protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; } + protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } + + protected IClock Clock { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ILogger Logger { get; } + + public MinioOssContainer( + IClock clock, + ICurrentTenant currentTenant, + ILogger logger, + IMinioBlobNameCalculator minioBlobNameCalculator, + IBlobNormalizeNamingService blobNormalizeNamingService, + IBlobContainerConfigurationProvider configurationProvider) + { + Clock = clock; + Logger = logger; + CurrentTenant = currentTenant; + MinioBlobNameCalculator = minioBlobNameCalculator; + BlobNormalizeNamingService = blobNormalizeNamingService; + ConfigurationProvider = configurationProvider; + } + + public async virtual Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request) + { + var client = GetMinioClient(); + + var bucket = GetBucket(request.Bucket); + + var prefixPath = GetPrefixPath(); + var path = GetBlobPath(prefixPath, request.Path); + + var args = new RemoveObjectsArgs() + .WithBucket(bucket) + .WithObjects(request.Objects.Select((x) => path + x.RemovePreFix("/")).ToList()); + + var response = await client.RemoveObjectsAsync(args); + + var tcs = new TaskCompletionSource(); + + using var _ = response.Subscribe( + onNext: (error) => + { + Logger.LogWarning("Batch deletion of objects failed, error details {code}: {message}", error.Code, error.Message); + }, + onError: tcs.SetException, + onCompleted: () => tcs.SetResult(true)); + + await tcs.Task; + } + + public async virtual Task CreateAsync(string name) + { + var client = GetMinioClient(); + + var bucket = GetBucket(name); + + if (await BucketExists(client, bucket)) + { + throw new BusinessException(code: OssManagementErrorCodes.ContainerAlreadyExists); + } + + await client.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucket)); + + return new OssContainer( + name, + Clock.Now, + 0L, + Clock.Now, + new Dictionary()); + } + + public async virtual Task CreateObjectAsync(CreateOssObjectRequest request) + { + var client = GetMinioClient(); + + var bucket = GetBucket(request.Bucket); + var prefixPath = GetPrefixPath(); + var objectPath = GetBlobPath(prefixPath, request.Path); + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + if (!request.Overwrite && await ObjectExists(client, bucket, objectName)) + { + throw new BusinessException(code: OssManagementErrorCodes.ObjectAlreadyExists); + } + + // 没有bucket则创建 + if (!await BucketExists(client, bucket)) + { + var configuration = GetMinioConfiguration(); + if (!configuration.CreateBucketIfNotExists) + { + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); + } + await client.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucket)); + } + if (request.Content.IsNullOrEmpty()) + { + var emptyContent = "This is an OSS object that simulates a directory.".GetBytes(); + request.SetContent(new MemoryStream(emptyContent)); + } + var putResponse = await client.PutObjectAsync(new PutObjectArgs() + .WithBucket(bucket) + .WithObject(objectName) + .WithStreamData(request.Content) + .WithObjectSize(request.Content.Length)); + + if (request.ExpirationTime.HasValue) + { + var lifecycleRule = new LifecycleRule + { + Status = "Enabled", + ID = putResponse.Etag, + Expiration = new Expiration(Clock.Now.Add(request.ExpirationTime.Value)) + }; + var lifecycleConfiguration = new LifecycleConfiguration(); + lifecycleConfiguration.Rules.Add(lifecycleRule); + + var lifecycleArgs = new SetBucketLifecycleArgs() + .WithBucket(bucket) + .WithLifecycleConfiguration(lifecycleConfiguration); + + await client.SetBucketLifecycleAsync(lifecycleArgs); + } + + var ossObject = new OssObject( + !objectPath.IsNullOrWhiteSpace() + ? objectName.Replace(objectPath, "") + : objectName, + objectPath.Replace(prefixPath, ""), + putResponse.Etag, + Clock.Now, + putResponse.Size, + Clock.Now, + new Dictionary(), + objectName.EndsWith('/')) + { + FullName = objectName.Replace(prefixPath, "") + }; + + if (!Equals(request.Content, Stream.Null)) + { + request.Content.Seek(0, SeekOrigin.Begin); + ossObject.SetContent(request.Content); + } + + return ossObject; + } + + public async virtual Task DeleteAsync(string name) + { + var client = GetMinioClient(); + var bucket = GetBucket(name); + + if (!await BucketExists(client, bucket)) + { + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); + } + + // 非空目录无法删除 + var tcs = new TaskCompletionSource(); + var listObjectObs = client.ListObjectsAsync( + new ListObjectsArgs() + .WithBucket(bucket)); + + var listObjects = new List(); + using var _ = listObjectObs.Subscribe( + (item) => + { + listObjects.Add(item.Key); + tcs.TrySetResult(true); + }, + (ex) => tcs.TrySetException(ex), + () => tcs.TrySetResult(true)); + + await tcs.Task; + + if (listObjects.Count > 0) + { + throw new BusinessException(code: OssManagementErrorCodes.ContainerDeleteWithNotEmpty); + } + + var deleteBucketArgs = new RemoveBucketArgs() + .WithBucket(bucket); + + await client.RemoveBucketAsync(deleteBucketArgs); + } + + public async virtual Task DeleteObjectAsync(GetOssObjectRequest request) + { + if (request.Object.EndsWith('/')) + { + // Minio系统设计并不支持目录的形式 + // 如果是目录的形式,那必定有文件存在,抛出目录不为空即可 + throw new BusinessException(code: OssManagementErrorCodes.ObjectDeleteWithNotEmpty); + } + + var client = GetMinioClient(); + + var bucket = GetBucket(request.Bucket); + + var prefixPath = GetPrefixPath(); + var objectPath = GetBlobPath(prefixPath, request.Path); + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath + request.Object; + + if (await BucketExists(client, bucket) && + await ObjectExists(client, bucket, objectName)) + { + var removeObjectArgs = new RemoveObjectArgs() + .WithBucket(bucket) + .WithObject(objectName); + + await client.RemoveObjectAsync(removeObjectArgs); + } + } + + public async virtual Task ExistsAsync(string name) + { + var client = GetMinioClient(); + + var bucket = GetBucket(name); + + return await BucketExists(client, bucket); + } + + public async virtual Task ExpireAsync(ExprieOssObjectRequest request) + { + var client = GetMinioClient(); + + var bucket = GetBucket(request.Bucket); + + var bucketListResult = await client.ListBucketsAsync(); + var expiredBuckets = bucketListResult.Buckets.Take(request.Batch); + + foreach (var expiredBucket in expiredBuckets) + { + var listObjectArgs = new ListObjectsArgs() + .WithBucket(expiredBucket.Name); + + var expiredObjectItem = client.ListObjectsAsync(listObjectArgs); + + var tcs = new TaskCompletionSource(); + using var _ = expiredObjectItem.Subscribe( + onNext: (item) => + { + var lifecycleRule = new LifecycleRule + { + Status = "Enabled", + ID = item.Key, + Expiration = new Expiration(Clock.Normalize(request.ExpirationTime.DateTime)) + }; + var lifecycleConfiguration = new LifecycleConfiguration(); + lifecycleConfiguration.Rules.Add(lifecycleRule); + + var lifecycleArgs = new SetBucketLifecycleArgs() + .WithBucket(bucket) + .WithLifecycleConfiguration(lifecycleConfiguration); + + var _ = client.SetBucketLifecycleAsync(lifecycleArgs); + }, + onError: tcs.SetException, + onCompleted: () => tcs.SetResult(true) + ); + + await tcs.Task; + } + } + + public async virtual Task GetAsync(string name) + { + var client = GetMinioClient(); + + var bucket = GetBucket(name); + + var bucketListResult = await client.ListBucketsAsync(); + + var bucketInfo = bucketListResult.Buckets.FirstOrDefault((x) => x.Name == bucket); + if (bucketInfo == null) + { + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); + } + + return new OssContainer( + bucketInfo.Name, + bucketInfo.CreationDateDateTime, + 0L, + bucketInfo.CreationDateDateTime, + new Dictionary()); + } + + public async virtual Task GetListAsync(GetOssContainersRequest request) + { + var client = GetMinioClient(); + + var bucketListResult = await client.ListBucketsAsync(); + + var totalCount = bucketListResult.Buckets.Count; + + var resultObjects = bucketListResult.Buckets + .AsQueryable() + .OrderBy(x => x.Name) + .PageBy(request.Current, request.MaxKeys ?? 10) + .Select(x => new OssContainer( + x.Name, + x.CreationDateDateTime, + 0L, + null, + new Dictionary())) + .ToList(); + + return new GetOssContainersResponse( + request.Prefix, + request.Marker, + null, + totalCount, + resultObjects); + } + + public async virtual Task GetObjectAsync(GetOssObjectRequest request) + { + var client = GetMinioClient(); + + var bucket = GetBucket(request.Bucket); + + var prefixPath = GetPrefixPath(); + var objectPath = GetBlobPath(prefixPath, request.Path); + var objectName = objectPath.IsNullOrWhiteSpace() + ? request.Object + : objectPath.EnsureEndsWith('/') + request.Object; + + if (!await ObjectExists(client, bucket, objectName)) + { + throw new BusinessException(code: OssManagementErrorCodes.ObjectNotFound); + } + + var memoryStream = new MemoryStream(); + var getObjectArgs = new GetObjectArgs() + .WithBucket(bucket) + .WithObject(objectName) + .WithCallbackStream((stream) => + { + if (stream != null) + { + stream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + } + else + { + memoryStream = null; + } + }); + var getObjectResult = await client.GetObjectAsync(getObjectArgs); + + var ossObject = new OssObject( + !objectPath.IsNullOrWhiteSpace() + ? getObjectResult.ObjectName.Replace(objectPath, "") + : getObjectResult.ObjectName, + request.Path, + getObjectResult.ETag, + getObjectResult.LastModified, + memoryStream.Length, + getObjectResult.LastModified, + getObjectResult.MetaData, + getObjectResult.ObjectName.EndsWith("/")) + { + FullName = getObjectResult.ObjectName.Replace(prefixPath, "") + }; + + if (memoryStream.Length > 0) + { + ossObject.SetContent(memoryStream); + } + + if (!request.Process.IsNullOrWhiteSpace()) + { + // TODO: 文件流处理 + } + + return ossObject; + } + + public async virtual Task GetObjectsAsync(GetOssObjectsRequest request) + { + var client = GetMinioClient(); + + var bucket = GetBucket(request.BucketName); + + var prefixPath = GetPrefixPath(); + var objectPath = GetBlobPath(prefixPath, request.Prefix); + var marker = !objectPath.IsNullOrWhiteSpace() && !request.Marker.IsNullOrWhiteSpace() + ? request.Marker.Replace(objectPath, "") + : request.Marker; + + var listObjectArgs = new ListObjectsArgs() + .WithBucket(bucket) + .WithPrefix(objectPath); + + var tcs = new TaskCompletionSource(); + + var listObjectResult = client.ListObjectsAsync(listObjectArgs); + + var resultObjects = new List(); + + using var _ = listObjectResult.Subscribe( + onNext: (item) => + { + resultObjects.Add(new OssObject( + !objectPath.IsNullOrWhiteSpace() + ? item.Key.Replace(objectPath, "") + : item.Key, + request.Prefix, + item.ETag, + item.LastModifiedDateTime, + item.Size.To(), + item.LastModifiedDateTime, + new Dictionary(), + item.IsDir)); + }, + onError: (ex) => + { + tcs.TrySetException(ex); + }, + onCompleted: () => + { + tcs.SetResult(true); + } + ); + + await tcs.Task; + + var totalCount = resultObjects.Count; + resultObjects = resultObjects + .AsQueryable() + .OrderBy(x => x.Name) + .PageBy(request.Current, request.MaxKeys ?? 10) + .ToList(); + + return new GetOssObjectsResponse( + bucket, + request.Prefix, + request.Marker, + marker, + "/", + totalCount, + resultObjects); + } + + protected virtual IMinioClient GetMinioClient() + { + var configuration = GetMinioConfiguration(); + + var client = new MinioClient() + .WithEndpoint(configuration.EndPoint) + .WithCredentials(configuration.AccessKey, configuration.SecretKey); + + if (configuration.WithSSL) + { + client.WithSSL(); + } + + return client.Build(); + } + + protected async virtual Task BucketExists(IMinioClient client, string bucket) + { + var args = new BucketExistsArgs().WithBucket(bucket); + + return await client.BucketExistsAsync(args); + } + + protected async virtual Task ObjectExists(IMinioClient client, string bucket, string @object) + { + if (await client.BucketExistsAsync(new BucketExistsArgs().WithBucket(bucket))) + { + try + { + await client.StatObjectAsync(new StatObjectArgs().WithBucket(bucket).WithObject(@object)); + } + catch (Exception e) + { + if (e is ObjectNotFoundException) + { + return false; + } + + throw; + } + + return true; + } + + return false; + } + + protected virtual MinioBlobProviderConfiguration GetMinioConfiguration() + { + var configuration = ConfigurationProvider.Get(); + + return configuration.GetMinioConfiguration(); + } + + protected virtual string GetBucket(string bucket) + { + var configuration = ConfigurationProvider.Get(); + var minioConfiguration = configuration.GetMinioConfiguration(); + + return bucket.IsNullOrWhiteSpace() + ? BlobNormalizeNamingService.NormalizeContainerName(configuration, minioConfiguration.BucketName!) + : BlobNormalizeNamingService.NormalizeContainerName(configuration, bucket); + } + + protected virtual string GetPrefixPath() + { + return CurrentTenant.Id == null + ? $"host/" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/"; + } + + protected virtual string GetBlobPath(string basePath, string path) + { + var resultPath = $"{basePath}{( + path.IsNullOrWhiteSpace() ? "" : + path.Replace("./", "").RemovePreFix("/"))}"; + + return resultPath.Replace("//", ""); + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs new file mode 100644 index 000000000..90ae1358f --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/LINGYUN/Abp/OssManagement/Minio/MinioOssContainerFactory.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Logging; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.Minio; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.OssManagement.Minio; +public class MinioOssContainerFactory : IOssContainerFactory +{ + protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; } + protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } + protected IBlobContainerConfigurationProvider ConfigurationProvider { get; } + + protected IClock Clock { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ILogger Logger { get; } + + public MinioOssContainerFactory( + IClock clock, + ICurrentTenant currentTenant, + ILogger logger, + IMinioBlobNameCalculator minioBlobNameCalculator, + IBlobNormalizeNamingService blobNormalizeNamingService, + IBlobContainerConfigurationProvider configurationProvider) + { + Clock = clock; + Logger = logger; + CurrentTenant = currentTenant; + MinioBlobNameCalculator = minioBlobNameCalculator; + BlobNormalizeNamingService = blobNormalizeNamingService; + ConfigurationProvider = configurationProvider; + } + + public IOssContainer Create() + { + return new MinioOssContainer( + Clock, + CurrentTenant, + Logger, + MinioBlobNameCalculator, + BlobNormalizeNamingService, + ConfigurationProvider); + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/README.md b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/README.md new file mode 100644 index 000000000..8155334ca --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Minio/README.md @@ -0,0 +1,19 @@ +# LINGYUN.Abp.OssManagement.Minio + +Oss容器管理接口的Minio实现 + +## 配置使用 + +模块按需引用 + +相关配置项请参考 [BlobStoring Minio](https://abp.io/docs/latest/framework/infrastructure/blob-storing/minio) + +```csharp +[DependsOn(typeof(AbpOssManagementMinioModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` + + diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs index 49b845e7c..07c80c134 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs @@ -118,10 +118,12 @@ public async virtual Task DeleteAsync(string name) var nexusComponentListResult = await NexusLookupService.ListComponentAsync(nexusSearchArgs); var nexusComponent = nexusComponentListResult.Items.FirstOrDefault(); - if (nexusComponent != null) + if (nexusComponent == null) { - await NexusComponentManager.DeleteAsync(nexusComponent.Id); + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); } + + await NexusComponentManager.DeleteAsync(nexusComponent.Id); } public async virtual Task DeleteObjectAsync(GetOssObjectRequest request) diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs index fdc0cf0a2..ee329d1d7 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs @@ -151,11 +151,12 @@ public async virtual Task DeleteAsync(string name) // 阿里云oss在控制台设置即可,无需改变 var ossClient = await CreateClientAsync(); - if (BucketExists(ossClient, name)) + if (!BucketExists(ossClient, name)) { - var deleteBucketRequest = new DeleteBucketRequest(name); - ossClient.DeleteBucket(deleteBucketRequest); + throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound); } + var deleteBucketRequest = new DeleteBucketRequest(name); + ossClient.DeleteBucket(deleteBucketRequest); } public async virtual Task ExpireAsync(ExprieOssObjectRequest request) diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj index 9823acb71..98101e714 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj +++ b/aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj @@ -205,6 +205,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs index 1d86ea567..330796a07 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs @@ -386,7 +386,7 @@ private void ConfigureKestrelServer() }); } - private void ConfigureBlobStoring() + private void ConfigureBlobStoring(IConfiguration configuration) { Configure(options => { @@ -396,6 +396,11 @@ private void ConfigureBlobStoring() { fileSystem.BasePath = Path.Combine(Directory.GetCurrentDirectory(), "blobs"); }); + + //containerConfiguration.UseMinio(minio => + //{ + // configuration.GetSection("Minio").Bind(minio); + //}); }); }); } @@ -405,9 +410,9 @@ private void ConfigureBackgroundTasks() Configure(options => { options.NodeName = ApplicationName; - options.JobCleanEnabled = false; - options.JobFetchEnabled = false; - options.JobCheckEnabled = false; + options.JobCleanEnabled = true; + options.JobFetchEnabled = true; + options.JobCheckEnabled = true; }); } diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs index 2141cca65..ca7aaa23a 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs +++ b/aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs @@ -45,9 +45,6 @@ using LINGYUN.Abp.Identity.OrganizaztionUnits; using LINGYUN.Abp.Identity.Session.AspNetCore; using LINGYUN.Abp.Identity.WeChat; -using LINGYUN.Abp.IdentityServer; -using LINGYUN.Abp.IdentityServer.EntityFrameworkCore; -using LINGYUN.Abp.IdentityServer.Session; using LINGYUN.Abp.IdGenerator; using LINGYUN.Abp.IM.SignalR; using LINGYUN.Abp.Localization.CultureMap; @@ -109,7 +106,6 @@ using LINGYUN.Platform.Settings.VueVbenAdmin; using LINGYUN.Platform.Theme.VueVbenAdmin; using LY.MicroService.Applications.Single.EntityFrameworkCore; -using Microsoft.AspNetCore.Authorization; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Authentication.JwtBearer; @@ -126,7 +122,6 @@ using Volo.Abp.OpenIddict.EntityFrameworkCore; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.PermissionManagement.Identity; -using Volo.Abp.PermissionManagement.IdentityServer; using Volo.Abp.SettingManagement; using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.Threading; @@ -180,10 +175,11 @@ namespace LY.MicroService.Applications.Single; typeof(AbpOpenIddictWeChatModule), typeof(AbpOpenIddictWeChatWorkModule), + // typeof(AbpOssManagementMinioModule), // 取消注释以使用Minio + typeof(AbpOssManagementFileSystemImageSharpModule), typeof(AbpOssManagementDomainModule), typeof(AbpOssManagementApplicationModule), typeof(AbpOssManagementHttpApiModule), - typeof(AbpOssManagementFileSystemImageSharpModule), typeof(AbpOssManagementSettingManagementModule), typeof(PlatformDomainModule), @@ -351,7 +347,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) ConfigureIdempotent(); ConfigureMvcUiTheme(); ConfigureDataSeeder(); - ConfigureBlobStoring(); ConfigureLocalization(); ConfigureKestrelServer(); ConfigureBackgroundTasks(); @@ -365,6 +360,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) ConfigureAuthServer(configuration); ConfigureSwagger(context.Services); ConfigureEndpoints(context.Services); + ConfigureBlobStoring(configuration); ConfigureMultiTenancy(configuration); ConfigureJsonSerializer(configuration); ConfigureTextTemplating(configuration); diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json index 8b8cd288b..883cb8c70 100644 --- a/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json +++ b/aspnet-core/services/LY.MicroService.Applications.Single/appsettings.Development.json @@ -223,6 +223,14 @@ "Elasticsearch": { "NodeUris": "http://127.0.0.1:9200" }, + "Minio": { + "WithSSL": false, + "BucketName": "blobs", + "EndPoint": "127.0.0.1:19000", + "AccessKey": "ZD43kNpimiJf9mCuomTP", + "SecretKey": "w8IqMgi4Tnz0DGzN8jZ7IJWq7OEdbUnAU0jlZxQK", + "CreateBucketIfNotExists": false + }, "Serilog": { "MinimumLevel": { "Default": "Debug", diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN.Abp.OssManagement.Domain.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN.Abp.OssManagement.Domain.Tests.csproj new file mode 100644 index 000000000..2dfe2a391 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN.Abp.OssManagement.Domain.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestBase.cs new file mode 100644 index 000000000..ebefcecb3 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestBase.cs @@ -0,0 +1,6 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.OssManagement; +public abstract class AbpOssManagementDomainTestBase : AbpTestsBase +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestsModule.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestsModule.cs new file mode 100644 index 000000000..10ae21037 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/AbpOssManagementDomainTestsModule.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.Tests; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.OssManagement; + +[DependsOn( + typeof(AbpOssManagementDomainModule), + typeof(AbpTestsBaseModule))] +public class AbpOssManagementDomainTestsModule : AbpModule +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/OssContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/OssContainer_Tests.cs new file mode 100644 index 000000000..74a2b5bd8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Domain.Tests/LINGYUN/Abp/OssManagement/OssContainer_Tests.cs @@ -0,0 +1,248 @@ +using Shouldly; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Testing; +using Xunit; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace LINGYUN.Abp.OssManagement; +public abstract class OssContainer_Tests : AbpIntegratedTest + where TStartupModule : IAbpModule +{ + private readonly ICurrentTenant _currentTenant; + private readonly IOssContainerFactory _ossContainerFactory; + + protected OssContainer_Tests() + { + _currentTenant = GetRequiredService(); + _ossContainerFactory = GetRequiredService(); + } + + [Theory] + [InlineData("test-bucket")] + public async virtual Task Should_Creat_And_Get_Bucket(string bucket) + { + var container = _ossContainerFactory.Create(); + + var ossContainer = await container.CreateAsync(bucket); + + ossContainer.ShouldNotBeNull(); + ossContainer.Name.ShouldBe(bucket); + + await container.DeleteAsync(bucket); + } + + [Theory] + [InlineData("test-bucket")] + public async virtual Task Should_Get_Exists_Bucket(string bucket) + { + var container = _ossContainerFactory.Create(); + + await container.CreateAsync(bucket); + + var getContainer = await container.GetAsync(bucket); + + getContainer.ShouldNotBeNull(); + getContainer.Name.ShouldBe(bucket); + + await container.DeleteAsync(bucket); + } + + [Theory] + [InlineData("test-bucket")] + public async virtual Task Should_Delete_Bucket(string bucket) + { + var container = _ossContainerFactory.Create(); + + await container.CreateAsync(bucket); + + await container.DeleteAsync(bucket); + + var getNotFoundException = await Assert.ThrowsAsync(async () => + await container.GetAsync(bucket) + ); + + getNotFoundException.Code.ShouldBe(OssManagementErrorCodes.ContainerNotFound); + + var deleteNotFoundException = await Assert.ThrowsAsync(async () => + await container.DeleteAsync(bucket) + ); + + deleteNotFoundException.Code.ShouldBe(OssManagementErrorCodes.ContainerNotFound); + } + + [Theory] + [InlineData("test-bucket", "test-object-1")] + [InlineData("test-bucket", "test-folder/test-object-1")] + public async virtual Task Should_Save_And_Get_Object(string bucket, string @object) + { + var container = _ossContainerFactory.Create(); + + if (!await container.ExistsAsync(bucket)) + { + await container.CreateAsync(bucket); + } + + var testContent = "test content".GetBytes(); + using var stream = new MemoryStream(testContent); + + var createObject = await container.CreateObjectAsync( + new CreateOssObjectRequest( + bucket, + @object, + stream)); + + var getOssObject = await container.GetObjectAsync( + new GetOssObjectRequest( + bucket, + @object)); + + var result = await getOssObject.Content.GetAllBytesAsync(); + result.SequenceEqual(testContent).ShouldBeTrue(); + + await container.DeleteObjectAsync( + new GetOssObjectRequest( + bucket, + @object)); + + await container.DeleteAsync(bucket); + } + + [Theory] + [InlineData("test-bucket", "test-object-1")] + [InlineData("test-bucket", "test-folder/test-object-1")] + public async virtual Task Should_Delete_Object(string bucket, string @object) + { + var container = _ossContainerFactory.Create(); + + if (!await container.ExistsAsync(bucket)) + { + await container.CreateAsync(bucket); + } + + var testContent = "test content".GetBytes(); + using var stream = new MemoryStream(testContent); + + await container.CreateObjectAsync( + new CreateOssObjectRequest( + bucket, + @object, + stream)); + + var getOssObjectRequest = new GetOssObjectRequest( + bucket, + @object); + await container.DeleteObjectAsync(getOssObjectRequest); + + var getNotFoundException = await Assert.ThrowsAsync(async () => + await container.GetObjectAsync(getOssObjectRequest) + ); + + getNotFoundException.Code.ShouldBe(OssManagementErrorCodes.ObjectNotFound); + + await container.DeleteAsync(bucket); + } + + [Theory] + [InlineData("test-bucket")] + public async virtual Task Should_Bulk_Delete_Object(string bucket) + { + var container = _ossContainerFactory.Create(); + + if (!await container.ExistsAsync(bucket)) + { + await container.CreateAsync(bucket); + } + + string[] testObjects = ["test-object-1", "test-object-2", "test-object-3"]; + var testContent = "test content".GetBytes(); + using var stream = new MemoryStream(testContent); + + foreach (var testObject in testObjects) + { + await container.CreateObjectAsync( + new CreateOssObjectRequest( + bucket, + testObject, + stream)); + } + + await container.BulkDeleteObjectsAsync( + new BulkDeleteObjectRequest(bucket, testObjects, "/")); + + var getNotFoundException = await Assert.ThrowsAsync(async () => + await container.GetObjectAsync( + new GetOssObjectRequest( + bucket, + testObjects[0]) + ) + ); + + getNotFoundException.Code.ShouldBe(OssManagementErrorCodes.ObjectNotFound); + + await container.BulkDeleteObjectsAsync( + new BulkDeleteObjectRequest( + bucket, testObjects, "/")); + + await container.DeleteAsync(bucket); + } + + [Theory] + [InlineData("test-bucket")] + public async virtual Task Should_List_Object(string bucket) + { + var container = _ossContainerFactory.Create(); + + if (!await container.ExistsAsync(bucket)) + { + await container.CreateAsync(bucket); + } + + string[] testObjects = [ + "test-object-1", + "test-object-2", + "test-object-3", + "test-object-4", + "test-object-5"]; + var testContent = "test content".GetBytes(); + using var stream = new MemoryStream(testContent); + + foreach (var testObject in testObjects) + { + await container.CreateObjectAsync( + new CreateOssObjectRequest( + bucket, + testObject, + stream)); + } + + var result1 = await container.GetObjectsAsync( + new GetOssObjectsRequest( + bucket, + "/", + maxKeys: 1)); + + result1.Objects.Count.ShouldBe(1); + result1.Objects[0].Name.ShouldBe(testObjects[0]); + + var result2 = await container.GetObjectsAsync( + new GetOssObjectsRequest( + bucket, + "/", + current: 2, + maxKeys: 2)); + result2.Objects.Count.ShouldBe(2); + result2.Objects[1].Name.ShouldBe(testObjects[3]); + + await container.BulkDeleteObjectsAsync( + new BulkDeleteObjectRequest( + bucket, testObjects, "/")); + + await container.DeleteAsync(bucket); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN.Abp.OssManagement.Minio.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN.Abp.OssManagement.Minio.Tests.csproj new file mode 100644 index 000000000..cfc16b8db --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN.Abp.OssManagement.Minio.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestBase.cs new file mode 100644 index 000000000..5c2fb9000 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestBase.cs @@ -0,0 +1,6 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.OssManagement.Minio; +public abstract class AbpOssManagementMinioTestBase : AbpTestsBase +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestsModule.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestsModule.cs new file mode 100644 index 000000000..ae534670a --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/AbpOssManagementMinioTestsModule.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.Minio; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.OssManagement.Minio; + +[DependsOn( + typeof(AbpOssManagementMinioModule), + typeof(AbpOssManagementDomainTestsModule))] +public class AbpOssManagementMinioTestsModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configurationOptions = new AbpConfigurationBuilderOptions + { + BasePath = @"D:\Projects\Development\Abp\BlobStoring\Minio", + EnvironmentName = "Test" + }; + + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(configurationOptions)); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseMinio(minio => + { + minio.BucketName = configuration[MinioBlobProviderConfigurationNames.BucketName]; + minio.AccessKey = configuration[MinioBlobProviderConfigurationNames.AccessKey]; + minio.SecretKey = configuration[MinioBlobProviderConfigurationNames.SecretKey]; + minio.EndPoint = configuration[MinioBlobProviderConfigurationNames.EndPoint]; + }); + }); + }); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer_Tests.cs new file mode 100644 index 000000000..f5e342fbe --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Minio.Tests/LINGYUN/Abp/OssManagement/Minio/MinioOssContainer_Tests.cs @@ -0,0 +1,4 @@ +namespace LINGYUN.Abp.OssManagement.Minio; +public class MinioOssContainer_Tests : OssContainer_Tests +{ +}