diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs index ffbbec1e3..680622be9 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Application/LINGYUN/Abp/Saas/Tenants/TenantAppService.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.Saas.Features; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +9,6 @@ using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Data; using Volo.Abp.EventBus.Distributed; -using Volo.Abp.Features; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectExtending; @@ -20,15 +20,18 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService protected IDistributedEventBus EventBus { get; } protected ITenantRepository TenantRepository { get; } protected ITenantManager TenantManager { get; } + protected IConnectionStringChecker ConnectionStringChecker { get; } public TenantAppService( ITenantRepository tenantRepository, ITenantManager tenantManager, - IDistributedEventBus eventBus) + IDistributedEventBus eventBus, + IConnectionStringChecker connectionStringChecker) { EventBus = eventBus; TenantRepository = tenantRepository; TenantManager = tenantManager; + ConnectionStringChecker = connectionStringChecker; } public async virtual Task GetAsync(Guid id) @@ -80,6 +83,7 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService if (!input.UseSharedDatabase && !input.DefaultConnectionString.IsNullOrWhiteSpace()) { + await CheckConnectionString(input.DefaultConnectionString); tenant.SetDefaultConnectionString(input.DefaultConnectionString); } @@ -87,6 +91,7 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService { foreach (var connectionString in input.ConnectionStrings) { + await CheckConnectionString(connectionString.Value, connectionString.Key); tenant.SetConnectionString(connectionString.Key, connectionString.Value); } } @@ -119,6 +124,7 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService await EventBus.PublishAsync(eto); }); + await CurrentUnitOfWork.SaveChangesAsync(); return ObjectMapper.Map(tenant); @@ -157,12 +163,17 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService } // 租户删除时查询会失效, 在删除前确认 - var strategy = await FeatureChecker.GetAsync(SaasFeatureNames.Tenant.RecycleStrategy, RecycleStrategy.Recycle); + var recycleStrategy = RecycleStrategy.Recycle; + var strategySet = await FeatureChecker.GetOrNullAsync(SaasFeatureNames.Tenant.RecycleStrategy); + if (!strategySet.IsNullOrWhiteSpace() && Enum.TryParse(strategySet, out var strategy)) + { + recycleStrategy = strategy; + } var eto = new TenantDeletedEto { Id = tenant.Id, Name = tenant.Name, - Strategy = strategy, + Strategy = recycleStrategy, EntityVersion = tenant.EntityVersion, DefaultConnectionString = tenant.FindDefaultConnectionString(), }; @@ -259,4 +270,34 @@ public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService await CurrentUnitOfWork.SaveChangesAsync(); } + + protected async virtual Task CheckConnectionString(string connectionString, string name = null) + { + try + { + var checkResult = await ConnectionStringChecker.CheckAsync(connectionString); + // 检查连接是否可用 + if (!checkResult.Connected) + { + throw name.IsNullOrWhiteSpace() + ? new BusinessException(AbpSaasErrorCodes.InvalidDefaultConnectionString) + : new BusinessException(AbpSaasErrorCodes.InvalidConnectionString) + .WithData("Name", name); + } + // 默认连接字符串改变不能影响到现有数据库 + if (checkResult.DatabaseExists && name.IsNullOrWhiteSpace()) + { + throw new BusinessException(AbpSaasErrorCodes.DefaultConnectionStringDatabaseExists); + } + } + catch (Exception e) + { + Logger.LogWarning("An error occurred while checking the validity of the connection string"); + Logger.LogWarning(e.Message); + throw name.IsNullOrWhiteSpace() + ? new BusinessException(AbpSaasErrorCodes.InvalidDefaultConnectionString) + : new BusinessException(AbpSaasErrorCodes.InvalidConnectionString) + .WithData("Name", name); + } + } } diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/AbpSaasErrorCodes.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/AbpSaasErrorCodes.cs index ec33cb29b..851e4642d 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/AbpSaasErrorCodes.cs +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/AbpSaasErrorCodes.cs @@ -7,4 +7,16 @@ public static class AbpSaasErrorCodes public const string DuplicateEditionDisplayName = Namespace + ":010001"; public const string DeleteUsedEdition = Namespace + ":010002"; public const string DuplicateTenantName = Namespace + ":020001"; + /// + /// 无效的默认连接字符串 + /// + public const string InvalidDefaultConnectionString = Namespace + ":020101"; + /// + /// 默认连接字符串指向的数据库已经存在 + /// + public const string DefaultConnectionStringDatabaseExists = Namespace + ":020102"; + /// + /// {Name} 的连接字符串无效 + /// + public const string InvalidConnectionString = Namespace + ":020103"; } diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json index 4054a2cb5..1f6f1d75c 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/en.json @@ -4,7 +4,9 @@ "Saas:010001": "Unable to create duplicate editions {DisplayName}!", "Saas:010002": "Tried to delete the edition in use: {DisplayName}!", "Saas:020001": "Unable to create duplicate tenants {Name}!", - "Saas:020002": "The database string that cannot be connected!", + "Saas:020101": "The default connection string cannot open a connection to the database!", + "Saas:020102": "The database pointed to by the default connection string already exists!", + "Saas:020103": "Unable to open the database connection pointed to by {Name}!", "Volo.AbpIo.MultiTenancy:010001": "The tenant is unavailable or restricted!", "Volo.AbpIo.MultiTenancy:010002": "Tenant unavailable!", "Menu:Saas": "Saas", diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json index cce383bb1..4f974c1ef 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain.Shared/LINGYUN/Abp/Saas/Localization/Resources/zh-Hans.json @@ -4,7 +4,9 @@ "Saas:010001": "已经存在名为 {DisplayName} 的版本!", "Saas:010002": "试图删除正在使用的版本: {DisplayName}!", "Saas:020001": "已经存在名为 {Name} 的租户!", - "Saas:020002": "无法连接的数据库字符串!", + "Saas:020101": "无法打开默认连接字符串指向的数据库连接!", + "Saas:020102": "默认连接字符串指向的数据库已经存在!", + "Saas:020103": "无法打开 {Name} 指向的数据库连接!", "Volo.AbpIo.MultiTenancy:010001": "租户不可用或受限制!", "Volo.AbpIo.MultiTenancy:010002": "租户不可用!", "Menu:Saas": "Saas", diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Features/SaasFeatureDefinitionProvider.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Features/SaasFeatureDefinitionProvider.cs index 62f036f69..4e3a508d4 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Features/SaasFeatureDefinitionProvider.cs +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Features/SaasFeatureDefinitionProvider.cs @@ -18,22 +18,22 @@ public class SaasFeatureDefinitionProvider : FeatureDefinitionProvider ItemSource = new StaticSelectionStringValueItemSource( new LocalizableSelectionStringValueItem { - Value = RecycleStrategy.Reserve.ToString(), + Value = "0", DisplayText = new LocalizableStringInfo( - LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), - "RecycleStrategy:Reserve") + LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), + "RecycleStrategy:Reserve") }, new LocalizableSelectionStringValueItem { - Value = RecycleStrategy.Recycle.ToString(), + Value = "1", DisplayText = new LocalizableStringInfo( - LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), - "RecycleStrategy:Recycle") + LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), + "RecycleStrategy:Recycle") }) }; saas.AddFeature( name: SaasFeatureNames.Tenant.RecycleStrategy, - defaultValue: RecycleStrategy.Recycle.ToString(), + defaultValue: "1", displayName: L("Features:RecycleStrategy"), description: L("Features:RecycleStrategyDesc"), valueType: selectionValueType,