diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln
index cf72d5b24..410a5913b 100644
--- a/aspnet-core/LINGYUN.MicroService.All.sln
+++ b/aspnet-core/LINGYUN.MicroService.All.sln
@@ -668,6 +668,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.WeChat.Work", "modules\wechat\LINGYUN.Abp.Identity.WeChat.Work\LINGYUN.Abp.Identity.WeChat.Work.csproj", "{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nexus", "nexus", "{87CE2F0B-0469-4C76-B325-00EA7AB94B99}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.BlobStoring.Nexus", "modules\nexus\LINGYUN.Abp.BlobStoring.Nexus\LINGYUN.Abp.BlobStoring.Nexus.csproj", "{34987F45-8234-428C-AB41-783D42295C32}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.BlobStoring.Nexus.Tests", "tests\LINGYUN.Abp.BlobStoring.Nexus.Tests\LINGYUN.Abp.BlobStoring.Nexus.Tests.csproj", "{227DA969-291B-4749-985C-7A83523B7F53}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Nexus", "modules\nexus\LINGYUN.Abp.OssManagement.Nexus\LINGYUN.Abp.OssManagement.Nexus.csproj", "{A35E90D3-5375-4BFF-B6E2-24A8CDCBF973}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Sonatype.Nexus", "modules\nexus\LINGYUN.Abp.Sonatype.Nexus\LINGYUN.Abp.Sonatype.Nexus.csproj", "{CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Nexus.Tests", "tests\LINGYUN.Abp.OssManagement.Nexus.Tests\LINGYUN.Abp.OssManagement.Nexus.Tests.csproj", "{F197F8BB-87E3-43FC-92E7-DE8E60EB22E9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1718,6 +1730,26 @@ Global
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89}.Release|Any CPU.Build.0 = Release|Any CPU
+ {34987F45-8234-428C-AB41-783D42295C32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {34987F45-8234-428C-AB41-783D42295C32}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {34987F45-8234-428C-AB41-783D42295C32}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {34987F45-8234-428C-AB41-783D42295C32}.Release|Any CPU.Build.0 = Release|Any CPU
+ {227DA969-291B-4749-985C-7A83523B7F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {227DA969-291B-4749-985C-7A83523B7F53}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {227DA969-291B-4749-985C-7A83523B7F53}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {227DA969-291B-4749-985C-7A83523B7F53}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A35E90D3-5375-4BFF-B6E2-24A8CDCBF973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A35E90D3-5375-4BFF-B6E2-24A8CDCBF973}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A35E90D3-5375-4BFF-B6E2-24A8CDCBF973}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A35E90D3-5375-4BFF-B6E2-24A8CDCBF973}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F197F8BB-87E3-43FC-92E7-DE8E60EB22E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F197F8BB-87E3-43FC-92E7-DE8E60EB22E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F197F8BB-87E3-43FC-92E7-DE8E60EB22E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F197F8BB-87E3-43FC-92E7-DE8E60EB22E9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2041,6 +2073,12 @@ Global
{2C86306D-D626-41F8-BA3C-5C9B4123CE7D} = {83E698F6-F8CD-4604-AB80-01A203389501}
{2DC43D15-F20F-44EC-B3A3-47BD8BBB50CA} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
{3E32DBDA-1C63-42B4-85D1-E84BBD072D89} = {DD9BE9E7-F6BF-4869-BCD2-82F5072BDA21}
+ {87CE2F0B-0469-4C76-B325-00EA7AB94B99} = {C5CAD011-DF84-4914-939C-0C029DCEF26F}
+ {34987F45-8234-428C-AB41-783D42295C32} = {87CE2F0B-0469-4C76-B325-00EA7AB94B99}
+ {227DA969-291B-4749-985C-7A83523B7F53} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
+ {A35E90D3-5375-4BFF-B6E2-24A8CDCBF973} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6}
+ {CDE35EAE-4B48-40D2-BF57-68EFC5B5A97C} = {87CE2F0B-0469-4C76-B325-00EA7AB94B99}
+ {F197F8BB-87E3-43FC-92E7-DE8E60EB22E9} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/FodyWeavers.xml b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/FodyWeavers.xsd b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/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/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN.Abp.BlobStoring.Nexus.csproj b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN.Abp.BlobStoring.Nexus.csproj
new file mode 100644
index 000000000..65dc76e53
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN.Abp.BlobStoring.Nexus.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ netstandard2.0
+
+ Oss对象存储Nexus集成
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusModule.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusModule.cs
new file mode 100644
index 000000000..f1f868445
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusModule.cs
@@ -0,0 +1,12 @@
+using LINGYUN.Abp.Sonatype.Nexus;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+
+[DependsOn(
+ typeof(AbpBlobStoringModule),
+ typeof(AbpSonatypeNexusModule))]
+public class AbpBlobStoringNexusModule : AbpModule
+{
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/DefaultBlobRawPathCalculator.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/DefaultBlobRawPathCalculator.cs
new file mode 100644
index 000000000..872931bd1
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/DefaultBlobRawPathCalculator.cs
@@ -0,0 +1,70 @@
+using System;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public class DefaultBlobRawPathCalculator : IBlobRawPathCalculator, ITransientDependency
+{
+ protected ICurrentTenant CurrentTenant { get; }
+ protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
+
+ public DefaultBlobRawPathCalculator(
+ ICurrentTenant currentTenant,
+ IBlobContainerConfigurationProvider configurationProvider)
+ {
+ CurrentTenant = currentTenant;
+ ConfigurationProvider = configurationProvider;
+ }
+
+ public string CalculateGroup(string containerName, string blobName)
+ {
+ var blobPath = CalculateBasePath(containerName);
+
+ var lastFolderIndex = blobName.LastIndexOf("/");
+ if (lastFolderIndex > 0)
+ {
+ blobPath = blobPath.EnsureEndsWith('/');
+ blobPath += blobName.Substring(0, lastFolderIndex);
+ }
+
+ return blobPath.EnsureStartsWith('/').RemovePostFix("/");
+ }
+
+ public string CalculateName(string containerName, string blobName, bool replacePath = false)
+ {
+ var blobPath = CalculateBasePath(containerName);
+ blobPath = blobPath.EnsureEndsWith('/');
+ blobPath += blobName;
+
+ if (replacePath)
+ {
+ return blobName.Replace(blobPath.RemovePreFix("/"), "").RemovePreFix("/");
+ }
+
+ return blobPath.RemovePreFix("/");
+ }
+
+ protected virtual string CalculateBasePath(string containerName)
+ {
+ var configuration = ConfigurationProvider.Get();
+ var nexusConfiguration = configuration.GetNexusConfiguration();
+ var blobPath = nexusConfiguration.BasePath;
+
+ if (CurrentTenant.Id == null)
+ {
+ blobPath = $"{blobPath}/host";
+ }
+ else
+ {
+ blobPath = $"{blobPath}/tenants/{CurrentTenant.Id.Value.ToString("D")}";
+ }
+
+ if (nexusConfiguration.AppendContainerNameToBasePath)
+ {
+ blobPath = $"{blobPath}/{containerName.RemovePreFix("/")}";
+ }
+
+ return blobPath.EnsureStartsWith('/');
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/IBlobRawPathCalculator.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/IBlobRawPathCalculator.cs
new file mode 100644
index 000000000..9ce0ba27e
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/IBlobRawPathCalculator.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public interface IBlobRawPathCalculator
+{
+ string CalculateGroup(string containerName, string blobName);
+
+ string CalculateName(string containerName, string blobName, bool replacePath = false);
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainerConfigurationExtensions.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainerConfigurationExtensions.cs
new file mode 100644
index 000000000..40670bc0b
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainerConfigurationExtensions.cs
@@ -0,0 +1,26 @@
+using System;
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus
+{
+ public static class NexusBlobContainerConfigurationExtensions
+ {
+ public static NexusBlobProviderConfiguration GetNexusConfiguration(
+ this BlobContainerConfiguration containerConfiguration)
+ {
+ return new NexusBlobProviderConfiguration(containerConfiguration);
+ }
+
+ public static BlobContainerConfiguration UseNexus(
+ this BlobContainerConfiguration containerConfiguration,
+ Action nexusConfigureAction)
+ {
+ containerConfiguration.ProviderType = typeof(NexusBlobProvider);
+ containerConfiguration.NamingNormalizers.TryAdd();
+
+ nexusConfigureAction(new NexusBlobProviderConfiguration(containerConfiguration));
+
+ return containerConfiguration;
+ }
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobNamingNormalizer.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobNamingNormalizer.cs
new file mode 100644
index 000000000..8c2f91131
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobNamingNormalizer.cs
@@ -0,0 +1,21 @@
+using Volo.Abp.BlobStoring;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public class NexusBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency
+{
+ public virtual string NormalizeContainerName(string containerName)
+ {
+ return Normalize(containerName);
+ }
+
+ public virtual string NormalizeBlobName(string blobName)
+ {
+ return Normalize(blobName);
+ }
+
+ protected virtual string Normalize(string fileName)
+ {
+ return fileName.Replace("\\", "/").Replace("//", "/");
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProvider.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProvider.cs
new file mode 100644
index 000000000..3f40842a0
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProvider.cs
@@ -0,0 +1,114 @@
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Components;
+using LINGYUN.Abp.Sonatype.Nexus.Search;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public class NexusBlobProvider : BlobProviderBase, ITransientDependency
+{
+ protected INexusAssetManager NexusAssetManager { get; }
+ protected INexusComponentManager NexusComponentManager { get; }
+ protected INexusLookupService NexusLookupService { get; }
+ protected IBlobRawPathCalculator BlobDirectoryCalculator { get; }
+
+ public NexusBlobProvider(
+ INexusAssetManager nexusAssetManager,
+ INexusComponentManager nexusComponentManager,
+ INexusLookupService nexusLookupService,
+ IBlobRawPathCalculator blobDirectoryCalculator)
+ {
+ NexusAssetManager = nexusAssetManager;
+ NexusComponentManager = nexusComponentManager;
+ NexusLookupService = nexusLookupService;
+ BlobDirectoryCalculator = blobDirectoryCalculator;
+ }
+
+ public async override Task DeleteAsync(BlobProviderDeleteArgs args)
+ {
+ var nexusComponent = await GetNexusomponentOrNull(args);
+ if (nexusComponent == null)
+ {
+ return false;
+ }
+ return await NexusComponentManager.DeleteAsync(nexusComponent.Id, args.CancellationToken);
+ }
+
+ public async override Task ExistsAsync(BlobProviderExistsArgs args)
+ {
+ var nexusAsset = await GetNexusAssetOrNull(args);
+ return nexusAsset != null;
+ }
+
+ public async override Task GetOrNullAsync(BlobProviderGetArgs args)
+ {
+ var nexusAsset = await GetNexusAssetOrNull(args);
+ if (nexusAsset == null)
+ {
+ return null;
+ }
+
+ return await NexusAssetManager.GetContentOrNullAsync(nexusAsset);
+ }
+
+ public async override Task SaveAsync(BlobProviderSaveArgs args)
+ {
+ var nexusAsset = await GetNexusAssetOrNull(args);
+ if (!args.OverrideExisting && nexusAsset != null)
+ {
+ throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
+ }
+
+ var fileBytes = await args.BlobStream.GetAllBytesAsync();
+ var blobPath = BlobDirectoryCalculator.CalculateGroup(args.ContainerName, args.BlobName);
+ var blobName = BlobDirectoryCalculator.CalculateName(args.ContainerName, args.BlobName, true);
+ // blobName = blobName.Replace(blobPath.RemovePreFix("/"), "").RemovePreFix("/");
+ var asset1 = new Asset(blobName, fileBytes);
+
+ var nexusConfiguration = args.Configuration.GetNexusConfiguration();
+ var repository = nexusConfiguration.Repository;
+
+ var nexusRawBlobUploadArgs = new NexusRawBlobUploadArgs(
+ repository,
+ blobPath,
+ asset1);
+
+ await NexusComponentManager.UploadAsync(nexusRawBlobUploadArgs, args.CancellationToken);
+ }
+
+ protected async virtual Task GetNexusAssetOrNull(BlobProviderArgs args)
+ {
+ var nexusConfiguration = args.Configuration.GetNexusConfiguration();
+ var blobPath = BlobDirectoryCalculator.CalculateGroup(args.ContainerName, args.BlobName);
+ var blobName = BlobDirectoryCalculator.CalculateName(args.ContainerName, args.BlobName);
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ blobName);
+
+ var nexusAssetListResult = await NexusLookupService.ListAssetAsync(nexusSearchArgs, args.CancellationToken);
+ var nexusAsset = nexusAssetListResult.Items.FirstOrDefault();
+
+ return nexusAsset;
+ }
+
+ protected async virtual Task GetNexusomponentOrNull(BlobProviderArgs args)
+ {
+ var nexusConfiguration = args.Configuration.GetNexusConfiguration();
+ var blobPath = BlobDirectoryCalculator.CalculateGroup(args.ContainerName, args.BlobName);
+ var blobName = BlobDirectoryCalculator.CalculateName(args.ContainerName, args.BlobName);
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ blobName);
+
+ var nexusComponentResult = await NexusLookupService.ListComponentAsync(nexusSearchArgs, args.CancellationToken);
+ var nexusComponent = nexusComponentResult.Items.FirstOrDefault();
+
+ return nexusComponent;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfiguration.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfiguration.cs
new file mode 100644
index 000000000..e1f298190
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfiguration.cs
@@ -0,0 +1,28 @@
+using Volo.Abp;
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public class NexusBlobProviderConfiguration
+{
+ public string BasePath {
+ get => _containerConfiguration.GetConfiguration(NexusBlobProviderConfigurationNames.BasePath);
+ set => _containerConfiguration.SetConfiguration(NexusBlobProviderConfigurationNames.BasePath, value);
+ }
+
+ public bool AppendContainerNameToBasePath {
+ get => _containerConfiguration.GetConfigurationOrDefault(NexusBlobProviderConfigurationNames.AppendContainerNameToBasePath, true);
+ set => _containerConfiguration.SetConfiguration(NexusBlobProviderConfigurationNames.AppendContainerNameToBasePath, value);
+ }
+
+ public string Repository {
+ get => _containerConfiguration.GetConfiguration(NexusBlobProviderConfigurationNames.Repository);
+ set => _containerConfiguration.SetConfiguration(NexusBlobProviderConfigurationNames.Repository, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+
+ private readonly BlobContainerConfiguration _containerConfiguration;
+
+ public NexusBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
+ {
+ _containerConfiguration = containerConfiguration;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfigurationNames.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfigurationNames.cs
new file mode 100644
index 000000000..641d5580c
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.BlobStoring.Nexus/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobProviderConfigurationNames.cs
@@ -0,0 +1,16 @@
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public static class NexusBlobProviderConfigurationNames
+{
+ ///
+ /// 基础路径
+ ///
+ public const string BasePath = "Sonatype:Nexus:Raw:BasePath";
+ ///
+ /// 添加容器名称到基础路径
+ ///
+ public const string AppendContainerNameToBasePath = "Sonatype:Nexus:Raw:AppendContainerNameToBasePath";
+ ///
+ /// Nexus raw仓库名称
+ ///
+ public const string Repository = "Sonatype:Nexus:Raw:Repository";
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/FodyWeavers.xml b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/FodyWeavers.xml
new file mode 100644
index 000000000..00e1d9a1c
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/FodyWeavers.xsd b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/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/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN.Abp.OssManagement.Nexus.csproj b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN.Abp.OssManagement.Nexus.csproj
new file mode 100644
index 000000000..c4aa8db10
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN.Abp.OssManagement.Nexus.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs
new file mode 100644
index 000000000..90584bfd2
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusModule.cs
@@ -0,0 +1,23 @@
+using LINGYUN.Abp.BlobStoring.Nexus;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+
+[DependsOn(
+ typeof(AbpBlobStoringNexusModule),
+ typeof(AbpOssManagementDomainModule))]
+public class AbpOssManagementNexusModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddTransient();
+
+ context.Services.AddTransient(provider =>
+ provider
+ .GetRequiredService()
+ .Create()
+ .As());
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs
new file mode 100644
index 000000000..49b845e7c
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainer.cs
@@ -0,0 +1,333 @@
+using LINGYUN.Abp.BlobStoring.Nexus;
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Components;
+using LINGYUN.Abp.Sonatype.Nexus.Search;
+using LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+using LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+
+internal class NexusOssContainer : IOssContainer, IOssObjectExpireor
+{
+ protected ICoreUiServiceProxy CoreUiServiceProxy { get; }
+ protected INexusAssetManager NexusAssetManager { get; }
+ protected INexusComponentManager NexusComponentManager { get; }
+ protected INexusLookupService NexusLookupService { get; }
+ protected ICurrentTenant CurrentTenant { get; }
+ protected IBlobRawPathCalculator BlobRawPathCalculator { get; }
+ protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
+
+ public NexusOssContainer(
+ ICoreUiServiceProxy coreUiServiceProxy,
+ INexusAssetManager nexusAssetManager,
+ INexusComponentManager nexusComponentManager,
+ INexusLookupService nexusLookupService,
+ ICurrentTenant currentTenant,
+ IBlobRawPathCalculator blobRawPathCalculator,
+ IBlobContainerConfigurationProvider configurationProvider)
+ {
+ CoreUiServiceProxy = coreUiServiceProxy;
+ NexusAssetManager = nexusAssetManager;
+ NexusComponentManager = nexusComponentManager;
+ NexusLookupService = nexusLookupService;
+ CurrentTenant = currentTenant;
+ BlobRawPathCalculator = blobRawPathCalculator;
+ ConfigurationProvider = configurationProvider;
+ }
+
+ public Task BulkDeleteObjectsAsync(BulkDeleteObjectRequest request)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async virtual Task CreateAsync(string name)
+ {
+ // 创建容器的逻辑就是创建一个包含一个空白assets的component
+
+ var blobPath = BlobRawPathCalculator.CalculateGroup(name, "readme");
+
+ var nexusConfiguration = GetNexusConfiguration();
+
+ var uploadArgs = new NexusRawBlobUploadArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ new Asset("readme", Encoding.UTF8.GetBytes("A placeholder for an empty container.")));
+
+ await NexusComponentManager.UploadAsync(uploadArgs);
+
+ return new OssContainer(
+ name,
+ DateTime.Now,
+ 0L,
+ DateTime.Now);
+ }
+
+ public async virtual Task CreateObjectAsync(CreateOssObjectRequest request)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = GetBasePath(request.Bucket, request.Path, request.Object);
+ if (!request.Overwrite)
+ {
+ var searchBlobName = GetObjectName(request.Bucket, request.Path, request.Object);
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ searchBlobName);
+
+ var nexusAssetListResult = await NexusLookupService.ListAssetAsync(nexusSearchArgs);
+ if (nexusAssetListResult.Items.Any())
+ {
+ throw new BusinessException(code: OssManagementErrorCodes.ObjectAlreadyExists);
+ }
+ }
+
+ var blobName = GetObjectName(request.Bucket, request.Path, request.Object, true);
+ var blobBytes = await request.Content.GetAllBytesAsync();
+ var uploadArgs = new NexusRawBlobUploadArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ new Asset(blobName, blobBytes));
+
+ await NexusComponentManager.UploadAsync(uploadArgs);
+
+ var getOssObjectRequest = new GetOssObjectRequest(
+ request.Bucket, request.Object, request.Path);
+
+ return await GetObjectAsync(getOssObjectRequest);
+ }
+
+ public async virtual Task DeleteAsync(string name)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = BlobRawPathCalculator.CalculateGroup(name, "/");
+
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ "/");
+
+ var nexusComponentListResult = await NexusLookupService.ListComponentAsync(nexusSearchArgs);
+ var nexusComponent = nexusComponentListResult.Items.FirstOrDefault();
+ if (nexusComponent != null)
+ {
+ await NexusComponentManager.DeleteAsync(nexusComponent.Id);
+ }
+ }
+
+ public async virtual Task DeleteObjectAsync(GetOssObjectRequest request)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = GetBasePath(request.Bucket, request.Path, request.Object);
+ var blobName = GetObjectName(request.Bucket, request.Path, request.Object);
+
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ blobName);
+
+ var nexusAssetListResult = await NexusLookupService.ListAssetAsync(nexusSearchArgs);
+ var nexusAsset = nexusAssetListResult.Items.FirstOrDefault();
+ if (nexusAsset != null)
+ {
+ await NexusAssetManager.DeleteAsync(nexusAsset.Id);
+ }
+ }
+
+ public virtual Task ExistsAsync(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ExpireAsync(ExprieOssObjectRequest request)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async virtual Task GetAsync(string name)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = BlobRawPathCalculator.CalculateGroup(name, "/");
+
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ "/");
+
+ var nexusComponentListResult = await NexusLookupService.ListComponentAsync(nexusSearchArgs);
+ var nexusComponent = nexusComponentListResult.Items.FirstOrDefault();
+ if (nexusComponent == null)
+ {
+ throw new BusinessException(code: OssManagementErrorCodes.ContainerNotFound);
+ }
+
+ var lastModified = nexusComponent.Assets
+ .OrderBy(asset => asset.LastModified)
+ .Select(asset => asset.LastModified)
+ .FirstOrDefault();
+
+ return new OssContainer(
+ nexusComponent.Name,
+ lastModified ?? new DateTime(),
+ 0L,
+ lastModified);
+ }
+
+ public async virtual Task GetListAsync(GetOssContainersRequest request)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = request.Prefix.RemovePreFix(".").RemovePreFix("/");
+ var readComponent = CoreUIBrowse.Read(nexusConfiguration.Repository, blobPath);
+
+ var coreUIResponse = await CoreUiServiceProxy.SearchAsync(readComponent);
+ var ifFolderComponents = coreUIResponse.Result.Data.Where(component => component.Type == "folder").ToArray();
+
+ return new GetOssContainersResponse(
+ request.Prefix,
+ request.Marker,
+ "",
+ ifFolderComponents.Length,
+ ifFolderComponents.Select(component =>
+ new OssContainer(
+ component.Text,
+ new DateTime(),
+ 0L,
+ null,
+ new Dictionary()))
+ .ToList());
+ }
+
+ public async virtual Task GetObjectAsync(GetOssObjectRequest request)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = GetBasePath(request.Bucket, request.Path, request.Object);
+ var blobFullName = GetObjectName(request.Bucket, request.Path, request.Object);
+ var blobName = GetObjectName(request.Bucket, request.Path, request.Object, true);
+
+ var nexusSearchArgs = new NexusSearchArgs(
+ nexusConfiguration.Repository,
+ blobPath,
+ blobFullName);
+
+ var nexusAssetListResult = await NexusLookupService.ListAssetAsync(nexusSearchArgs);
+ var nexusAssetItem = nexusAssetListResult.Items.FirstOrDefault();
+ if (nexusAssetItem == null)
+ {
+ throw new BusinessException(code: OssManagementErrorCodes.ObjectNotFound);
+ }
+
+ var (Repository, ComponentId) = DecodeBase64Id(nexusAssetItem.Id);
+
+ var readAsset = CoreUIAsset.Read(ComponentId, Repository);
+
+ var coreUIResponse = await CoreUiServiceProxy.SearchAsync(readAsset);
+ var checksum = coreUIResponse.Result.Data.Attributes.GetOrDefault("checksum");
+ var metadata = new Dictionary();
+ if (checksum != null)
+ {
+ foreach (var data in checksum)
+ {
+ metadata.Add(data.Key, data.Value.ToString());
+ }
+ }
+
+ return new OssObject(
+ blobName,
+ blobPath,
+ checksum?.GetOrDefault("md5")?.ToString(),
+ coreUIResponse.Result.Data.BlobCreated,
+ coreUIResponse.Result.Data.Size,
+ coreUIResponse.Result.Data.BlobUpdated,
+ metadata
+ );
+ }
+
+ public async virtual Task GetObjectsAsync(GetOssObjectsRequest request)
+ {
+ var nexusConfiguration = GetNexusConfiguration();
+ var blobPath = GetBasePath(request.BucketName, request.Prefix, "");
+ var readComponent = CoreUIBrowse.Read(nexusConfiguration.Repository, blobPath.RemovePreFix("/"));
+
+ var coreUIResponse = await CoreUiServiceProxy.SearchAsync(readComponent);
+ var filterComponents = coreUIResponse.Result.Data
+ .WhereIf(string.Equals(request.Delimiter, "/"), component => component.Type == "folder")
+ .OrderBy(component => component.Text)
+ .AsQueryable()
+ .PageBy(request.Current, request.MaxKeys ?? 10)
+ .ToArray();
+
+ var response = new GetOssObjectsResponse(
+ request.BucketName,
+ request.Prefix,
+ request.Marker,
+ "",
+ "/", // 文件系统目录分隔符
+ coreUIResponse.Result.Data.Count,
+ filterComponents.Select(component => new OssObject(
+ component.Text,
+ request.Prefix,
+ "",
+ null,
+ 0L,
+ null,
+ new Dictionary(),
+ component.Type == "folder")
+ {
+ FullName = component.Id
+ })
+ .ToList());
+
+ return response;
+ }
+
+ protected virtual NexusBlobProviderConfiguration GetNexusConfiguration()
+ {
+ var configuration = ConfigurationProvider.Get();
+ var nexusConfiguration = configuration.GetNexusConfiguration();
+ return nexusConfiguration;
+ }
+
+ protected virtual string GetBasePath(string bucket, string path, string @object)
+ {
+ var objectPath = bucket.EnsureEndsWith('/') + (!path.IsNullOrWhiteSpace() ? path.RemovePreFix("/") : "");
+ objectPath = BlobRawPathCalculator.CalculateGroup(objectPath, @object);
+ return objectPath;
+ }
+
+ protected virtual string GetObjectName(string bucket, string path, string @object, bool replaceObjectPath = false)
+ {
+ var objectPath = bucket.EnsureEndsWith('/') + (!path.IsNullOrWhiteSpace() ? path.RemovePreFix("/") : "");
+ //objectPath = BlobRawPathCalculator.CalculateGroup(objectPath, @object);
+ var objectName = BlobRawPathCalculator.CalculateName(objectPath, @object, replaceObjectPath);
+ return objectName;
+ }
+
+ protected virtual (string Repository, string ComponentId) DecodeBase64Id(string base64id)
+ {
+ base64id = base64id.Replace("-", "+").Replace("_", "/");
+ var base64 = Encoding.ASCII.GetBytes(base64id);
+ var padding = base64.Length * 3 % 4;//(base64.Length*6 % 8)/2
+ if (padding != 0)
+ {
+ base64id = base64id.PadRight(base64id.Length + padding, '=');
+ }
+
+ var buffer = Convert.FromBase64String(base64id);
+ var decoded = Encoding.UTF8.GetString(buffer);
+ var parts = decoded.Split(":");
+ if (parts.Length != 2)
+ {
+ throw new AbpException("Unable to parse component id " + decoded);
+ }
+ return (parts[0], parts[1]);
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactory.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactory.cs
new file mode 100644
index 000000000..de29b88a5
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactory.cs
@@ -0,0 +1,49 @@
+using LINGYUN.Abp.BlobStoring.Nexus;
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Components;
+using LINGYUN.Abp.Sonatype.Nexus.Search;
+using LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+internal class NexusOssContainerFactory : IOssContainerFactory
+{
+ protected ICoreUiServiceProxy CoreUiServiceProxy { get; }
+ protected INexusAssetManager NexusAssetManager { get; }
+ protected INexusComponentManager NexusComponentManager { get; }
+ protected INexusLookupService NexusLookupService { get; }
+ protected ICurrentTenant CurrentTenant { get; }
+ protected IBlobRawPathCalculator BlobRawPathCalculator { get; }
+ protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
+
+ public NexusOssContainerFactory(
+ ICoreUiServiceProxy coreUiServiceProxy,
+ INexusAssetManager nexusAssetManager,
+ INexusComponentManager nexusComponentManager,
+ INexusLookupService nexusLookupService,
+ ICurrentTenant currentTenant,
+ IBlobRawPathCalculator blobRawPathCalculator,
+ IBlobContainerConfigurationProvider configurationProvider)
+ {
+ CoreUiServiceProxy = coreUiServiceProxy;
+ NexusAssetManager = nexusAssetManager;
+ NexusComponentManager = nexusComponentManager;
+ NexusLookupService = nexusLookupService;
+ CurrentTenant = currentTenant;
+ BlobRawPathCalculator = blobRawPathCalculator;
+ ConfigurationProvider = configurationProvider;
+ }
+
+ public IOssContainer Create()
+ {
+ return new NexusOssContainer(
+ CoreUiServiceProxy,
+ NexusAssetManager,
+ NexusComponentManager,
+ NexusLookupService,
+ CurrentTenant,
+ BlobRawPathCalculator,
+ ConfigurationProvider);
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/System/IO/SystemExtensions.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/System/IO/SystemExtensions.cs
new file mode 100644
index 000000000..5a28f21d1
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.OssManagement.Nexus/System/IO/SystemExtensions.cs
@@ -0,0 +1,25 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace System.IO
+{
+ internal static class SystemExtensions
+ {
+ public static string MD5(this Stream stream)
+ {
+ if (stream.CanSeek)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ }
+ using MD5 md5 = new MD5CryptoServiceProvider();
+ byte[] retVal = md5.ComputeHash(stream);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < retVal.Length; i++)
+ {
+ sb.Append(retVal[i].ToString("x2"));
+ }
+ stream.Seek(0, SeekOrigin.Begin);
+ return sb.ToString();
+ }
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/FodyWeavers.xml b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/FodyWeavers.xsd b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/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/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN.Abp.Sonatype.Nexus.csproj b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN.Abp.Sonatype.Nexus.csproj
new file mode 100644
index 000000000..e0e5d8f12
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN.Abp.Sonatype.Nexus.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusModule.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusModule.cs
new file mode 100644
index 000000000..05b1899c4
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusModule.cs
@@ -0,0 +1,28 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using System;
+using Volo.Abp.Json;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.Sonatype.Nexus;
+
+[DependsOn(
+ typeof(AbpJsonModule))]
+public class AbpSonatypeNexusModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var configuration = context.Services.GetConfiguration();
+ Configure(configuration.GetSection("Sonatype:Nexus"));
+
+ context.Services.AddHttpClient(
+ SonatypeNexusConsts.ApiClient,
+ (serviceProvider, client) =>
+ {
+ var options = serviceProvider.GetRequiredService>().Value;
+ client.BaseAddress = new Uri(options.BaseUrl);
+ client.DefaultRequestHeaders.Add("X-Nexus-Ui", "true");
+ client.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
+ });
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusOptions.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusOptions.cs
new file mode 100644
index 000000000..f07f72ec7
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/AbpSonatypeNexusOptions.cs
@@ -0,0 +1,13 @@
+namespace LINGYUN.Abp.Sonatype.Nexus;
+public class AbpSonatypeNexusOptions
+{
+ public string BaseUrl { get; set; }
+ public string UserName { get; set; }
+ public string Password { get; set; }
+ public AbpSonatypeNexusOptions()
+ {
+ BaseUrl = "http://127.0.0.1:8081";
+ UserName = "sonatype";
+ Password = "sonatype";
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/INexusAssetManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/INexusAssetManager.cs
new file mode 100644
index 000000000..f3ea41370
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/INexusAssetManager.cs
@@ -0,0 +1,26 @@
+using JetBrains.Annotations;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Assets;
+
+public interface INexusAssetManager
+{
+ Task ListAsync(
+ [NotNull] string repository,
+ string continuationToken = null,
+ CancellationToken cancellationToken = default);
+
+ Task GetAsync(
+ [NotNull] string id,
+ CancellationToken cancellationToken = default);
+
+ Task DeleteAsync(
+ [NotNull] string id,
+ CancellationToken cancellationToken = default);
+
+ Task GetContentOrNullAsync(
+ [NotNull] NexusAsset asset,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAsset.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAsset.cs
new file mode 100644
index 000000000..230894162
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAsset.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Assets;
+public class NexusAsset
+{
+ [JsonPropertyName("downloadUrl")]
+ public string DownloadUrl { get; set; }
+
+ [JsonPropertyName("path")]
+ public string Path { get; set; }
+
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("repository")]
+ public string Repository { get; set; }
+
+ [JsonPropertyName("format")]
+ public string Format { get; set; }
+
+ [JsonPropertyName("contentType")]
+ public string ContentType { get; set; }
+
+ [JsonPropertyName("lastModified")]
+ public DateTime? LastModified { get; set; }
+
+ [JsonPropertyName("blobCreated")]
+ public DateTime? BlobCreated { get; set; }
+
+ [JsonPropertyName("checksum")]
+ public Dictionary Checksum { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetListResult.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetListResult.cs
new file mode 100644
index 000000000..0632471b6
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetListResult.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Assets;
+
+[Serializable]
+public class NexusAssetListResult
+{
+ [JsonPropertyName("continuationToken")]
+ public string ContinuationToken { get; set; }
+
+ [JsonPropertyName("items")]
+ public List Items { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetManager.cs
new file mode 100644
index 000000000..dc6fe331c
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Assets/NexusAssetManager.cs
@@ -0,0 +1,98 @@
+using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Assets;
+public class NexusAssetManager : INexusAssetManager, ISingletonDependency
+{
+ protected IJsonSerializer JsonSerializer { get; }
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected AbpSonatypeNexusOptions Options { get; }
+
+ public NexusAssetManager(
+ IJsonSerializer jsonSerializer,
+ IHttpClientFactory httpClientFactory,
+ IOptions options)
+ {
+ Options = options.Value;
+ JsonSerializer = jsonSerializer;
+ HttpClientFactory = httpClientFactory;
+ }
+
+ public async virtual Task DeleteAsync([NotNull] string id, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+ var username = Options.UserName;
+ var password = Options.Password;
+ var authBase64Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
+
+ var requestMessage = new HttpRequestMessage(HttpMethod.Delete, $"/service/rest/v1/assets/{id}");
+ requestMessage.Headers.Add("Authorization", $"Basic {authBase64Data}");
+
+ var response = await client.SendAsync(requestMessage, cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+ }
+
+ public async virtual Task GetAsync([NotNull] string id, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var response = await client.GetAsync($"/service/rest/v1/assets/{id}", cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusAsset = JsonSerializer.Deserialize(responseContent);
+
+ return nexusAsset;
+ }
+
+ public async virtual Task GetContentOrNullAsync([NotNull] NexusAsset asset, CancellationToken cancellationToken = default)
+ {
+ if (asset == null || asset.DownloadUrl.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ return await client.GetStreamAsync(asset.DownloadUrl);
+ }
+
+ public async virtual Task ListAsync([NotNull] string repository, string continuationToken = null, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/service/rest/v1/assets");
+ urlBuilder.AppendFormat("?repository={0}", repository);
+ if (!continuationToken.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&continuationToken={0}", continuationToken);
+ }
+
+ var response = await client.GetAsync(urlBuilder.ToString(), cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusAssetListResult = JsonSerializer.Deserialize(responseContent);
+
+ return nexusAssetListResult;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/Asset.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/Asset.cs
new file mode 100644
index 000000000..417013b20
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/Asset.cs
@@ -0,0 +1,11 @@
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+public class Asset
+{
+ public string FileName { get; }
+ public byte[] FileBytes { get; }
+ public Asset(string fileName, byte[] fileBytes)
+ {
+ FileName = fileName;
+ FileBytes = fileBytes;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/INexusComponentManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/INexusComponentManager.cs
new file mode 100644
index 000000000..1802676ad
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/INexusComponentManager.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+
+public interface INexusComponentManager
+{
+ Task ListAsync(
+ [NotNull] string repository,
+ string continuationToken = null,
+ CancellationToken cancellationToken = default);
+
+ Task GetAsync(
+ [NotNull] string id,
+ CancellationToken cancellationToken = default);
+
+ Task DeleteAsync(
+ [NotNull] string id,
+ CancellationToken cancellationToken = default);
+
+ Task UploadAsync(
+ [NotNull] NexusComponentUploadArgs args,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponent.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponent.cs
new file mode 100644
index 000000000..af0c54213
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponent.cs
@@ -0,0 +1,36 @@
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+
+[Serializable]
+public class NexusComponent
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("repository")]
+ public string Repository { get; set; }
+
+ [JsonPropertyName("format")]
+ public string Format { get; set; }
+
+ [JsonPropertyName("group")]
+ public string Group { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("version")]
+ public string Version { get; set; }
+
+ [JsonPropertyName("assets")]
+ public List Assets { get; set; }
+
+ public NexusComponent()
+ {
+ Assets = new List();
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentListResult.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentListResult.cs
new file mode 100644
index 000000000..4186d6107
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentListResult.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+
+[Serializable]
+public class NexusComponentListResult
+{
+ [JsonPropertyName("continuationToken")]
+ public string ContinuationToken { get; set; }
+
+ [JsonPropertyName("items")]
+ public List Items { get; set; }
+
+ public NexusComponentListResult()
+ {
+ Items = new List();
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentManager.cs
new file mode 100644
index 000000000..3a91593be
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentManager.cs
@@ -0,0 +1,107 @@
+using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+public class NexusComponentManager : INexusComponentManager, ISingletonDependency
+{
+ protected IJsonSerializer JsonSerializer { get; }
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected AbpSonatypeNexusOptions Options { get; }
+
+ public NexusComponentManager(
+ IJsonSerializer jsonSerializer,
+ IHttpClientFactory httpClientFactory,
+ IOptions options)
+ {
+ Options = options.Value;
+ JsonSerializer = jsonSerializer;
+ HttpClientFactory = httpClientFactory;
+ }
+
+ public async virtual Task UploadAsync([NotNull] NexusComponentUploadArgs args, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var username = Options.UserName;
+ var password = Options.Password;
+ var authBase64Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
+
+ using var formDataContent = args.BuildContent();
+ var requestMessage = new HttpRequestMessage(
+ HttpMethod.Post,
+ $"/service/rest/v1/components?repository={args.Repository}")
+ {
+ Content = formDataContent,
+ };
+ requestMessage.Headers.Add("Authorization", $"Basic {authBase64Data}");
+
+ var response = await client.SendAsync(requestMessage, cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+ }
+
+ public async virtual Task DeleteAsync([NotNull] string id, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var username = Options.UserName;
+ var password = Options.Password;
+ var authBase64Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
+
+ var requestMessage = new HttpRequestMessage(HttpMethod.Delete, $"/service/rest/v1/components/{id}");
+ requestMessage.Headers.Add("Authorization", $"Basic {authBase64Data}");
+
+ var response = await client.SendAsync(requestMessage, cancellationToken);
+ return response.IsSuccessStatusCode;
+ }
+
+ public async virtual Task GetAsync([NotNull] string id, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var response = await client.GetAsync($"/service/rest/v1/components/{id}", cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusComponent = JsonSerializer.Deserialize(responseContent);
+
+ return nexusComponent;
+ }
+
+ public async virtual Task ListAsync([NotNull] string repository, string continuationToken = null, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/service/rest/v1/components");
+ urlBuilder.AppendFormat("?repository={0}", repository);
+ if (!continuationToken.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&continuationToken={0}", continuationToken);
+ }
+
+ var response = await client.GetAsync(urlBuilder.ToString(), cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusComponentListResult = JsonSerializer.Deserialize(responseContent);
+
+ return nexusComponentListResult;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentUploadArgs.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentUploadArgs.cs
new file mode 100644
index 000000000..3d8ebb348
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusComponentUploadArgs.cs
@@ -0,0 +1,16 @@
+using System.Net.Http;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+public abstract class NexusComponentUploadArgs
+{
+ public string Repository { get; }
+ public string Directory { get; }
+
+ protected NexusComponentUploadArgs(string repository, string directory)
+ {
+ Repository = repository;
+ Directory = directory;
+ }
+
+ public abstract HttpContent BuildContent();
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusRawBlobUploadArgs.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusRawBlobUploadArgs.cs
new file mode 100644
index 000000000..1b781d92c
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Components/NexusRawBlobUploadArgs.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Components;
+public class NexusRawBlobUploadArgs : NexusComponentUploadArgs
+{
+ public Asset Asset1 { get; }
+ public Asset Asset2 { get; }
+ public Asset Asset3 { get; }
+
+ public NexusRawBlobUploadArgs(
+ string repository,
+ string directory,
+ Asset asset1,
+ Asset asset2 = null,
+ Asset asset3 = null)
+ : base(repository, directory)
+ {
+ Asset1 = asset1;
+ Asset2 = asset2;
+ Asset3 = asset3;
+ }
+
+ public override HttpContent BuildContent()
+ {
+ var boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
+ var formDataContent = new MultipartFormDataContent(boundary);
+ formDataContent.Headers.ContentType = MediaTypeHeaderValue.Parse($"multipart/form-data; boundary={boundary}");
+
+ var rawDirectory = new StringContent(Directory);
+ rawDirectory.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.directory");
+
+ if (Asset1 != null)
+ {
+ var rawAsset1 = new ByteArrayContent(Asset1.FileBytes);
+ rawAsset1.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset1");
+ rawAsset1.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
+ rawAsset1.Headers.ContentLength = Asset1.FileBytes.Length;
+
+ var rawAsset1FileName = new StringContent(Asset1.FileName);
+ rawAsset1FileName.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset1.filename");
+
+ formDataContent.Add(rawAsset1);
+ formDataContent.Add(rawAsset1FileName);
+ }
+
+ if (Asset2 != null)
+ {
+ var rawAsset2 = new ByteArrayContent(Asset2.FileBytes);
+ rawAsset2.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset2");
+ rawAsset2.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
+ rawAsset2.Headers.ContentLength = Asset2.FileBytes.Length;
+
+ var rawAsset2FileName = new StringContent(Asset2.FileName);
+ rawAsset2FileName.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset2.filename");
+
+ formDataContent.Add(rawAsset2);
+ formDataContent.Add(rawAsset2FileName);
+ }
+
+ if (Asset3 != null)
+ {
+ var rawAsset3 = new ByteArrayContent(Asset2.FileBytes);
+ rawAsset3.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset3");
+ rawAsset3.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
+ rawAsset3.Headers.ContentLength = Asset2.FileBytes.Length;
+
+ var rawAsset3FileName = new StringContent(Asset2.FileName);
+ rawAsset3FileName.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse($"form-data; name=raw.asset3.filename");
+
+ formDataContent.Add(rawAsset3);
+ formDataContent.Add(rawAsset3FileName);
+ }
+
+ formDataContent.Add(rawDirectory);
+
+ return formDataContent;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/INexusRepositoryManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/INexusRepositoryManager.cs
new file mode 100644
index 000000000..7904314f1
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/INexusRepositoryManager.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories;
+
+public interface INexusRepositoryManager
+ where TRepository : NexusRepository
+ where TRepositoryCreateArgs : NexusRepositoryCreateArgs
+ where TRepositoryUpdateArgs: NexusRepositoryUpdateArgs
+{
+ Task CreateAsync(TRepositoryCreateArgs args, CancellationToken cancellationToken = default);
+
+ Task GetAsync(string name, CancellationToken cancellationToken = default);
+
+ Task UpdateAsync(string name, TRepositoryUpdateArgs args, CancellationToken cancellationToken = default);
+
+ Task DeleteAsync(string name, CancellationToken cancellationToken = default);
+
+ Task> ListAsync(CancellationToken cancellationToken = default);
+}
+
+public interface INexusRepositoryManager
+ where TRepository : NexusRepository
+ where TRepositoryCreateArgs : NexusRepositoryCreateArgs
+{
+ Task CreateAsync(TRepositoryCreateArgs args, CancellationToken cancellationToken = default);
+
+ Task GetAsync(string name, CancellationToken cancellationToken = default);
+
+ Task DeleteAsync(string name, CancellationToken cancellationToken = default);
+
+ Task> ListAsync(CancellationToken cancellationToken = default);
+}
+
+
+public interface INexusRepositoryManager
+ where TRepository : NexusRepository
+{
+ Task GetAsync(string name, CancellationToken cancellationToken = default);
+
+ Task DeleteAsync(string name, CancellationToken cancellationToken = default);
+
+ Task> ListAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepository.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepository.cs
new file mode 100644
index 000000000..f04fd533b
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepository.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories;
+
+[Serializable]
+public abstract class NexusRepository
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("online")]
+ public bool Online { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryCreateArgs.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryCreateArgs.cs
new file mode 100644
index 000000000..31d680af3
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryCreateArgs.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories;
+
+[Serializable]
+public abstract class NexusRepositoryCreateArgs
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("online")]
+ public bool Online { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryListResult.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryListResult.cs
new file mode 100644
index 000000000..9ca93ebf6
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryListResult.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories;
+
+[Serializable]
+public class NexusRepositoryListResult
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("format")]
+ public string Format { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+
+ [JsonPropertyName("attributes")]
+ public Dictionary Attributes { get; set; }
+
+ public NexusRepositoryListResult()
+ {
+ Attributes = new Dictionary();
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryUpdateArgs.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryUpdateArgs.cs
new file mode 100644
index 000000000..46b188b7a
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/NexusRepositoryUpdateArgs.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories;
+
+[Serializable]
+public abstract class NexusRepositoryUpdateArgs
+{
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("online")]
+ public bool Online { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/INexusRawRepositoryManager.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/INexusRawRepositoryManager.cs
new file mode 100644
index 000000000..a6fbae003
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/INexusRawRepositoryManager.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories.Raw;
+
+public interface INexusRawRepositoryManager
+{
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/NexusRawRepository.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/NexusRawRepository.cs
new file mode 100644
index 000000000..b16666972
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/NexusRawRepository.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories.Raw;
+
+public class NexusRawRepository : NexusRepository
+{
+ [JsonPropertyName("storage")]
+ public RawStorage Storage { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawGroup.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawGroup.cs
new file mode 100644
index 000000000..b556153df
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawGroup.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories.Raw;
+
+[Serializable]
+public class RawGroup
+{
+ [JsonPropertyName("memberNames")]
+ public List MemberNames { get; set; }
+
+ public RawGroup()
+ {
+ MemberNames = new List();
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawStorage.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawStorage.cs
new file mode 100644
index 000000000..5c1aefb6b
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Repositories/Raw/RawStorage.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Repositories.Raw;
+
+[Serializable]
+public class RawStorage
+{
+ [JsonPropertyName("blobStoreName")]
+ public string BlobStoreName { get; set; }
+
+ [JsonPropertyName("strictContentTypeValidation")]
+ public bool StrictContentTypeValidation { get; set; }
+
+ [JsonPropertyName("RawGroup")]
+ public RawGroup Group { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/INexusLookupService.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/INexusLookupService.cs
new file mode 100644
index 000000000..86d8aa531
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/INexusLookupService.cs
@@ -0,0 +1,13 @@
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Components;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Search;
+
+public interface INexusLookupService
+{
+ Task ListComponentAsync(NexusSearchArgs args, CancellationToken cancellationToken = default);
+
+ Task ListAssetAsync(NexusSearchArgs args, CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusLookupService.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusLookupService.cs
new file mode 100644
index 000000000..efa046f18
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusLookupService.cs
@@ -0,0 +1,97 @@
+using LINGYUN.Abp.Sonatype.Nexus.Assets;
+using LINGYUN.Abp.Sonatype.Nexus.Components;
+using Microsoft.Extensions.Options;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Search;
+public class NexusLookupService : INexusLookupService, ISingletonDependency
+{
+ protected IJsonSerializer JsonSerializer { get; }
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected AbpSonatypeNexusOptions Options { get; }
+
+ public NexusLookupService(
+ IJsonSerializer jsonSerializer,
+ IHttpClientFactory httpClientFactory,
+ IOptions options)
+ {
+ Options = options.Value;
+ JsonSerializer = jsonSerializer;
+ HttpClientFactory = httpClientFactory;
+ }
+
+ public async virtual Task ListAssetAsync(NexusSearchArgs args, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var urlBuilder = new StringBuilder();
+ urlBuilder.AppendFormat("/service/rest/v1/search/assets?format={0}", args.Format);
+ urlBuilder.AppendFormat("&repository={0}", args.Repository);
+ urlBuilder.AppendFormat("&group={0}", args.Group);
+ urlBuilder.AppendFormat("&name={0}", args.Name);
+ if(!args.Keyword.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&q={0}", args.Keyword);
+ }
+ if (!args.Version.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&version={0}", args.Version);
+ }
+ if (args.Timeout.HasValue)
+ {
+ urlBuilder.AppendFormat("&timeout={0}", args.Timeout.Value);
+ }
+
+ var response = await client.GetAsync(urlBuilder.ToString(), cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusAssetListResult = JsonSerializer.Deserialize(responseContent);
+
+ return nexusAssetListResult;
+ }
+
+ public async virtual Task ListComponentAsync(NexusSearchArgs args, CancellationToken cancellationToken = default)
+ {
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ var urlBuilder = new StringBuilder();
+ urlBuilder.AppendFormat("/service/rest/v1/search?format={0}", args.Format);
+ urlBuilder.AppendFormat("&repository={0}", args.Repository);
+ urlBuilder.AppendFormat("&group={0}", args.Group);
+ urlBuilder.AppendFormat("&name={0}", args.Name);
+ if (!args.Keyword.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&q={0}", args.Keyword);
+ }
+ if (!args.Version.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&version={0}", args.Version);
+ }
+ if (args.Timeout.HasValue)
+ {
+ urlBuilder.AppendFormat("&timeout={0}", args.Timeout.Value);
+ }
+
+ var response = await client.GetAsync(urlBuilder.ToString(), cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var nexusComponentListResult = JsonSerializer.Deserialize(responseContent);
+
+ return nexusComponentListResult;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusSearchArgs.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusSearchArgs.cs
new file mode 100644
index 000000000..7593da112
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Search/NexusSearchArgs.cs
@@ -0,0 +1,29 @@
+namespace LINGYUN.Abp.Sonatype.Nexus.Search;
+public class NexusSearchArgs
+{
+ public string Keyword { get; }
+ public string Repository { get; }
+ public string Group { get; }
+ public string Name { get; }
+ public string Format { get; set; } = "raw";
+ public int? Timeout { get; set; }
+ public string Version { get; }
+
+ public NexusSearchArgs(
+ string repository,
+ string group,
+ string name,
+ string format = "raw",
+ string keyword = null,
+ string version = null,
+ int? timeout = null)
+ {
+ Keyword = keyword;
+ Repository = repository;
+ Group = group;
+ Name = name;
+ Format = format;
+ Timeout = timeout;
+ Version = version;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAsset.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAsset.cs
new file mode 100644
index 000000000..8742588ea
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAsset.cs
@@ -0,0 +1,16 @@
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Assets;
+
+public static class CoreUIAsset
+{
+ public static CoreUIRequest Read(
+ string assetId,
+ string repository)
+ {
+ var asset = new CoreUIAssetRead(assetId, repository);
+
+ return new CoreUIRequest(
+ "coreui_Component",
+ "readAsset",
+ asset);
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetData.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetData.cs
new file mode 100644
index 000000000..265c8cce3
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetData.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Assets;
+
+[Serializable]
+public class CoreUIAssetData
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("format")]
+ public string Format { get; set; }
+
+ [JsonPropertyName("contentType")]
+ public string ContentType { get; set; }
+
+ [JsonPropertyName("blobUpdated")]
+ public DateTime? BlobUpdated { get; set; }
+
+ [JsonPropertyName("blobCreated")]
+ public DateTime? BlobCreated { get; set; }
+
+ [JsonPropertyName("createdBy")]
+ public string CreatedBy { get; set; }
+
+ [JsonPropertyName("createdByIp")]
+ public string CreatedByIp { get; set; }
+
+ [JsonPropertyName("blobRef")]
+ public string BlobRef { get; set; }
+
+ [JsonPropertyName("componentId")]
+ public string ComponentId { get; set; }
+
+ [JsonPropertyName("lastDownloaded")]
+ public string LastDownloaded { get; set; }
+
+ [JsonPropertyName("containingRepositoryName")]
+ public string ContainingRepositoryName { get; set; }
+
+ [JsonPropertyName("repositoryName")]
+ public string RepositoryName { get; set; }
+
+ [JsonPropertyName("size")]
+ public long Size { get; set; }
+
+ [JsonPropertyName("attributes")]
+ public Dictionary> Attributes { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetRead.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetRead.cs
new file mode 100644
index 000000000..5ff7225e6
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetRead.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Assets;
+public class CoreUIAssetRead : List
+{
+ public CoreUIAssetRead(
+ string assetId,
+ string repository)
+ {
+ Add(assetId);
+ Add(repository);
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetResult.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetResult.cs
new file mode 100644
index 000000000..1c9fdaa4b
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Assets/CoreUIAssetResult.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Assets;
+
+[Serializable]
+public class CoreUIAssetResult
+{
+ [JsonPropertyName("success")]
+ public bool Success { get; set; }
+
+ [JsonPropertyName("data")]
+ public CoreUIAssetData Data { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowse.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowse.cs
new file mode 100644
index 000000000..487b8f20b
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowse.cs
@@ -0,0 +1,16 @@
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+
+public static class CoreUIBrowse
+{
+ public static CoreUIRequest Read(
+ string repository,
+ string node = "/")
+ {
+ var readComponent = new CoreUIBrowseReadComponent(repository, node);
+
+ return new CoreUIRequest(
+ "coreui_Browse",
+ "read",
+ readComponent);
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponent.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponent.cs
new file mode 100644
index 000000000..51dfce235
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponent.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+
+[Serializable]
+public class CoreUIBrowseComponent
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("assetId")]
+ public string AssetId { get; set; }
+
+ [JsonPropertyName("componentId")]
+ public string ComponentId { get; set; }
+
+ [JsonPropertyName("packageUrl")]
+ public string PackageUrl { get; set; }
+
+ [JsonPropertyName("text")]
+ public string Text { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("leaf")]
+ public bool Leaf { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponentResult.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponentResult.cs
new file mode 100644
index 000000000..9da2a1813
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseComponentResult.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+
+[Serializable]
+public class CoreUIBrowseComponentResult
+{
+ [JsonPropertyName("success")]
+ public bool Success { get; set; }
+
+ [JsonPropertyName("data")]
+ public List Data { get; set; }
+
+ public CoreUIBrowseComponentResult()
+ {
+ Data = new List();
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseNode.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseNode.cs
new file mode 100644
index 000000000..180a99c50
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseNode.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+
+[Serializable]
+public class CoreUIBrowseNode
+{
+ [JsonPropertyName("node")]
+ public string Node { get; set; }
+
+ [JsonPropertyName("repositoryName")]
+ public string RepositoryName { get; set; }
+ public CoreUIBrowseNode(string repositoryName, string node = "/")
+ {
+ Node = node;
+ RepositoryName = repositoryName;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseReadComponent.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseReadComponent.cs
new file mode 100644
index 000000000..5910d61c6
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/Browsers/CoreUIBrowseReadComponent.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI.Browsers;
+public class CoreUIBrowseReadComponent : List
+{
+ public CoreUIBrowseReadComponent(string repository, string node = "/")
+ {
+ Add(new CoreUIBrowseNode(repository, node));
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIRequest.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIRequest.cs
new file mode 100644
index 000000000..250d51eb8
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIRequest.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+
+[Serializable]
+public class CoreUIRequest
+{
+ [JsonPropertyName("action")]
+ public string Action { get; set; }
+
+ [JsonPropertyName("method")]
+ public string Method { get; set; }
+
+ [JsonPropertyName("tid")]
+ public long Tid { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("data")]
+ public TData Data { get; set; }
+
+ public CoreUIRequest(
+ string action,
+ string method,
+ TData data,
+ string type = "rpc")
+ {
+ Action = action;
+ Method = method;
+ Type = type;
+ Data = data;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIResponse.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIResponse.cs
new file mode 100644
index 000000000..dc7d64e60
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUIResponse.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+
+[Serializable]
+public class CoreUIResponse
+{
+ [JsonPropertyName("action")]
+ public string Action { get; set; }
+
+ [JsonPropertyName("method")]
+ public string Method { get; set; }
+
+ [JsonPropertyName("tid")]
+ public long Tid { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("result")]
+ public TResult Result { get; set; }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUiServiceProxy.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUiServiceProxy.cs
new file mode 100644
index 000000000..f2ab4969d
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/CoreUiServiceProxy.cs
@@ -0,0 +1,55 @@
+using Microsoft.Extensions.Options;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Json;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+
+public class CoreUiServiceProxy : ICoreUiServiceProxy, ISingletonDependency
+{
+ private static int _idCurrent = 1;
+ protected IJsonSerializer JsonSerializer { get; }
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected AbpSonatypeNexusOptions Options { get; }
+
+ public CoreUiServiceProxy(
+ IJsonSerializer jsonSerializer,
+ IHttpClientFactory httpClientFactory,
+ IOptions options)
+ {
+ Options = options.Value;
+ JsonSerializer = jsonSerializer;
+ HttpClientFactory = httpClientFactory;
+ }
+
+ public async virtual Task> SearchAsync(
+ CoreUIRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Interlocked.Increment(ref _idCurrent);
+ request.Tid = _idCurrent;
+ var client = HttpClientFactory.CreateClient(SonatypeNexusConsts.ApiClient);
+
+ using var requestContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
+ var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/service/extdirect")
+ {
+ Content = requestContent,
+ };
+
+ var response = await client.SendAsync(requestMessage, cancellationToken);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpException(response.ReasonPhrase);
+ }
+
+ var responseContent = await response.Content.ReadAsStringAsync();
+ var coreUiResponse = JsonSerializer.Deserialize>(responseContent);
+
+ return coreUiResponse;
+ }
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/ICoreUiServiceProxy.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/ICoreUiServiceProxy.cs
new file mode 100644
index 000000000..49790e478
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/CoreUI/ICoreUiServiceProxy.cs
@@ -0,0 +1,9 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.Sonatype.Nexus.Services.CoreUI;
+
+public interface ICoreUiServiceProxy : INexusServiceProxy
+{
+ Task> SearchAsync(CoreUIRequest request, CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/INexusServiceProxy.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/INexusServiceProxy.cs
new file mode 100644
index 000000000..75dba860a
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/Services/INexusServiceProxy.cs
@@ -0,0 +1,5 @@
+namespace LINGYUN.Abp.Sonatype.Nexus.Services;
+public interface INexusServiceProxy
+{
+
+}
diff --git a/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/SonatypeNexusConsts.cs b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/SonatypeNexusConsts.cs
new file mode 100644
index 000000000..887bbddc4
--- /dev/null
+++ b/aspnet-core/modules/nexus/LINGYUN.Abp.Sonatype.Nexus/LINGYUN/Abp/Sonatype/Nexus/SonatypeNexusConsts.cs
@@ -0,0 +1,5 @@
+namespace LINGYUN.Abp.Sonatype.Nexus;
+internal static class SonatypeNexusConsts
+{
+ public const string ApiClient = "Sonatype.Nexus";
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/GlobalUsings.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/GlobalUsings.cs
new file mode 100644
index 000000000..bd8299f6f
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/GlobalUsings.cs
@@ -0,0 +1,2 @@
+global using Xunit;
+global using Shouldly;
\ No newline at end of file
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN.Abp.BlobStoring.Nexus.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN.Abp.BlobStoring.Nexus.Tests.csproj
new file mode 100644
index 000000000..322977a07
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN.Abp.BlobStoring.Nexus.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net7.0
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestBase.cs
new file mode 100644
index 000000000..94ab0df57
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestBase.cs
@@ -0,0 +1,6 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public abstract class AbpBlobStoringNexusTestBase : AbpTestsBase
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestModule.cs
new file mode 100644
index 000000000..bd6a9c5b2
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/AbpBlobStoringNexusTestModule.cs
@@ -0,0 +1,41 @@
+using LINGYUN.Abp.Tests;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+
+[DependsOn(
+ typeof(AbpBlobStoringNexusModule),
+ typeof(AbpTestsBaseModule))]
+public class AbpBlobStoringNexusTestModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ var configurationOptions = new AbpConfigurationBuilderOptions
+ {
+ BasePath = @"D:\Projects\Development\Abp\BlobStoring\Nexus",
+ 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.UseNexus(nexus =>
+ {
+ nexus.BasePath = configuration[NexusBlobProviderConfigurationNames.BasePath];
+ nexus.Repository = configuration[NexusBlobProviderConfigurationNames.Repository];
+ });
+ });
+ });
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainer_Tests.cs
new file mode 100644
index 000000000..0fb47cd9d
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/LINGYUN/Abp/BlobStoring/Nexus/NexusBlobContainer_Tests.cs
@@ -0,0 +1,11 @@
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Nexus;
+public class NexusBlobContainer_Tests : BlobContainer_Tests
+{
+ public NexusBlobContainer_Tests()
+ {
+
+ }
+}
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs
new file mode 100644
index 000000000..e41421da5
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.BlobStoring.TestObjects;
+using Volo.Abp.Clients;
+using Volo.Abp.Modularity;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Testing;
+using Xunit;
+
+namespace Volo.Abp.BlobStoring;
+
+public abstract class BlobContainer_Tests : AbpIntegratedTest
+ where TStartupModule : IAbpModule
+{
+ protected IBlobContainer Container { get; }
+
+ protected ICurrentTenant CurrentTenant { get; }
+
+ protected BlobContainer_Tests()
+ {
+ Container = GetRequiredService>();
+ CurrentTenant = GetRequiredService();
+ }
+
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+
+ [Theory]
+ [InlineData("Should_Save_And_Get_Blobs")]
+ [InlineData("Should_Save_And_Get_Blobs.txt")]
+ [InlineData("test-folder/Should_Save_And_Get_Blobs")]
+ public async Task Should_Save_And_Get_Blobs(string blobName)
+ {
+ var testContent = "test content".GetBytes();
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(3000);
+ var result = await Container.GetAllBytesAsync(blobName);
+ result.SequenceEqual(testContent).ShouldBeTrue();
+ await Container.DeleteAsync(blobName);
+ }
+
+ [Fact]
+ public async Task Should_Save_And_Get_Blobs_In_Different_Tenant()
+ {
+ var blobName = "Should_Save_And_Get_Blobs_In_Different_Tenant";
+ var testContent = "test content".GetBytes();
+
+ using (CurrentTenant.Change(Guid.NewGuid()))
+ {
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(2000);
+ (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue();
+ await Container.DeleteAsync(blobName);
+ await Task.Delay(2000);
+ }
+
+ using (CurrentTenant.Change(Guid.NewGuid()))
+ {
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(2000);
+ (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue();
+
+ using (CurrentTenant.Change(null))
+ {
+ // Could not found the requested BLOB...
+ await Assert.ThrowsAsync(async () =>
+ await Container.GetAllBytesAsync(blobName)
+ );
+ }
+
+ await Container.DeleteAsync(blobName);
+ await Task.Delay(2000);
+ }
+
+ using (CurrentTenant.Change(null))
+ {
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(2000);
+ (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue();
+ await Container.DeleteAsync(blobName);
+ }
+ }
+
+ [Fact]
+ public async Task Should_Overwrite_Pre_Saved_Blob_If_Requested()
+ {
+ var blobName = "Should_Overwrite_Pre_Saved_Blob_If_Requested";
+
+ var testContent = "test content".GetBytes();
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(2000);
+ var testContentOverwritten = "test content overwritten".GetBytes();
+ await Container.SaveAsync(blobName, testContentOverwritten, true);
+ await Task.Delay(2000);
+ var result = await Container.GetAllBytesAsync(blobName);
+ result.SequenceEqual(testContentOverwritten).ShouldBeTrue();
+ await Container.DeleteAsync(blobName);
+ }
+
+ [Fact]
+ public async Task Should_Not_Allow_To_Overwrite_Pre_Saved_Blob_By_Default()
+ {
+ var blobName = "Should_Not_Allow_To_Overwrite_Pre_Saved_Blob_By_Default";
+
+ var testContent = "test content".GetBytes();
+ await Container.SaveAsync(blobName, testContent);
+ await Task.Delay(2000);
+ var testContentOverwritten = "test content overwritten".GetBytes();
+ await Assert.ThrowsAsync(() =>
+ Container.SaveAsync(blobName, testContentOverwritten)
+ );
+
+ await Container.DeleteAsync(blobName);
+ }
+
+ [Theory]
+ [InlineData("Should_Delete_Saved_Blobs")]
+ [InlineData("Should_Delete_Saved_Blobs.txt")]
+ [InlineData("test-folder/Should_Delete_Saved_Blobs")]
+ public async Task Should_Delete_Saved_Blobs(string blobName)
+ {
+ await Container.SaveAsync(blobName, "test content".GetBytes());
+ await Task.Delay(2000);
+ (await Container.GetAllBytesAsync(blobName)).ShouldNotBeNull();
+
+ await Container.DeleteAsync(blobName);
+ await Task.Delay(2000);
+ (await Container.GetAllBytesOrNullAsync(blobName)).ShouldBeNull();
+ }
+
+ [Theory]
+ [InlineData("Saved_Blobs_Should_Exists")]
+ [InlineData("Saved_Blobs_Should_Exists.txt")]
+ [InlineData("test-folder/Saved_Blobs_Should_Exists")]
+ public async Task Saved_Blobs_Should_Exists(string blobName)
+ {
+ await Container.SaveAsync(blobName, "test content".GetBytes());
+ await Task.Delay(2000);
+ (await Container.ExistsAsync(blobName)).ShouldBeTrue();
+
+ await Container.DeleteAsync(blobName);
+ await Task.Delay(2000);
+ (await Container.ExistsAsync(blobName)).ShouldBeFalse();
+ }
+
+ [Theory]
+ [InlineData("Unknown_Blobs_Should_Not_Exists")]
+ [InlineData("Unknown_Blobs_Should_Not_Exists.txt")]
+ [InlineData("test-folder/Unknown_Blobs_Should_Not_Exists")]
+ public async Task Unknown_Blobs_Should_Not_Exists(string blobName)
+ {
+ await Container.DeleteAsync(blobName);
+ await Task.Delay(2000);
+ (await Container.ExistsAsync(blobName)).ShouldBeFalse();
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer1.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer1.cs
new file mode 100644
index 000000000..b39fde642
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer1.cs
@@ -0,0 +1,6 @@
+namespace Volo.Abp.BlobStoring.TestObjects;
+
+public class TestContainer1
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer2.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer2.cs
new file mode 100644
index 000000000..192387db5
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer2.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.BlobStoring.TestObjects;
+
+[BlobContainerName("Test2")]
+public class TestContainer2
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer3.cs b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer3.cs
new file mode 100644
index 000000000..77589bf19
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.BlobStoring.Nexus.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer3.cs
@@ -0,0 +1,6 @@
+namespace Volo.Abp.BlobStoring.TestObjects;
+
+public class TestContainer3
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/GlobalUsings.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/GlobalUsings.cs
new file mode 100644
index 000000000..8c927eb74
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN.Abp.OssManagement.Nexus.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN.Abp.OssManagement.Nexus.Tests.csproj
new file mode 100644
index 000000000..40e07a32e
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN.Abp.OssManagement.Nexus.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net7.0
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsBase.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsBase.cs
new file mode 100644
index 000000000..e1d9501e6
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsBase.cs
@@ -0,0 +1,6 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+public abstract class AbpOssManagementNexusTestsBase : AbpTestsBase
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsModule.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsModule.cs
new file mode 100644
index 000000000..513369192
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/AbpOssManagementNexusTestsModule.cs
@@ -0,0 +1,12 @@
+using LINGYUN.Abp.BlobStoring.Nexus;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+
+[DependsOn(
+ typeof(AbpOssManagementNexusModule),
+ typeof(AbpBlobStoringNexusTestModule))]
+public class AbpOssManagementNexusTestsModule : AbpModule
+{
+
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactoryTests.cs b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactoryTests.cs
new file mode 100644
index 000000000..c990b9258
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.OssManagement.Nexus.Tests/LINGYUN/Abp/OssManagement/Nexus/NexusOssContainerFactoryTests.cs
@@ -0,0 +1,44 @@
+using Shouldly;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.OssManagement.Nexus;
+public class NexusOssContainerFactoryTests : AbpOssManagementNexusTestsBase
+{
+ protected IOssContainerFactory OssContainerFactory { get; }
+
+ public NexusOssContainerFactoryTests()
+ {
+ OssContainerFactory = GetRequiredService();
+ }
+
+ [Theory]
+ [InlineData("/test-repo")]
+ public async virtual Task CreateAsync(string containerName)
+ {
+ var ossContainer = OssContainerFactory.Create();
+
+ await ossContainer.CreateAsync(containerName);
+ }
+
+ [Theory]
+ [InlineData("/test-repo", "CreateObjectAsync", "/aaa/bbb/ccc")]
+ public async virtual Task CreateObjectAsync(string containerName, string objectName, string path = null)
+ {
+ var textBytes = Encoding.UTF8.GetBytes("CreateObjectAsync");
+ using var stream = new MemoryStream();
+ await stream.WriteAsync(textBytes, 0, textBytes.Length);
+
+ var ossContainer = OssContainerFactory.Create();
+ var createObjectRequest = new CreateOssObjectRequest(
+ containerName,
+ objectName,
+ stream,
+ path);
+
+ var oss = await ossContainer.CreateObjectAsync(createObjectRequest);
+
+ oss.ShouldNotBeNull();
+ }
+}