committed by
GitHub
60 changed files with 847 additions and 346 deletions
@ -0,0 +1,15 @@ |
|||
@echo off |
|||
cls |
|||
|
|||
call .\migrate-db-cmd.bat LY.MicroService.Platform.EntityFrameworkCore platform --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.BackendAdmin.EntityFrameworkCore admin --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.AuthServer.EntityFrameworkCore auth-server --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.IdentityServer.EntityFrameworkCore identityserver4-admin --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.LocalizationManagement.EntityFrameworkCore localization --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.RealtimeMessage.EntityFrameworkCore messages --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.TaskManagement.EntityFrameworkCore task-management --ef-u |
|||
call .\migrate-db-cmd.bat LY.MicroService.WebhooksManagement.EntityFrameworkCore webhooks-management --ef-u |
|||
|
|||
taskkill /IM dotnet.exe /F |
|||
|
|||
pause |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -1,12 +1,14 @@ |
|||
@echo off |
|||
cls |
|||
|
|||
call .\migrate-db-cmd.bat LY.MicroService.BackendAdmin.DbMigrator admin --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.Platform.DbMigrator platform --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.AuthServer.DbMigrator auth-server --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.IdentityServer.DbMigrator identityserver4-admin --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.LocalizationManagement.DbMigrator localization --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.Platform.DbMigrator platform --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.RealtimeMessage.DbMigrator messages --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.TaskManagement.DbMigrator task-management --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.WebhooksManagement.DbMigrator webhooks-management --run |
|||
call .\migrate-db-cmd.bat LY.MicroService.BackendAdmin.DbMigrator admin --run |
|||
|
|||
|
|||
pause |
|||
@ -0,0 +1,46 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.PermissionManagement; |
|||
|
|||
namespace LY.MicroService.BackendAdmin.EntityFrameworkCore; |
|||
|
|||
public class RolePermissionDataSeedContributor : IDataSeedContributor |
|||
{ |
|||
public ILogger<RolePermissionDataSeedContributor> Logger { protected get; set; } |
|||
|
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IPermissionDataSeeder PermissionDataSeeder { get; } |
|||
protected IPermissionDefinitionManager PermissionDefinitionManager { get; } |
|||
|
|||
public RolePermissionDataSeedContributor( |
|||
ICurrentTenant currentTenant, |
|||
IPermissionDataSeeder permissionDataSeeder, |
|||
IPermissionDefinitionManager permissionDefinitionManager) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
PermissionDataSeeder = permissionDataSeeder; |
|||
PermissionDefinitionManager = permissionDefinitionManager; |
|||
|
|||
Logger = NullLogger<RolePermissionDataSeedContributor>.Instance; |
|||
} |
|||
|
|||
public async virtual Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
using (CurrentTenant.Change(context.TenantId)) |
|||
{ |
|||
Logger.LogInformation("Seeding the new tenant admin role permissions..."); |
|||
|
|||
var definitionPermissions = await PermissionDefinitionManager.GetPermissionsAsync(); |
|||
await PermissionDataSeeder.SeedAsync( |
|||
RolePermissionValueProvider.ProviderName, |
|||
"admin", |
|||
definitionPermissions.Select(x => x.Name), |
|||
context.TenantId); |
|||
} |
|||
} |
|||
} |
|||
@ -1,40 +1,89 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using LINGYUN.Abp.Saas.Features; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Data.Common; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore.Migrations; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LY.MicroService.Platform.EntityFrameworkCore; |
|||
public class PlatformDbMigrationEventHandler : EfCoreDatabaseMigrationEventHandlerBase<PlatformMigrationsDbContext> |
|||
public class PlatformDbMigrationEventHandler : |
|||
EfCoreDatabaseMigrationEventHandlerBase<PlatformMigrationsDbContext>, |
|||
IDistributedEventHandler<TenantDeletedEto> |
|||
{ |
|||
protected IDataSeeder DataSeeder { get; } |
|||
|
|||
protected IFeatureChecker FeatureChecker { get; } |
|||
protected IConfiguration Configuration { get; } |
|||
public PlatformDbMigrationEventHandler( |
|||
ICurrentTenant currentTenant, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
ITenantStore tenantStore, |
|||
IDistributedEventBus distributedEventBus, |
|||
ILoggerFactory loggerFactory, |
|||
IDataSeeder dataSeeder) |
|||
IDataSeeder dataSeeder, |
|||
IFeatureChecker featureChecker, |
|||
IConfiguration configuration) |
|||
: base( |
|||
ConnectionStringNameAttribute.GetConnStringName<PlatformMigrationsDbContext>(), |
|||
currentTenant, unitOfWorkManager, tenantStore, distributedEventBus, loggerFactory) |
|||
{ |
|||
DataSeeder = dataSeeder; |
|||
FeatureChecker = featureChecker; |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected async override Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated) |
|||
protected async override Task SeedAsync(Guid? tenantId) |
|||
{ |
|||
if (!schemaMigrated) |
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
return; |
|||
await DataSeeder.SeedAsync(tenantId); |
|||
} |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(TenantDeletedEto eventData) |
|||
{ |
|||
var hostDefaultConnectionString = Configuration.GetConnectionString(ConnectionStrings.DefaultConnectionStringName); |
|||
using (CurrentTenant.Change(eventData.Id)) |
|||
{ |
|||
await DataSeeder.SeedAsync(eventData.Id); |
|||
// 租户删除时的资源回收策略
|
|||
var strategyFeature = await FeatureChecker.GetOrNullAsync(SaasFeatureNames.Tenant.RecycleStrategy); |
|||
if (!strategyFeature.IsNullOrWhiteSpace() && Enum.TryParse<RecycleStrategy>(strategyFeature, out var strategy)) |
|||
{ |
|||
// 需要回收策略为回收且存在默认连接字符串且默认连接字符串与宿主不同
|
|||
if (strategy == RecycleStrategy.Recycle && !eventData.DefaultConnectionString.IsNullOrWhiteSpace()) |
|||
{ |
|||
var hostConnection = new DbConnectionStringBuilder() |
|||
{ |
|||
ConnectionString = hostDefaultConnectionString, |
|||
}; |
|||
var tenantConnection = new DbConnectionStringBuilder() |
|||
{ |
|||
ConnectionString = eventData.DefaultConnectionString, |
|||
}; |
|||
if (hostConnection.EquivalentTo(tenantConnection)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
using var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true); |
|||
var buildr = new DbContextOptionsBuilder(); |
|||
buildr.UseMySql(eventData.DefaultConnectionString, ServerVersion.AutoDetect(eventData.DefaultConnectionString)); |
|||
await using var dbConnection = new DbContext(buildr.Options); |
|||
if ((await dbConnection.Database.GetAppliedMigrationsAsync()).Any()) |
|||
{ |
|||
await dbConnection.Database.EnsureDeletedAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
/// <summary>
|
|||
/// 资源回收策略
|
|||
/// </summary>
|
|||
public enum RecycleStrategy |
|||
{ |
|||
/// <summary>
|
|||
/// 保留
|
|||
/// </summary>
|
|||
Reserve, |
|||
/// <summary>
|
|||
/// 回收
|
|||
/// </summary>
|
|||
Recycle, |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using Volo.Abp.EventBus; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
[Serializable] |
|||
[EventName("abp.saas.tenant.deleted")] |
|||
public class TenantDeletedEto : TenantEto |
|||
{ |
|||
public string DefaultConnectionString { get; set; } |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Features; |
|||
public class SaasFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var saas = context.AddGroup( |
|||
name: SaasFeatureNames.GroupName, |
|||
displayName: L("Features:Saas")); |
|||
|
|||
var selectionValueType = new SelectionStringValueType |
|||
{ |
|||
ItemSource = new StaticSelectionStringValueItemSource( |
|||
new LocalizableSelectionStringValueItem |
|||
{ |
|||
Value = RecycleStrategy.Reserve.ToString(), |
|||
DisplayText = new LocalizableStringInfo( |
|||
LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), |
|||
"RecycleStrategy:Reserve") |
|||
}, |
|||
new LocalizableSelectionStringValueItem |
|||
{ |
|||
Value = RecycleStrategy.Recycle.ToString(), |
|||
DisplayText = new LocalizableStringInfo( |
|||
LocalizationResourceNameAttribute.GetName(typeof(AbpSaasResource)), |
|||
"RecycleStrategy:Recycle") |
|||
}) |
|||
}; |
|||
saas.AddFeature( |
|||
name: SaasFeatureNames.Tenant.RecycleStrategy, |
|||
defaultValue: RecycleStrategy.Recycle.ToString(), |
|||
displayName: L("Features:RecycleStrategy"), |
|||
description: L("Features:RecycleStrategyDesc"), |
|||
valueType: selectionValueType, |
|||
isAvailableToHost: false); |
|||
saas.AddFeature( |
|||
name: SaasFeatureNames.Tenant.ExpirationReminderDays, |
|||
defaultValue: 15.ToString(), |
|||
displayName: L("Features:ExpirationReminderDays"), |
|||
description: L("Features:ExpirationReminderDaysDesc"), |
|||
valueType: new ToggleStringValueType(new NumericValueValidator(1, 30)), |
|||
isAvailableToHost: false); |
|||
saas.AddFeature( |
|||
name: SaasFeatureNames.Tenant.ExpiredRecoveryTime, |
|||
defaultValue: 15.ToString(), |
|||
displayName: L("Features:ExpiredRecoveryTime"), |
|||
description: L("Features:ExpiredRecoveryTimeDesc"), |
|||
valueType: new ToggleStringValueType(new NumericValueValidator(1, 30)), |
|||
isAvailableToHost: false); |
|||
} |
|||
|
|||
protected ILocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<AbpSaasResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
namespace LINGYUN.Abp.Saas.Features; |
|||
public static class SaasFeatureNames |
|||
{ |
|||
public const string GroupName = "AbpSaas"; |
|||
|
|||
public static class Tenant |
|||
{ |
|||
public const string Default = GroupName + ".Tenants"; |
|||
/// <summary>
|
|||
/// 资源回收策略
|
|||
/// </summary>
|
|||
public const string RecycleStrategy = Default + ".RecycleStrategy"; |
|||
/// <summary>
|
|||
/// 过期回收时长
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 当资源快过期时, 需要邮件通知对应租户管理员, 超期多久没有处理, 将回收租户资源
|
|||
/// </remarks>
|
|||
public const string ExpiredRecoveryTime = Default + ".ExpiredRecoveryTime"; |
|||
/// <summary>
|
|||
/// 过期预警天数
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 当资源还有多少天过期时, 给管理员发送提醒
|
|||
/// </remarks>
|
|||
public const string ExpirationReminderDays = Default + ".ExpirationReminderDays"; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\Saas\Jobs\Localization\Resources\en.json" /> |
|||
<None Remove="LINGYUN\Abp\Saas\Jobs\Localization\Resources\zh-Hans.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Saas\Jobs\Localization\Resources\en.json" /> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Saas\Jobs\Localization\Resources\zh-Hans.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Emailing" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\task-management\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,29 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Jobs; |
|||
|
|||
[DependsOn(typeof(AbpEmailingModule))] |
|||
[DependsOn(typeof(AbpSaasDomainModule))] |
|||
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] |
|||
public class AbpSaasJobsModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpSaasJobsModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpSaasResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/Saas/Jobs/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Jobs; |
|||
|
|||
internal static class LocalizableStatic |
|||
{ |
|||
public static ILocalizableString Create(string name) |
|||
{ |
|||
return LocalizableString.Create<AbpSaasResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"TenantUsageMonitoringJob": "Tenant Usage Monitoring Job", |
|||
"Saas:AdminEmail": "Admin Email", |
|||
"Saas:TenantId": "Tenant Id" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"TenantUsageMonitoringJob": "租户使用情况监听作业", |
|||
"Saas:AdminEmail": "管理员邮件地址", |
|||
"Saas:TenantId": "租户标识" |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Jobs; |
|||
public class SaasJobDefinitionProvider : JobDefinitionProvider |
|||
{ |
|||
public override void Define(IJobDefinitionContext context) |
|||
{ |
|||
context.Add(new JobDefinition( |
|||
"TenantUsageMonitoringJob", |
|||
typeof(TenantUsageMonitoringJob), |
|||
LocalizableStatic.Create("TenantUsageMonitoringJob"), |
|||
TenantUsageMonitoringJob.Paramters)); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Abp.Saas.Features; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Timing; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Jobs; |
|||
public class TenantUsageMonitoringJob : IJobRunnable |
|||
{ |
|||
#region Definition Paramters
|
|||
|
|||
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters = |
|||
new List<JobDefinitionParamter> |
|||
{ |
|||
new JobDefinitionParamter(PropertyAdminEmail, LocalizableStatic.Create("Saas:AdminEmail"), required: true), |
|||
new JobDefinitionParamter(PropertyTenantId, LocalizableStatic.Create("Saas:TenantId"), required: true), |
|||
}; |
|||
|
|||
#endregion
|
|||
|
|||
public const string PropertyAdminEmail = "AdminEmail"; |
|||
public const string PropertyTenantId = "TenantId"; |
|||
|
|||
public async virtual Task ExecuteAsync(JobRunnableContext context) |
|||
{ |
|||
var clock = context.GetRequiredService<IClock>(); |
|||
var currentTenant = context.GetRequiredService<ICurrentTenant>(); |
|||
var repository = context.GetRequiredService<ITenantRepository>(); |
|||
var featureChecker = context.GetRequiredService<IFeatureChecker>(); |
|||
using (currentTenant.Change(null)) |
|||
{ |
|||
var tenantId = context.GetJobData<Guid>(PropertyTenantId); |
|||
// TODO: 租户已删除需要取消作业运行
|
|||
var tenant = await repository.GetAsync(tenantId); |
|||
if (!tenant.DisableTime.HasValue) |
|||
{ |
|||
return; |
|||
} |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var allowExpirationDays = await featureChecker.GetAsync(SaasFeatureNames.Tenant.ExpirationReminderDays, 15); |
|||
if (tenant.DisableTime <= clock.Now.AddDays(allowExpirationDays)) |
|||
{ |
|||
var adminEmail = context.GetString(PropertyAdminEmail); |
|||
var emailSender = context.GetRequiredService<IEmailSender>(); |
|||
// TODO: 需要使用模板发送
|
|||
await emailSender.SendAsync(adminEmail, "资源超时预警", "您好, 您的平台资源已到超时预警时间, 过期资源将会被回收, 且无法恢复, 请注意及时延长使用时间或对资源备份!"); |
|||
return; |
|||
} |
|||
|
|||
var expiredRecoveryTime = await featureChecker.GetAsync(SaasFeatureNames.Tenant.ExpiredRecoveryTime, 15); |
|||
if (clock.Now > tenant.DisableTime && clock.Now.Subtract(TimeSpan.FromDays(expiredRecoveryTime)) <= tenant.DisableTime) |
|||
{ |
|||
// 租户已过期且达到最大宽限时间, 删除租户
|
|||
await repository.DeleteAsync(tenant); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
@echo off |
|||
cd ..\aspnet-core |
|||
create-database.bat |
|||
|
|||
Loading…
Reference in new issue