diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN.Abp.BlobStoring.Qiniu.csproj b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN.Abp.BlobStoring.Qiniu.csproj
new file mode 100644
index 000000000..95f8bf682
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN.Abp.BlobStoring.Qiniu.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netstandard2.0
+ true
+ 2.9.0
+ LINGYUN
+ 阿里云Oss对象存储Abp集成
+
+
+
+ D:\LocalNuget
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/AbpBlobStoringQiniuModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/AbpBlobStoringQiniuModule.cs
new file mode 100644
index 000000000..8631fb6a8
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/AbpBlobStoringQiniuModule.cs
@@ -0,0 +1,11 @@
+using System;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ [DependsOn(typeof(AbpBlobStoringModule))]
+ public class AbpBlobStoringQiniuModule : AbpModule
+ {
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/DefaultQiniuBlobNameCalculator.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/DefaultQiniuBlobNameCalculator.cs
new file mode 100644
index 000000000..7d49d0548
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/DefaultQiniuBlobNameCalculator.cs
@@ -0,0 +1,24 @@
+using Volo.Abp.BlobStoring;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public class DefaultQiniuBlobNameCalculator : IQiniuBlobNameCalculator, ITransientDependency
+ {
+ protected ICurrentTenant CurrentTenant { get; }
+
+ public DefaultQiniuBlobNameCalculator(
+ ICurrentTenant currentTenant)
+ {
+ CurrentTenant = currentTenant;
+ }
+
+ public string Calculate(BlobProviderArgs args)
+ {
+ return CurrentTenant.Id == null
+ ? $"host/{args.BlobName}"
+ : $"tenants/{CurrentTenant.Id.Value:D}/{args.BlobName}";
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/IQiniuBlobNameCalculator.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/IQiniuBlobNameCalculator.cs
new file mode 100644
index 000000000..e58ba7b4b
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/IQiniuBlobNameCalculator.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public interface IQiniuBlobNameCalculator
+ {
+ string Calculate(BlobProviderArgs args);
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobContainerConfigurationExtensions.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobContainerConfigurationExtensions.cs
new file mode 100644
index 000000000..c3b562c67
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobContainerConfigurationExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public static class QiniuBlobContainerConfigurationExtensions
+ {
+ public static QiniuBlobProviderConfiguration GetQiniuConfiguration(
+ this BlobContainerConfiguration containerConfiguration)
+ {
+ return new QiniuBlobProviderConfiguration(containerConfiguration);
+ }
+
+ public static BlobContainerConfiguration UseQiniu(
+ this BlobContainerConfiguration containerConfiguration,
+ Action qiniuConfigureAction)
+ {
+ containerConfiguration.ProviderType = typeof(QiniuBlobProvider);
+
+ qiniuConfigureAction(new QiniuBlobProviderConfiguration(containerConfiguration));
+
+ return containerConfiguration;
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProvider.cs
new file mode 100644
index 000000000..0b9d2f4c9
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProvider.cs
@@ -0,0 +1,50 @@
+using Qiniu.Util;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Volo.Abp.BlobStoring;
+using Volo.Abp.DependencyInjection;
+using QiniuConfig = Qiniu.Common.Config;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public class QiniuBlobProvider : BlobProviderBase, ITransientDependency
+ {
+ protected IQiniuBlobNameCalculator QiniuBlobNameCalculator { get; }
+ public override Task DeleteAsync(BlobProviderDeleteArgs args)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task ExistsAsync(BlobProviderExistsArgs args)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task GetOrNullAsync(BlobProviderGetArgs args)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task SaveAsync(BlobProviderSaveArgs args)
+ {
+ throw new NotImplementedException();
+ }
+
+ private Mac GetMac(BlobProviderArgs args)
+ {
+ var configuration = args.Configuration.GetQiniuConfiguration();
+ return new Mac(configuration.AccessKey, configuration.SecretKey);
+ }
+
+ private string GetAndInitBucketName(BlobProviderArgs args)
+ {
+ var configuration = args.Configuration.GetQiniuConfiguration();
+ var bucketName = configuration.BucketName.IsNullOrWhiteSpace()
+ ? args.ContainerName
+ : configuration.BucketName;
+ QiniuConfig.AutoZone(configuration.AccessKey, bucketName, true);
+ return bucketName;
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfiguration.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfiguration.cs
new file mode 100644
index 000000000..2a2941291
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfiguration.cs
@@ -0,0 +1,85 @@
+using Volo.Abp;
+using Volo.Abp.BlobStoring;
+
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public class QiniuBlobProviderConfiguration
+ {
+ ///
+ /// Api授权码
+ ///
+ public string AccessKey
+ {
+ get => _containerConfiguration.GetConfiguration(QiniuBlobProviderConfigurationNames.AccessKey);
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.AccessKey, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+ ///
+ /// Api密钥
+ ///
+ public string SecretKey
+ {
+ get => _containerConfiguration.GetConfiguration(QiniuBlobProviderConfigurationNames.SecretKey);
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.SecretKey, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+ ///
+ /// 默认的Bucket名称
+ ///
+ public string BucketName
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.BucketName, "");
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.BucketName, value ?? "");
+ }
+ ///
+ /// 默认自动删除该文件天数
+ /// 默认 0,不删除
+ ///
+ public int DeleteAfterDays
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.DeleteAfterDays, 0);
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.DeleteAfterDays, value);
+ }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL。
+ /// 必须是公网上可以正常进行 POST 请求并能响应 HTTP/1.1 200 OK 的有效 URL
+ ///
+ public string UploadCallbackUrl
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.UploadCallbackUrl, "");
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.UploadCallbackUrl, value ?? "");
+ }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知时的 Host 值。
+ /// 与 callbackUrl 配合使用,仅当设置了 callbackUrl 时才有效。
+ ///
+ public string UploadCallbackHost
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.UploadCallbackHost, "");
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.UploadCallbackHost, value ?? "");
+ }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知 callbackBody 的 Content-Type。
+ /// 默认为 application/x-www-form-urlencoded,也可设置为 application/json。
+ ///
+ public string UploadCallbackBodyType
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.UploadCallbackBodyType, "");
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.UploadCallbackBodyType, value ?? "");
+ }
+ ///
+ /// 上传成功后,自定义七牛云最终返回給上传端(在指定 returnUrl 时是携带在跳转路径参数中)的数据。
+ /// 支持魔法变量和自定义变量。returnBody 要求是合法的 JSON 文本。
+ /// 例如 {"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}。
+ ///
+ public string UploadCallbackBody
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(QiniuBlobProviderConfigurationNames.UploadCallbackBody, "");
+ set => _containerConfiguration.SetConfiguration(QiniuBlobProviderConfigurationNames.UploadCallbackBody, value ?? "");
+ }
+ private readonly BlobContainerConfiguration _containerConfiguration;
+
+ public QiniuBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
+ {
+ _containerConfiguration = containerConfiguration;
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfigurationNames.cs b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfigurationNames.cs
new file mode 100644
index 000000000..ade594564
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.BlobStoring.Qiniu/LINGYUN/Abp/BlobStoring/Qiniu/QiniuBlobProviderConfigurationNames.cs
@@ -0,0 +1,44 @@
+namespace LINGYUN.Abp.BlobStoring.Qiniu
+{
+ public static class QiniuBlobProviderConfigurationNames
+ {
+ ///
+ /// Api授权码
+ ///
+ public const string AccessKey = "Qiniu:OSS:AccessKey";
+ ///
+ /// Api密钥
+ ///
+ public const string SecretKey = "Qiniu:OSS:SecretKey";
+ ///
+ /// 默认的Bucket名称
+ ///
+ public const string BucketName = "Qiniu:OSS:BucketName";
+ ///
+ /// 默认自动删除该文件天数
+ /// 默认 0,不删除
+ ///
+ public const string DeleteAfterDays = "Qiniu:OSS:DeleteAfterDays";
+ ///
+ /// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL。
+ /// 必须是公网上可以正常进行 POST 请求并能响应 HTTP/1.1 200 OK 的有效 URL
+ ///
+ public const string UploadCallbackUrl = "Qiniu:OSS:UploadCallbackUrl";
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知时的 Host 值。
+ /// 与 callbackUrl 配合使用,仅当设置了 callbackUrl 时才有效。
+ ///
+ public const string UploadCallbackHost = "Qiniu:OSS:UploadCallbackHost";
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知 callbackBody 的 Content-Type。
+ /// 默认为 application/x-www-form-urlencoded,也可设置为 application/json。
+ ///
+ public const string UploadCallbackBodyType = "Qiniu:OSS:UploadCallbackBodyType";
+ ///
+ /// 上传成功后,自定义七牛云最终返回給上传端(在指定 returnUrl 时是携带在跳转路径参数中)的数据。
+ /// 支持魔法变量和自定义变量。returnBody 要求是合法的 JSON 文本。
+ /// 例如 {"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}。
+ ///
+ public const string UploadCallbackBody = "Qiniu:OSS:UploadCallbackBody";
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
index 7a4dbebec..9a462fc2e 100644
--- a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
@@ -9,10 +9,6 @@
-
-
-
-
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN.Abp.MultiTenancy.DbFinder.csproj b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN.Abp.MultiTenancy.DbFinder.csproj
new file mode 100644
index 000000000..d73ac691a
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN.Abp.MultiTenancy.DbFinder.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netstandard2.0
+
+ true
+ 2.9.0
+ LINGYUN
+
+
+
+
+ D:\LocalNuget
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/AbpDbFinderMultiTenancyModule.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/AbpDbFinderMultiTenancyModule.cs
new file mode 100644
index 000000000..65eebc71b
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/AbpDbFinderMultiTenancyModule.cs
@@ -0,0 +1,14 @@
+using Volo.Abp.Caching;
+using Volo.Abp.Modularity;
+using Volo.Abp.TenantManagement;
+
+namespace LINGYUN.Abp.MultiTenancy.DbFinder
+{
+ [DependsOn(
+ typeof(AbpCachingModule),
+ typeof(AbpTenantManagementDomainModule)
+ )]
+ public class AbpDbFinderMultiTenancyModule : AbpModule
+ {
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantConfigurationCacheItem.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantConfigurationCacheItem.cs
new file mode 100644
index 000000000..55616c480
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantConfigurationCacheItem.cs
@@ -0,0 +1,26 @@
+using System;
+using Volo.Abp.Data;
+
+namespace LINGYUN.Abp.MultiTenancy.DbFinder
+{
+ public class TenantConfigurationCacheItem
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ // TODO: 是否需要加密存储?
+ public ConnectionStrings ConnectionStrings { get; set; } = new ConnectionStrings();
+
+ public TenantConfigurationCacheItem() { }
+
+ public TenantConfigurationCacheItem(Guid id, string name, ConnectionStrings connectionStrings)
+ {
+ Id = id;
+ Name = name;
+ ConnectionStrings = connectionStrings ?? ConnectionStrings;
+ }
+ public static string CalculateCacheKey(string key)
+ {
+ return "p:tenant" + ",k:" + key;
+ }
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantStore.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantStore.cs
new file mode 100644
index 000000000..3246f451a
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/LINGYUN/Abp/MultiTenancy/DbFinder/TenantStore.cs
@@ -0,0 +1,146 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Caching;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.TenantManagement;
+using Volo.Abp.Threading;
+
+namespace LINGYUN.Abp.MultiTenancy.DbFinder
+{
+ [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
+ [ExposeServices(typeof(ITenantStore))]
+ public class TenantStore : ITenantStore
+ {
+ public ILogger Logger { protected get; set; }
+ private readonly IDistributedCache _cache;
+
+ private readonly ITenantRepository _tenantRepository;
+
+ public TenantStore(
+ ITenantRepository tenantRepository,
+ IDistributedCache cache)
+ {
+ _cache = cache;
+ _tenantRepository = tenantRepository;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public virtual TenantConfiguration Find(string name)
+ {
+ var tenantCacheItem = AsyncHelper.RunSync(async () => await
+ GetCacheItemByNameAsync(name));
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual TenantConfiguration Find(Guid id)
+ {
+ var tenantCacheItem = AsyncHelper.RunSync(async () => await
+ GetCacheItemByIdAsync(id));
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual async Task FindAsync(string name)
+ {
+ var tenantCacheItem = await GetCacheItemByNameAsync(name);
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual async Task FindAsync(Guid id)
+ {
+ var tenantCacheItem = await GetCacheItemByIdAsync(id);
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ protected virtual async Task GetCacheItemByIdAsync(Guid id)
+ {
+ var cacheKey = TenantConfigurationCacheItem.CalculateCacheKey(id.ToString());
+
+ Logger.LogDebug($"TenantStore.GetCacheItemByIdAsync: {cacheKey}");
+
+ var cacheItem = await _cache.GetAsync(cacheKey);
+
+ if (cacheItem != null)
+ {
+ Logger.LogDebug($"Found in the cache: {cacheKey}");
+ return cacheItem;
+ }
+ Logger.LogDebug($"Not found in the cache, getting from the repository: {cacheKey}");
+
+ var tenant = await _tenantRepository.FindAsync(id, true);
+ if (tenant == null)
+ {
+ Logger.LogWarning($"Can not found tenant by id: {id}");
+ throw new AbpException($"Can not found tenant by id: {id}");
+ }
+ var connectionStrings = new ConnectionStrings();
+ foreach (var tenantConnectionString in tenant.ConnectionStrings)
+ {
+ connectionStrings[tenantConnectionString.Name] = tenantConnectionString.Value;
+ }
+ cacheItem = new TenantConfigurationCacheItem(tenant.Id, tenant.Name, connectionStrings);
+
+ Logger.LogDebug($"Setting the cache item: {cacheKey}");
+ await _cache.SetAsync(cacheKey, cacheItem);
+ Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
+
+ return cacheItem;
+ }
+ protected virtual async Task GetCacheItemByNameAsync(string name)
+ {
+ var cacheKey = TenantConfigurationCacheItem.CalculateCacheKey(name);
+
+ Logger.LogDebug($"TenantStore.GetCacheItemByNameAsync: {cacheKey}");
+
+ var cacheItem = await _cache.GetAsync(cacheKey);
+
+ if (cacheItem != null)
+ {
+ Logger.LogDebug($"Found in the cache: {cacheKey}");
+ return cacheItem;
+ }
+ Logger.LogDebug($"Not found in the cache, getting from the repository: {cacheKey}");
+
+ var tenant = await _tenantRepository.FindByNameAsync(name);
+ if (tenant == null)
+ {
+ Logger.LogWarning($"Can not found tenant by name: {name}");
+ throw new AbpException($"Can not found tenant by name: {name}");
+ }
+ var connectionStrings = new ConnectionStrings();
+ foreach (var tenantConnectionString in tenant.ConnectionStrings)
+ {
+ connectionStrings[tenantConnectionString.Name] = tenantConnectionString.Value;
+ }
+ cacheItem = new TenantConfigurationCacheItem(tenant.Id, tenant.Name, connectionStrings);
+
+ Logger.LogDebug($"Setting the cache item: {cacheKey}");
+ await _cache.SetAsync(cacheKey, cacheItem);
+ Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
+
+ return cacheItem;
+ }
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/README.md b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/README.md
new file mode 100644
index 000000000..0660b9bdd
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.DbFinder/README.md
@@ -0,0 +1,39 @@
+# LINGYUN.Abp.MultiTenancy.DbFinder
+
+abp 多租户数据库查询组件,引用此模块将首先从分布式缓存查询当前租户配置
+
+如果缓存不存在,则调用租户仓储接口获取租户数据,并存储到分布式缓存中
+
+## 配置使用
+
+模块按需引用
+
+启动项目需要引用**Volo.Abp.TenantManagement.EntityFrameworkCore**
+
+``` shell
+ // .NET CLI
+ dotnet add package Volo.Abp.TenantManagement.EntityFrameworkCore --version 2.9.0
+
+ // Package Manager
+Install-Package Volo.Abp.TenantManagement.EntityFrameworkCore -Version 2.9.0
+
+```
+
+事先定义**appsettings.json**文件
+
+```json
+{
+ "ConnectionStrings": {
+ "AbpTenantManagement": "Server=127.0.0.1;Database=TenantDb;User Id=root;Password=yourPassword"
+ }
+}
+
+```
+
+```csharp
+[DependsOn(typeof(AbpDbFinderMultiTenancyModule))]
+public class YouProjectModule : AbpModule
+{
+ // other
+}
+```
\ No newline at end of file
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN.Abp.MultiTenancy.RemoteService.csproj b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN.Abp.MultiTenancy.RemoteService.csproj
new file mode 100644
index 000000000..73b82b7f5
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN.Abp.MultiTenancy.RemoteService.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0
+
+ true
+ 2.9.0
+ LINGYUN
+
+
+
+
+ D:\LocalNuget
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/AbpRemoteServiceMultiTenancyModule.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/AbpRemoteServiceMultiTenancyModule.cs
new file mode 100644
index 000000000..ee4819e5c
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/AbpRemoteServiceMultiTenancyModule.cs
@@ -0,0 +1,13 @@
+using LINGYUN.Abp.TenantManagement;
+using Volo.Abp.Caching;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.MultiTenancy.RemoteService
+{
+ [DependsOn(
+ typeof(AbpCachingModule),
+ typeof(AbpTenantManagementHttpApiClientModule))]
+ public class AbpRemoteServiceMultiTenancyModule : AbpModule
+ {
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantConfigurationCacheItem.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantConfigurationCacheItem.cs
new file mode 100644
index 000000000..6a1994d2d
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantConfigurationCacheItem.cs
@@ -0,0 +1,26 @@
+using System;
+using Volo.Abp.Data;
+
+namespace LINGYUN.Abp.MultiTenancy.RemoteService
+{
+ public class TenantConfigurationCacheItem
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ // TODO: 是否需要加密存储?
+ public ConnectionStrings ConnectionStrings { get; set; } = new ConnectionStrings();
+
+ public TenantConfigurationCacheItem() { }
+
+ public TenantConfigurationCacheItem(Guid id, string name, ConnectionStrings connectionStrings)
+ {
+ Id = id;
+ Name = name;
+ ConnectionStrings = connectionStrings ?? ConnectionStrings;
+ }
+ public static string CalculateCacheKey(string key)
+ {
+ return "p:tenant" + ",k:" + key;
+ }
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantStore.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantStore.cs
new file mode 100644
index 000000000..65abf5ca0
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/LINGYUN/Abp/MultiTenancy/RemoteService/TenantStore.cs
@@ -0,0 +1,136 @@
+using LINGYUN.Abp.TenantManagement;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Caching;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.ObjectMapping;
+using Volo.Abp.Threading;
+
+namespace LINGYUN.Abp.MultiTenancy.RemoteService
+{
+ [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
+ [ExposeServices(typeof(ITenantStore))]
+ public class TenantStore : ITenantStore
+ {
+ public ILogger Logger { protected get; set; }
+ private readonly IDistributedCache _cache;
+
+ private readonly ITenantAppService _tenantAppService;
+ public TenantStore(
+ ITenantAppService tenantAppService,
+ IDistributedCache cache)
+ {
+ _cache = cache;
+ _tenantAppService = tenantAppService;
+
+ Logger = NullLogger.Instance;
+ }
+ public virtual TenantConfiguration Find(string name)
+ {
+ var tenantCacheItem = AsyncHelper.RunSync(async () => await
+ GetCacheItemByNameAsync(name));
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual TenantConfiguration Find(Guid id)
+ {
+ var tenantCacheItem = AsyncHelper.RunSync(async () => await
+ GetCacheItemByIdAsync(id));
+
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual async Task FindAsync(string name)
+ {
+ var tenantCacheItem = await GetCacheItemByNameAsync(name);
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ public virtual async Task FindAsync(Guid id)
+ {
+ var tenantCacheItem = await GetCacheItemByIdAsync(id);
+ return new TenantConfiguration(tenantCacheItem.Id, tenantCacheItem.Name)
+ {
+ ConnectionStrings = tenantCacheItem.ConnectionStrings
+ };
+ }
+
+ protected virtual async Task GetCacheItemByIdAsync(Guid id)
+ {
+ var cacheKey = TenantConfigurationCacheItem.CalculateCacheKey(id.ToString());
+
+ Logger.LogDebug($"TenantStore.GetCacheItemByIdAsync: {cacheKey}");
+
+ var cacheItem = await _cache.GetAsync(cacheKey);
+
+ if (cacheItem != null)
+ {
+ Logger.LogDebug($"Found in the cache: {cacheKey}");
+ return cacheItem;
+ }
+ Logger.LogDebug($"Not found in the cache, getting from the remote service: {cacheKey}");
+
+ var tenantDto = await _tenantAppService.GetAsync(id);
+ var tenantConnectionStringsDto = await _tenantAppService.GetConnectionStringAsync(id);
+ var connectionStrings = new ConnectionStrings();
+ foreach (var tenantConnectionString in tenantConnectionStringsDto.Items)
+ {
+ connectionStrings[tenantConnectionString.Name] = tenantConnectionString.Value;
+ }
+ cacheItem = new TenantConfigurationCacheItem(tenantDto.Id, tenantDto.Name, connectionStrings);
+
+ Logger.LogDebug($"Setting the cache item: {cacheKey}");
+ await _cache.SetAsync(cacheKey, cacheItem);
+ Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
+
+ return cacheItem;
+ }
+ protected virtual async Task GetCacheItemByNameAsync(string name)
+ {
+ var cacheKey = TenantConfigurationCacheItem.CalculateCacheKey(name);
+
+ Logger.LogDebug($"TenantStore.GetCacheItemByNameAsync: {cacheKey}");
+
+ var cacheItem = await _cache.GetAsync(cacheKey);
+
+ if (cacheItem != null)
+ {
+ Logger.LogDebug($"Found in the cache: {cacheKey}");
+ return cacheItem;
+ }
+ Logger.LogDebug($"Not found in the cache, getting from the remote service: {cacheKey}");
+
+ var tenantDto = await _tenantAppService.GetAsync(new TenantGetByNameInputDto(name));
+ var tenantConnectionStringsDto = await _tenantAppService.GetConnectionStringAsync(tenantDto.Id);
+ var connectionStrings = new ConnectionStrings();
+ foreach(var tenantConnectionString in tenantConnectionStringsDto.Items)
+ {
+ connectionStrings[tenantConnectionString.Name] = tenantConnectionString.Value;
+ }
+ cacheItem = new TenantConfigurationCacheItem(tenantDto.Id, tenantDto.Name, connectionStrings);
+
+ Logger.LogDebug($"Setting the cache item: {cacheKey}");
+ await _cache.SetAsync(cacheKey, cacheItem);
+ Logger.LogDebug($"Finished setting the cache item: {cacheKey}");
+
+ return cacheItem;
+ }
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/README.md b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/README.md
new file mode 100644
index 000000000..157b3f33e
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.MultiTenancy.RemoteService/README.md
@@ -0,0 +1,47 @@
+# LINGYUN.Abp.MultiTenancy.RemoteService
+
+abp 多租户远程服务组件,引用此模块将首先从分布式缓存查询当前租户配置
+
+如果缓存不存在,则调用远程租户服务接口获取租户数据,并存储到分布式缓存中
+
+## 配置使用
+
+模块按需引用,因为远程服务接口定义了授权策略,需要配置接口客户端授权
+
+具体**RemoteServices**与**IdentityClients**配置请阅读abp文档
+
+[RemoteServices配置参阅](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients)
+
+[IdentityClients配置参阅](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs)
+
+事先定义**appsettings.json**文件
+
+```json
+{
+ "RemoteServices": {
+ "TenantManagement": {
+ "BaseUrl": "http://localhost:30000/",
+ "IdentityClient": "tenant-finder-client"
+ }
+ },
+ "IdentityClients": {
+ "tenant-finder-client": {
+ "Authority": "http://localhost:44385",
+ "RequireHttps": false,
+ "GrantType": "client_credentials",
+ "ClientId": "tenant-finder-client",
+ "Scope": "tenant-service",
+ "ClientSecret": "1q2w3e*"
+ }
+ }
+}
+
+```
+
+```csharp
+[DependsOn(typeof(AbpRemoteServiceMultiTenancyModule))]
+public class YouProjectModule : AbpModule
+{
+ // other
+}
+```
\ No newline at end of file
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN.Abp.TenantManagement.Application.Contracts.csproj b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN.Abp.TenantManagement.Application.Contracts.csproj
index 0cac8b4d5..95f2f3261 100644
--- a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN.Abp.TenantManagement.Application.Contracts.csproj
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN.Abp.TenantManagement.Application.Contracts.csproj
@@ -3,8 +3,26 @@
netstandard2.0
+ true
+ 2.9.0
+ LINGYUN
+
+
+ D:\LocalNuget
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs
index dd1150e88..f79a22236 100644
--- a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/AbpTenantManagementApplicationContractsModule.cs
@@ -1,6 +1,9 @@
using Volo.Abp.Application;
+using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.TenantManagement;
+using Volo.Abp.TenantManagement.Localization;
+using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.TenantManagement
{
@@ -9,6 +12,19 @@ namespace LINGYUN.Abp.TenantManagement
typeof(AbpTenantManagementDomainSharedModule))]
public class AbpTenantManagementApplicationContractsModule : AbpModule
{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddVirtualJson("/LINGYUN/Abp/TenantManagement/Localization/Resources");
+ });
+ }
}
}
\ No newline at end of file
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Dto/TenantGetByNameInputDto.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Dto/TenantGetByNameInputDto.cs
new file mode 100644
index 000000000..1c79cc41c
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Dto/TenantGetByNameInputDto.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.TenantManagement;
+
+namespace LINGYUN.Abp.TenantManagement
+{
+ public class TenantGetByNameInputDto
+ {
+ [Required]
+ [StringLength(TenantConsts.MaxNameLength)]
+ public string Name { get; set; }
+
+ public TenantGetByNameInputDto() { }
+ public TenantGetByNameInputDto(string name)
+ {
+ Name = name;
+ }
+ }
+}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/ITenantAppService.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/ITenantAppService.cs
index f1d0d913e..d1e7b8b9e 100644
--- a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/ITenantAppService.cs
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/ITenantAppService.cs
@@ -7,6 +7,8 @@ namespace LINGYUN.Abp.TenantManagement
{
public interface ITenantAppService : ICrudAppService
{
+ Task GetAsync(TenantGetByNameInputDto tenantGetByNameInput);
+
Task GetConnectionStringAsync(TenantConnectionGetByNameInputDto tenantConnectionGetByName);
Task> GetConnectionStringAsync(Guid id);
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/en.json b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/en.json
new file mode 100644
index 000000000..79bff8a1b
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/en.json
@@ -0,0 +1,7 @@
+{
+ "culture": "en",
+ "texts": {
+ "TenantNotFoundById": "Tenant: {0} not found!",
+ "TenantNotFoundByName": "Tenant: {0} not found!"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/zh-Hans.json
new file mode 100644
index 000000000..b52f72dd3
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application.Contracts/LINGYUN/Abp/TenantManagement/Localization/Resources/zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "TenantNotFoundById": "租户: {0} 不存在!",
+ "TenantNotFoundByName": "租户: {0} 不存在!"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN/Abp/TenantManagement/TenantAppService.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN/Abp/TenantManagement/TenantAppService.cs
index f45750382..2782cd12b 100644
--- a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN/Abp/TenantManagement/TenantAppService.cs
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.Application/LINGYUN/Abp/TenantManagement/TenantAppService.cs
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectExtending;
@@ -29,7 +30,21 @@ namespace LINGYUN.Abp.TenantManagement
public virtual async Task GetAsync(Guid id)
{
- var tenant = await TenantRepository.GetAsync(id);
+ var tenant = await TenantRepository.FindAsync(id, false);
+ if(tenant == null)
+ {
+ throw new UserFriendlyException(L["TenantNotFoundById", id]);
+ }
+ return ObjectMapper.Map(tenant);
+ }
+
+ public virtual async Task GetAsync(TenantGetByNameInputDto tenantGetByNameInput)
+ {
+ var tenant = await TenantRepository.FindByNameAsync(tenantGetByNameInput.Name, false);
+ if (tenant == null)
+ {
+ throw new UserFriendlyException(L["TenantNotFoundByName", tenantGetByNameInput.Name]);
+ }
return ObjectMapper.Map(tenant);
}
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN.Abp.TenantManagement.HttpApi.Client.csproj b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN.Abp.TenantManagement.HttpApi.Client.csproj
new file mode 100644
index 000000000..7ece2b666
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN.Abp.TenantManagement.HttpApi.Client.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0
+
+ true
+ 2.9.0
+ LINGYUN
+
+
+
+
+ D:\LocalNuget
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs
new file mode 100644
index 000000000..9d8ee5004
--- /dev/null
+++ b/aspnet-core/modules/tenants/LINGYUN.Abp.TenantManagement.HttpApi.Client/LINGYUN/Abp/TenantManagement/AbpTenantManagementHttpApiClientModule.cs
@@ -0,0 +1,22 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Http.Client;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.TenantManagement
+{
+ [DependsOn(
+ typeof(AbpTenantManagementApplicationContractsModule),
+ typeof(AbpHttpClientModule))]
+ public class AbpTenantManagementHttpApiClientModule : AbpModule
+ {
+ public const string RemoteServiceName = "TenantManagement";
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddHttpClientProxies(
+ typeof(AbpTenantManagementApplicationContractsModule).Assembly,
+ RemoteServiceName
+ );
+ }
+ }
+}