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 + ); + } + } +}