committed by
GitHub
164 changed files with 4683 additions and 187 deletions
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Authorization.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain.Shared\LINGYUN.Abp.Saas.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Volo.Abp.Application; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Authorization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasDomainSharedModule))] |
||||
|
[DependsOn(typeof(AbpAuthorizationAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpDddApplicationContractsModule))] |
||||
|
public class AbpSaasApplicationContractsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public class AbpSaasRemoteServiceConsts |
||||
|
{ |
||||
|
public const string RemoteServiceName = "AbpSaas"; |
||||
|
|
||||
|
public const string ModuleName = "saas"; |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionCreateDto : EditionCreateOrUpdateBase |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public abstract class EditionCreateOrUpdateBase : ExtensibleObject |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(EditionConsts), nameof(EditionConsts.MaxDisplayNameLength))] |
||||
|
[Display(Name = "EditionName")] |
||||
|
public string DisplayName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionDto : ExtensibleFullAuditedEntityDto<Guid> |
||||
|
{ |
||||
|
public string DisplayName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionGetListInput : PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionUpdateDto : EditionCreateOrUpdateBase, IHasConcurrencyStamp |
||||
|
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public interface IEditionAppService : |
||||
|
ICrudAppService< |
||||
|
EditionDto, |
||||
|
Guid, |
||||
|
EditionGetListInput, |
||||
|
EditionCreateDto, |
||||
|
EditionUpdateDto> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using LINGYUN.Abp.Saas.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public class AbpSaasPermissionDefinitionProvider : PermissionDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IPermissionDefinitionContext context) |
||||
|
{ |
||||
|
var saasGroup = context.AddGroup(AbpSaasPermissions.GroupName, L("Permission:Saas")); |
||||
|
|
||||
|
var editionsPermission = saasGroup.AddPermission(AbpSaasPermissions.Editions.Default, L("Permission:EditionManagement"), multiTenancySide: MultiTenancySides.Host); |
||||
|
editionsPermission.AddChild(AbpSaasPermissions.Editions.Create, L("Permission:Create"), multiTenancySide: MultiTenancySides.Host); |
||||
|
editionsPermission.AddChild(AbpSaasPermissions.Editions.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host); |
||||
|
editionsPermission.AddChild(AbpSaasPermissions.Editions.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host); |
||||
|
editionsPermission.AddChild(AbpSaasPermissions.Editions.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host); |
||||
|
|
||||
|
var tenantsPermission = saasGroup.AddPermission(AbpSaasPermissions.Tenants.Default, L("Permission:TenantManagement"), multiTenancySide: MultiTenancySides.Host); |
||||
|
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Create, L("Permission:Create"), multiTenancySide: MultiTenancySides.Host); |
||||
|
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host); |
||||
|
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host); |
||||
|
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host); |
||||
|
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageConnectionStrings, L("Permission:ManageConnectionStrings"), multiTenancySide: MultiTenancySides.Host); |
||||
|
} |
||||
|
|
||||
|
private static LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<AbpSaasResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public static class AbpSaasPermissions |
||||
|
{ |
||||
|
public const string GroupName = "AbpSaas"; |
||||
|
|
||||
|
public static class Editions |
||||
|
{ |
||||
|
public const string Default = GroupName + ".Editions"; |
||||
|
public const string Create = Default + ".Create"; |
||||
|
public const string Update = Default + ".Update"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
public const string ManageFeatures = Default + ".ManageFeatures"; |
||||
|
} |
||||
|
|
||||
|
public static class Tenants |
||||
|
{ |
||||
|
public const string Default = GroupName + ".Tenants"; |
||||
|
public const string Create = Default + ".Create"; |
||||
|
public const string Update = Default + ".Update"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
public const string ManageFeatures = Default + ".ManageFeatures"; |
||||
|
public const string ManageConnectionStrings = Default + ".ManageConnectionStrings"; |
||||
|
} |
||||
|
|
||||
|
public static string[] GetAll() |
||||
|
{ |
||||
|
return ReflectionHelper.GetPublicConstantsRecursively(typeof(AbpSaasPermissions)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantConnectionGetByNameInput |
||||
|
{ |
||||
|
[Required] |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxNameLength))] |
||||
|
public string Name { get; set; } |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantConnectionStringCreateOrUpdate |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxNameLength))] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxValueLength))] |
||||
|
public string Value { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantConnectionStringDto |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Value { get; set; } |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantCreateDto : TenantCreateOrUpdateBase |
||||
|
{ |
||||
|
[Required] |
||||
|
[EmailAddress] |
||||
|
[MaxLength(256)] |
||||
|
public string AdminEmailAddress { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[MaxLength(128)] |
||||
|
public string AdminPassword { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 使用共享数据库
|
||||
|
/// </summary>
|
||||
|
public bool UseSharedDatabase { get; set; } = true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 默认数据库连接字符串
|
||||
|
/// </summary>
|
||||
|
public string DefaultConnectionString { get; set; } |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public abstract class TenantCreateOrUpdateBase : ExtensibleObject |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsActive { get; set; } = true; |
||||
|
|
||||
|
public Guid? EditionId { get; set; } |
||||
|
|
||||
|
public DateTime? EnableTime { get; set; } |
||||
|
|
||||
|
public DateTime? DisableTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantDto : ExtensibleFullAuditedEntityDto<Guid> |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public Guid? EditionId { get; set; } |
||||
|
|
||||
|
public string EditionName { get; set; } |
||||
|
|
||||
|
public bool IsActive { get; set; } |
||||
|
|
||||
|
public DateTime? EnableTime { get; set; } |
||||
|
|
||||
|
public DateTime? DisableTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantGetByNameInput |
||||
|
{ |
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public TenantGetByNameInput() { } |
||||
|
public TenantGetByNameInput(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantGetListInput : PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
public class TenantUpdateDto : TenantCreateOrUpdateBase, IHasConcurrencyStamp |
||||
|
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public interface ITenantAppService : |
||||
|
ICrudAppService< |
||||
|
TenantDto, |
||||
|
Guid, |
||||
|
TenantGetListInput, |
||||
|
TenantCreateDto, |
||||
|
TenantUpdateDto> |
||||
|
{ |
||||
|
Task<TenantDto> GetAsync( |
||||
|
[Required] string name); |
||||
|
|
||||
|
Task<TenantConnectionStringDto> GetConnectionStringAsync( |
||||
|
Guid id, |
||||
|
[Required] string connectionName); |
||||
|
|
||||
|
Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync( |
||||
|
Guid id); |
||||
|
|
||||
|
Task<TenantConnectionStringDto> SetConnectionStringAsync( |
||||
|
Guid id, |
||||
|
TenantConnectionStringCreateOrUpdate input); |
||||
|
|
||||
|
Task DeleteConnectionStringAsync( |
||||
|
Guid id, |
||||
|
[Required] string connectionName); |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.Saas.Localization; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
public abstract class AbpSaasAppServiceBase : ApplicationService |
||||
|
{ |
||||
|
protected AbpSaasAppServiceBase() |
||||
|
{ |
||||
|
ObjectMapperContext = typeof(AbpSaasApplicationModule); |
||||
|
LocalizationResource = typeof(AbpSaasResource); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using AutoMapper; |
||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public class AbpSaasApplicationAutoMapperProfile : Profile |
||||
|
{ |
||||
|
public AbpSaasApplicationAutoMapperProfile() |
||||
|
{ |
||||
|
CreateMap<TenantConnectionString, TenantConnectionStringDto>(); |
||||
|
|
||||
|
CreateMap<Tenant, TenantDto>() |
||||
|
.ForMember(dto => dto.EditionId, map => |
||||
|
{ |
||||
|
map.MapFrom((tenant, dto) => |
||||
|
{ |
||||
|
return tenant.Edition?.Id; |
||||
|
}); |
||||
|
}) |
||||
|
.ForMember(dto => dto.EditionName, map => |
||||
|
{ |
||||
|
map.MapFrom((tenant, dto) => |
||||
|
{ |
||||
|
return tenant.Edition?.DisplayName; |
||||
|
}); |
||||
|
}) |
||||
|
.MapExtraProperties(); |
||||
|
|
||||
|
CreateMap<Edition, EditionDto>() |
||||
|
.MapExtraProperties(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasDomainModule))] |
||||
|
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
||||
|
public class AbpSaasApplicationModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<AbpSaasApplicationModule>(); |
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<AbpSaasApplicationAutoMapperProfile>(validate: true); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Editions.Default)] |
||||
|
public class EditionAppService : AbpSaasAppServiceBase, IEditionAppService |
||||
|
{ |
||||
|
protected EditionManager EditionManager { get; } |
||||
|
protected IEditionRepository EditionRepository { get; } |
||||
|
|
||||
|
public EditionAppService( |
||||
|
EditionManager editionManager, |
||||
|
IEditionRepository editionRepository) |
||||
|
{ |
||||
|
EditionManager = editionManager; |
||||
|
EditionRepository = editionRepository; |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Editions.Create)] |
||||
|
public async virtual Task<EditionDto> CreateAsync(EditionCreateDto input) |
||||
|
{ |
||||
|
var edition = await EditionManager.CreateAsync(input.DisplayName); |
||||
|
input.MapExtraPropertiesTo(edition); |
||||
|
|
||||
|
await EditionRepository.InsertAsync(edition); |
||||
|
|
||||
|
await CurrentUnitOfWork.SaveChangesAsync(); |
||||
|
|
||||
|
return ObjectMapper.Map<Edition, EditionDto>(edition); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Editions.Delete)] |
||||
|
public async virtual Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
var edition = await EditionRepository.GetAsync(id); |
||||
|
|
||||
|
await EditionManager.DeleteAsync(edition); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<EditionDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
var edition = await EditionRepository.GetAsync(id, false); |
||||
|
|
||||
|
return ObjectMapper.Map<Edition, EditionDto>(edition); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<PagedResultDto<EditionDto>> GetListAsync(EditionGetListInput input) |
||||
|
{ |
||||
|
var totalCount = await EditionRepository.GetCountAsync(input.Filter); |
||||
|
var editions = await EditionRepository.GetListAsync( |
||||
|
input.Sorting, |
||||
|
input.MaxResultCount, |
||||
|
input.SkipCount, |
||||
|
input.Filter |
||||
|
); |
||||
|
|
||||
|
return new PagedResultDto<EditionDto>( |
||||
|
totalCount, |
||||
|
ObjectMapper.Map<List<Edition>, List<EditionDto>>(editions) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Editions.Update)] |
||||
|
public async virtual Task<EditionDto> UpdateAsync(Guid id, EditionUpdateDto input) |
||||
|
{ |
||||
|
var edition = await EditionRepository.GetAsync(id, false); |
||||
|
|
||||
|
if (!string.Equals(edition.DisplayName, input.DisplayName)) |
||||
|
{ |
||||
|
await EditionManager.ChangeDisplayNameAsync(edition, input.DisplayName); |
||||
|
} |
||||
|
edition.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
||||
|
input.MapExtraPropertiesTo(edition); |
||||
|
|
||||
|
await EditionRepository.UpdateAsync(edition); |
||||
|
|
||||
|
await CurrentUnitOfWork.SaveChangesAsync(); |
||||
|
|
||||
|
return ObjectMapper.Map<Edition, EditionDto>(edition); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
using LINGYUN.Abp.MultiTenancy; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.Default)] |
||||
|
public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService |
||||
|
{ |
||||
|
protected IDistributedEventBus EventBus { get; } |
||||
|
protected ITenantRepository TenantRepository { get; } |
||||
|
protected ITenantManager TenantManager { get; } |
||||
|
|
||||
|
public TenantAppService( |
||||
|
ITenantRepository tenantRepository, |
||||
|
ITenantManager tenantManager, |
||||
|
IDistributedEventBus eventBus) |
||||
|
{ |
||||
|
EventBus = eventBus; |
||||
|
TenantRepository = tenantRepository; |
||||
|
TenantManager = tenantManager; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TenantDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindAsync(id, false); |
||||
|
if (tenant == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException(L["TenantNotFoundById", id]); |
||||
|
} |
||||
|
|
||||
|
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TenantDto> GetAsync(string name) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindByNameAsync(name, false); |
||||
|
if (tenant == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException(L["TenantNotFoundByName", name]); |
||||
|
} |
||||
|
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<PagedResultDto<TenantDto>> GetListAsync(TenantGetListInput input) |
||||
|
{ |
||||
|
var count = await TenantRepository.GetCountAsync(input.Filter); |
||||
|
var list = await TenantRepository.GetListAsync( |
||||
|
input.Sorting, |
||||
|
input.MaxResultCount, |
||||
|
input.SkipCount, |
||||
|
input.Filter |
||||
|
); |
||||
|
|
||||
|
return new PagedResultDto<TenantDto>( |
||||
|
count, |
||||
|
ObjectMapper.Map<List<Tenant>, List<TenantDto>>(list) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.Create)] |
||||
|
public virtual async Task<TenantDto> CreateAsync(TenantCreateDto input) |
||||
|
{ |
||||
|
var tenant = await TenantManager.CreateAsync(input.Name); |
||||
|
tenant.IsActive = input.IsActive; |
||||
|
tenant.EditionId = input.EditionId; |
||||
|
tenant.SetEnableTime(input.EnableTime); |
||||
|
tenant.SetDisableTime(input.DisableTime); |
||||
|
input.MapExtraPropertiesTo(tenant); |
||||
|
|
||||
|
if (!input.UseSharedDatabase && !input.DefaultConnectionString.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
tenant.SetDefaultConnectionString(input.DefaultConnectionString); |
||||
|
} |
||||
|
|
||||
|
await TenantRepository.InsertAsync(tenant); |
||||
|
|
||||
|
CurrentUnitOfWork.OnCompleted(async () => |
||||
|
{ |
||||
|
var createEventData = new CreateEventData |
||||
|
{ |
||||
|
Id = tenant.Id, |
||||
|
Name = tenant.Name, |
||||
|
AdminUserId = GuidGenerator.Create(), |
||||
|
AdminEmailAddress = input.AdminEmailAddress, |
||||
|
AdminPassword = input.AdminPassword |
||||
|
}; |
||||
|
// 因为项目各自独立,租户增加时添加管理用户必须通过事件总线
|
||||
|
// 而 TenantEto 对象没有包含所需的用户名密码,需要独立发布事件
|
||||
|
await EventBus.PublishAsync(createEventData); |
||||
|
}); |
||||
|
|
||||
|
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.Update)] |
||||
|
public virtual async Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.GetAsync(id, false); |
||||
|
|
||||
|
if (!string.Equals(tenant.Name, input.Name)) |
||||
|
{ |
||||
|
await TenantManager.ChangeNameAsync(tenant, input.Name); |
||||
|
} |
||||
|
|
||||
|
tenant.IsActive = input.IsActive; |
||||
|
tenant.EditionId = input.EditionId; |
||||
|
tenant.SetEnableTime(input.EnableTime); |
||||
|
tenant.SetDisableTime(input.DisableTime); |
||||
|
tenant.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
||||
|
input.MapExtraPropertiesTo(tenant); |
||||
|
await TenantRepository.UpdateAsync(tenant); |
||||
|
|
||||
|
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.Delete)] |
||||
|
public virtual async Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindAsync(id); |
||||
|
if (tenant == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
await TenantRepository.DeleteAsync(tenant); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual async Task<TenantConnectionStringDto> GetConnectionStringAsync(Guid id, string name) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.GetAsync(id); |
||||
|
|
||||
|
var tenantConnectionString = tenant.FindConnectionString(name); |
||||
|
|
||||
|
return new TenantConnectionStringDto |
||||
|
{ |
||||
|
Name = name, |
||||
|
Value = tenantConnectionString |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual async Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync(Guid id) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.GetAsync(id); |
||||
|
|
||||
|
return new ListResultDto<TenantConnectionStringDto>( |
||||
|
ObjectMapper.Map<List<TenantConnectionString>, List<TenantConnectionStringDto>>(tenant.ConnectionStrings.ToList())); |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual async Task<TenantConnectionStringDto> SetConnectionStringAsync(Guid id, TenantConnectionStringCreateOrUpdate input) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.GetAsync(id); |
||||
|
if (tenant.FindConnectionString(input.Name) == null) |
||||
|
{ |
||||
|
CurrentUnitOfWork.OnCompleted(async () => |
||||
|
{ |
||||
|
var eventData = new ConnectionStringCreatedEventData |
||||
|
{ |
||||
|
TenantId = tenant.Id, |
||||
|
TenantName = tenant.Name, |
||||
|
Name = input.Name |
||||
|
}; |
||||
|
|
||||
|
await EventBus.PublishAsync(eventData); |
||||
|
}); |
||||
|
} |
||||
|
tenant.SetConnectionString(input.Name, input.Value); |
||||
|
|
||||
|
return new TenantConnectionStringDto |
||||
|
{ |
||||
|
Name = input.Name, |
||||
|
Value = input.Value |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual async Task DeleteConnectionStringAsync(Guid id, string name) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.GetAsync(id); |
||||
|
|
||||
|
tenant.RemoveConnectionString(name); |
||||
|
|
||||
|
CurrentUnitOfWork.OnCompleted(async () => |
||||
|
{ |
||||
|
var eventData = new ConnectionStringDeletedEventData |
||||
|
{ |
||||
|
TenantId = tenant.Id, |
||||
|
TenantName = tenant.Name, |
||||
|
Name = name |
||||
|
}; |
||||
|
|
||||
|
await EventBus.PublishAsync(eventData); |
||||
|
}); |
||||
|
|
||||
|
await TenantRepository.UpdateAsync(tenant); |
||||
|
} |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\Saas\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\Saas\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Validation" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,34 @@ |
|||||
|
using LINGYUN.Abp.Saas.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Localization.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpValidationModule))] |
||||
|
public class AbpSaasDomainSharedModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpSaasDomainSharedModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<AbpSaasResource>("en") |
||||
|
.AddVirtualJson("/LINGYUN/Abp/Saas/Localization/Resources"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpExceptionLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.MapCodeNamespace(AbpSaasErrorCodes.Namespace, typeof(AbpSaasResource)); |
||||
|
// 见租户管理模块
|
||||
|
options.MapCodeNamespace("Volo.AbpIo.MultiTenancy", typeof(AbpSaasResource)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public static class AbpSaasErrorCodes |
||||
|
{ |
||||
|
public const string Namespace = "Saas"; |
||||
|
|
||||
|
public const string DuplicateEditionDisplayName = Namespace + ":010001"; |
||||
|
public const string DeleteUsedEdition = Namespace + ":010002"; |
||||
|
public const string DuplicateTenantName = Namespace + ":020001"; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public static class EditionConsts |
||||
|
{ |
||||
|
public static int MaxDisplayNameLength { get; set; } = 64; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class EditionEto |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("AbpSaas")] |
||||
|
public class AbpSaasResource |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"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!", |
||||
|
"Volo.AbpIo.MultiTenancy:010001": "The tenant is unavailable or restricted!", |
||||
|
"Volo.AbpIo.MultiTenancy:010002": "Tenant unavailable!", |
||||
|
"Menu:Saas": "Saas", |
||||
|
"Editions": "Editions", |
||||
|
"NewEdition": "New edition", |
||||
|
"EditionName": "Edition name", |
||||
|
"DisplayName:EditionName": "Edition name", |
||||
|
"EditionDeletionConfirmationMessage": "Edition '{0}' will be deleted. Do you confirm that?", |
||||
|
"Tenants": "Tenants", |
||||
|
"NewTenant": "New tenant", |
||||
|
"TenantName": "Tenant name", |
||||
|
"DisplayName:TenantName": "Tenant name", |
||||
|
"TenantDeletionConfirmationMessage": "Tenant '{0}' will be deleted. Do you confirm that?", |
||||
|
"ConnectionStrings": "Connection Strings", |
||||
|
"DisplayName:DefaultConnectionString": "Default Connection String", |
||||
|
"DisplayName:UseSharedDatabase": "Use the Shared Database", |
||||
|
"DisplayName:Name": "Name", |
||||
|
"DisplayName:Value": "Value", |
||||
|
"DisplayName:IsActive": "Active", |
||||
|
"DisplayName:EnableTime": "Enable Time", |
||||
|
"DisplayName:DisableTime": "Disable Time", |
||||
|
"ManageHostFeatures": "Manage Host features", |
||||
|
"Permission:Saas": "Saas", |
||||
|
"Permission:EditionManagement": "Edition management", |
||||
|
"Permission:TenantManagement": "Tenant management", |
||||
|
"Permission:Create": "Create", |
||||
|
"Permission:Edit": "Edit", |
||||
|
"Permission:Delete": "Delete", |
||||
|
"Permission:ManageConnectionStrings": "Manage connection strings", |
||||
|
"Permission:ManageFeatures": "Manage features", |
||||
|
"DisplayName:AdminEmailAddress": "Admin Email Address", |
||||
|
"DisplayName:AdminPassword": "Admin Password", |
||||
|
"TenantNotFoundById": "Tenant: {0} not found!", |
||||
|
"TenantNotFoundByName": "Tenant: {0} not found!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Saas:010001": "已经存在名为 {DisplayName} 的版本!", |
||||
|
"Saas:010002": "试图删除正在使用的版本: {DisplayName}!", |
||||
|
"Saas:020001": "已经存在名为 {Name} 的租户!", |
||||
|
"Saas:020002": "无法连接的数据库字符串!", |
||||
|
"Volo.AbpIo.MultiTenancy:010001": "租户不可用或受限制!", |
||||
|
"Volo.AbpIo.MultiTenancy:010002": "租户不可用!", |
||||
|
"Menu:Saas": "Saas", |
||||
|
"Editions": "版本", |
||||
|
"NewEdition": "新版本", |
||||
|
"EditionName": "版本名称", |
||||
|
"DisplayName:EditionName": "版本名称", |
||||
|
"EditionDeletionConfirmationMessage": "版本 '{0}' 将被删除. 你确定吗?", |
||||
|
"Tenants": "租户", |
||||
|
"NewTenant": "新租户", |
||||
|
"TenantName": "租户名称", |
||||
|
"DisplayName:TenantName": "租户名称", |
||||
|
"TenantDeletionConfirmationMessage": "租户 '{0}' 将被删除. 你确定吗?", |
||||
|
"ConnectionStrings": "连接字符串", |
||||
|
"DisplayName:DefaultConnectionString": "默认连接字符串", |
||||
|
"DisplayName:UseSharedDatabase": "使用共享数据库", |
||||
|
"DisplayName:Name": "名称", |
||||
|
"DisplayName:Value": "值", |
||||
|
"DisplayName:IsActive": "启用", |
||||
|
"DisplayName:EnableTime": "启用时间", |
||||
|
"DisplayName:DisableTime": "禁用时间", |
||||
|
"ManageHostFeatures": "管理Host特性", |
||||
|
"Permission:Saas": "Saas", |
||||
|
"Permission:EditionManagement": "版本管理", |
||||
|
"Permission:TenantManagement": "租户管理", |
||||
|
"Permission:Create": "创建", |
||||
|
"Permission:Edit": "编辑", |
||||
|
"Permission:Delete": "删除", |
||||
|
"Permission:ManageConnectionStrings": "管理连接字符串", |
||||
|
"Permission:ManageFeatures": "管理功能", |
||||
|
"DisplayName:AdminEmailAddress": "管理员电子邮件地址", |
||||
|
"DisplayName:AdminPassword": "管理员密码", |
||||
|
"TenantNotFoundById": "租户: {0} 不存在!", |
||||
|
"TenantNotFoundByName": "租户: {0} 不存在!" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.ObjectExtending.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.ObjectExtending; |
||||
|
|
||||
|
public class SaasModuleExtensionConfiguration : ModuleExtensionConfiguration |
||||
|
{ |
||||
|
public SaasModuleExtensionConfiguration ConfigureTenant( |
||||
|
Action<EntityExtensionConfiguration> configureAction) |
||||
|
{ |
||||
|
return this.ConfigureEntity( |
||||
|
SaasModuleExtensionConsts.EntityNames.Edition, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.ObjectExtending.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.ObjectExtending; |
||||
|
|
||||
|
public static class SaasModuleExtensionConfigurationDictionaryExtensions |
||||
|
{ |
||||
|
public static ModuleExtensionConfigurationDictionary ConfigureTenantManagement( |
||||
|
this ModuleExtensionConfigurationDictionary modules, |
||||
|
Action<SaasModuleExtensionConfiguration> configureAction) |
||||
|
{ |
||||
|
return modules.ConfigureModule( |
||||
|
SaasModuleExtensionConsts.ModuleName, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.ObjectExtending; |
||||
|
|
||||
|
public class SaasModuleExtensionConsts |
||||
|
{ |
||||
|
public const string ModuleName = "Saas"; |
||||
|
public static class EntityNames |
||||
|
{ |
||||
|
public const string Edition = "Edition"; |
||||
|
public const string Tenant = "Tenant"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public static class TenantConnectionStringConsts |
||||
|
{ |
||||
|
public static int MaxNameLength { get; set; } = 64; |
||||
|
public static int MaxValueLength { get; set; } = 1024; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public static class TenantConsts |
||||
|
{ |
||||
|
public static int MaxNameLength { get; set; } = 64; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class TenantEto |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\tenants\LINGYUN.Abp.MultiTenancy.Editions\LINGYUN.Abp.MultiTenancy.Editions.csproj" /> |
||||
|
<ProjectReference Include="..\..\tenants\LINGYUN.Abp.MultiTenancy\LINGYUN.Abp.MultiTenancy.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain.Shared\LINGYUN.Abp.Saas.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public class AbpSaasDbProperties |
||||
|
{ |
||||
|
public static string DbTablePrefix { get; set; } = AbpCommonDbProperties.DbTablePrefix; |
||||
|
|
||||
|
public static string DbSchema { get; set; } = AbpCommonDbProperties.DbSchema; |
||||
|
|
||||
|
public const string ConnectionStringName = "AbpSaas"; |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using AutoMapper; |
||||
|
using LINGYUN.Abp.MultiTenancy.Editions; |
||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using System; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
public class AbpSaasDomainMappingProfile : Profile |
||||
|
{ |
||||
|
public AbpSaasDomainMappingProfile() |
||||
|
{ |
||||
|
CreateMap<Edition, EditionInfo>(); |
||||
|
CreateMap<Edition, EditionEto>(); |
||||
|
|
||||
|
CreateMap<Tenant, TenantConfiguration>() |
||||
|
.ForMember(ti => ti.ConnectionStrings, opts => |
||||
|
{ |
||||
|
opts.MapFrom((tenant, ti) => |
||||
|
{ |
||||
|
var connStrings = new ConnectionStrings(); |
||||
|
|
||||
|
foreach (var connectionString in tenant.ConnectionStrings) |
||||
|
{ |
||||
|
connStrings[connectionString.Name] = connectionString.Value; |
||||
|
} |
||||
|
|
||||
|
return connStrings; |
||||
|
}); |
||||
|
}) |
||||
|
.ForMember(ti => ti.IsActive, opts => |
||||
|
{ |
||||
|
opts.MapFrom((tenant, ti) => |
||||
|
{ |
||||
|
if (!tenant.IsActive) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
// Injection IClock ?
|
||||
|
if (tenant.EnableTime.HasValue && DateTime.Now < tenant.EnableTime) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if(tenant.DisableTime.HasValue && DateTime.Now > tenant.DisableTime) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
CreateMap<Tenant, TenantEto>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using LINGYUN.Abp.MultiTenancy.Editions; |
||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.ObjectExtending; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Domain; |
||||
|
using Volo.Abp.Domain.Entities.Events.Distributed; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.ObjectExtending.Modularity; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasDomainSharedModule))] |
||||
|
[DependsOn(typeof(AbpAutoMapperModule))] |
||||
|
[DependsOn(typeof(AbpDddDomainModule))] |
||||
|
[DependsOn(typeof(AbpMultiTenancyEditionsModule))] |
||||
|
public class AbpSaasDomainModule : AbpModule |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new(); |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<AbpSaasDomainModule>(); |
||||
|
|
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<AbpSaasDomainMappingProfile>(validate: true); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpDistributedEntityEventOptions>(options => |
||||
|
{ |
||||
|
options.EtoMappings.Add<Edition, EditionEto>(); |
||||
|
options.EtoMappings.Add<Tenant, TenantEto>(); |
||||
|
|
||||
|
options.AutoEventSelectors.Add<Edition>(); |
||||
|
options.AutoEventSelectors.Add<Tenant>(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void PostConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( |
||||
|
SaasModuleExtensionConsts.ModuleName, |
||||
|
SaasModuleExtensionConsts.EntityNames.Edition, |
||||
|
typeof(Edition) |
||||
|
); |
||||
|
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( |
||||
|
SaasModuleExtensionConsts.ModuleName, |
||||
|
SaasModuleExtensionConsts.EntityNames.Tenant, |
||||
|
typeof(Tenant) |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class Edition : FullAuditedAggregateRoot<Guid> |
||||
|
{ |
||||
|
public virtual string DisplayName { get; protected set; } |
||||
|
|
||||
|
protected Edition() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected internal Edition(Guid id, [NotNull] string displayName) |
||||
|
: base(id) |
||||
|
{ |
||||
|
SetDisplayName(displayName); |
||||
|
} |
||||
|
|
||||
|
protected internal virtual void SetDisplayName([NotNull] string displayName) |
||||
|
{ |
||||
|
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), EditionConsts.MaxDisplayNameLength); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using LINGYUN.Abp.MultiTenancy.Editions; |
||||
|
using System; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
[Serializable] |
||||
|
[IgnoreMultiTenancy] |
||||
|
public class EditionCacheItem |
||||
|
{ |
||||
|
private const string CacheKeyFormat = "t:{0}"; |
||||
|
|
||||
|
public EditionInfo Value { get; set; } |
||||
|
|
||||
|
public EditionCacheItem() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public EditionCacheItem(EditionInfo value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public static string CalculateCacheKey(Guid tenantId) |
||||
|
{ |
||||
|
return string.Format(CacheKeyFormat, tenantId.ToString()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionDataSeeder : IEditionDataSeeder, ITransientDependency |
||||
|
{ |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected IEditionRepository EditionRepository { get; } |
||||
|
|
||||
|
public EditionDataSeeder( |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IEditionRepository editionRepository) |
||||
|
{ |
||||
|
GuidGenerator = guidGenerator; |
||||
|
EditionRepository = editionRepository; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task SeedDefaultEditionsAsync() |
||||
|
{ |
||||
|
await AddEditionIfNotExistsAsync("Free"); |
||||
|
await AddEditionIfNotExistsAsync("Standard"); |
||||
|
await AddEditionIfNotExistsAsync("Professional"); |
||||
|
await AddEditionIfNotExistsAsync("Enterprise"); |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task AddEditionIfNotExistsAsync(string displayName) |
||||
|
{ |
||||
|
if (await EditionRepository.FindByDisplayNameAsync(displayName) != null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await EditionRepository.InsertAsync( |
||||
|
new Edition( |
||||
|
GuidGenerator.Create(), |
||||
|
displayName |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionManager : DomainService |
||||
|
{ |
||||
|
protected IEditionRepository EditionRepository { get; } |
||||
|
|
||||
|
public EditionManager(IEditionRepository editionRepository) |
||||
|
{ |
||||
|
EditionRepository = editionRepository; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task DeleteAsync(Edition edition) |
||||
|
{ |
||||
|
if (await EditionRepository.CheckUsedByTenantAsync(edition.Id)) |
||||
|
{ |
||||
|
throw new BusinessException(AbpSaasErrorCodes.DeleteUsedEdition) |
||||
|
.WithData(nameof(Edition.DisplayName), edition.DisplayName); |
||||
|
} |
||||
|
await EditionRepository.DeleteAsync(edition); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<Edition> CreateAsync(string displayName) |
||||
|
{ |
||||
|
Check.NotNull(displayName, nameof(displayName)); |
||||
|
|
||||
|
await ValidateDisplayNameAsync(displayName); |
||||
|
return new Edition(GuidGenerator.Create(), displayName); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ChangeDisplayNameAsync(Edition edition, string displayName) |
||||
|
{ |
||||
|
Check.NotNull(edition, nameof(edition)); |
||||
|
Check.NotNull(displayName, nameof(displayName)); |
||||
|
|
||||
|
await ValidateDisplayNameAsync(displayName, edition.Id); |
||||
|
|
||||
|
edition.SetDisplayName(displayName); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task ValidateDisplayNameAsync(string displayName, Guid? expectedId = null) |
||||
|
{ |
||||
|
var edition = await EditionRepository.FindByDisplayNameAsync(displayName); |
||||
|
if (edition != null && edition.Id != expectedId) |
||||
|
{ |
||||
|
throw new BusinessException(AbpSaasErrorCodes.DuplicateEditionDisplayName) |
||||
|
.WithData(nameof(Edition.DisplayName), displayName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using LINGYUN.Abp.MultiTenancy.Editions; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public class EditionStore : IEditionStore, ITransientDependency |
||||
|
{ |
||||
|
protected IEditionRepository EditionRepository { get; } |
||||
|
protected IObjectMapper<AbpSaasDomainModule> ObjectMapper { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected IDistributedCache<EditionCacheItem> Cache { get; } |
||||
|
|
||||
|
public EditionStore( |
||||
|
IEditionRepository editionRepository, |
||||
|
IObjectMapper<AbpSaasDomainModule> objectMapper, |
||||
|
ICurrentTenant currentTenant, |
||||
|
IDistributedCache<EditionCacheItem> cache) |
||||
|
{ |
||||
|
EditionRepository = editionRepository; |
||||
|
ObjectMapper = objectMapper; |
||||
|
CurrentTenant = currentTenant; |
||||
|
Cache = cache; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<EditionInfo> FindByTenantAsync(Guid tenantId) |
||||
|
{ |
||||
|
return (await GetCacheItemAsync(tenantId)).Value; |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<EditionCacheItem> GetCacheItemAsync(Guid tenantId) |
||||
|
{ |
||||
|
var cacheKey = CalculateCacheKey(tenantId); |
||||
|
|
||||
|
var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); |
||||
|
if (cacheItem != null) |
||||
|
{ |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
using (CurrentTenant.Change(null)) |
||||
|
{ |
||||
|
var edition = await EditionRepository.FindByTenantIdAsync(tenantId); |
||||
|
return await SetCacheAsync(cacheKey, edition); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected async virtual Task<EditionCacheItem> SetCacheAsync(string cacheKey, [CanBeNull] Edition edition) |
||||
|
{ |
||||
|
var editionInfo = edition != null ? ObjectMapper.Map<Edition, EditionInfo>(edition) : null; |
||||
|
var cacheItem = new EditionCacheItem(editionInfo); |
||||
|
await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
protected virtual string CalculateCacheKey(Guid tenantId) |
||||
|
{ |
||||
|
return EditionCacheItem.CalculateCacheKey(tenantId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public interface IEditionDataSeeder |
||||
|
{ |
||||
|
Task SeedDefaultEditionsAsync(); |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
public interface IEditionRepository : IBasicRepository<Edition, Guid> |
||||
|
{ |
||||
|
Task<bool> CheckUsedByTenantAsync( |
||||
|
Guid id, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<Edition> FindByDisplayNameAsync( |
||||
|
string displayName, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<Edition> FindByTenantIdAsync( |
||||
|
Guid tenantId, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<Edition>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 0, |
||||
|
string filter = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<long> GetCountAsync( |
||||
|
string filter = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public interface ITenantManager : IDomainService |
||||
|
{ |
||||
|
[NotNull] |
||||
|
Task<Tenant> CreateAsync([NotNull] string name); |
||||
|
|
||||
|
Task ChangeNameAsync([NotNull] Tenant tenant, [NotNull] string name); |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public interface ITenantRepository : IBasicRepository<Tenant, Guid> |
||||
|
{ |
||||
|
[Obsolete("Use FindByNameAsync method.")] |
||||
|
Tenant FindByName( |
||||
|
string name, |
||||
|
bool includeDetails = true |
||||
|
); |
||||
|
|
||||
|
[Obsolete("Use FindAsync method.")] |
||||
|
Tenant FindById( |
||||
|
Guid id, |
||||
|
bool includeDetails = true |
||||
|
); |
||||
|
|
||||
|
Task<Tenant> FindByNameAsync( |
||||
|
string name, |
||||
|
bool includeDetails = true, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<Tenant>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = int.MaxValue, |
||||
|
int skipCount = 0, |
||||
|
string filter = null, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<long> GetCountAsync( |
||||
|
string filter = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class Tenant : FullAuditedAggregateRoot<Guid> |
||||
|
{ |
||||
|
protected const string DefaultConnectionStringName = Volo.Abp.Data.ConnectionStrings.DefaultConnectionStringName; |
||||
|
|
||||
|
public virtual string Name { get; protected set; } |
||||
|
|
||||
|
public virtual bool IsActive { get; set; } |
||||
|
|
||||
|
public virtual DateTime? EnableTime { get; protected set; } |
||||
|
|
||||
|
public virtual DateTime? DisableTime { get; protected set; } |
||||
|
|
||||
|
public virtual Guid? EditionId { get; set; } |
||||
|
public virtual Edition Edition { get; set; } |
||||
|
|
||||
|
public virtual ICollection<TenantConnectionString> ConnectionStrings { get; protected set; } |
||||
|
|
||||
|
protected Tenant() |
||||
|
{ |
||||
|
ConnectionStrings = new Collection<TenantConnectionString>(); |
||||
|
} |
||||
|
|
||||
|
protected internal Tenant(Guid id, [NotNull] string name) |
||||
|
: base(id) |
||||
|
{ |
||||
|
SetName(name); |
||||
|
|
||||
|
ConnectionStrings = new Collection<TenantConnectionString>(); |
||||
|
} |
||||
|
|
||||
|
public virtual void SetEnableTime(DateTime? enableTime = null) |
||||
|
{ |
||||
|
EnableTime = enableTime; |
||||
|
} |
||||
|
|
||||
|
public virtual void SetDisableTime(DateTime? disableTime = null) |
||||
|
{ |
||||
|
DisableTime = disableTime; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public virtual string FindDefaultConnectionString() |
||||
|
{ |
||||
|
return FindConnectionString(DefaultConnectionStringName); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public virtual string FindConnectionString(string name) |
||||
|
{ |
||||
|
return ConnectionStrings.FirstOrDefault(c => c.Name == name)?.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual void SetDefaultConnectionString(string connectionString) |
||||
|
{ |
||||
|
SetConnectionString(DefaultConnectionStringName, connectionString); |
||||
|
} |
||||
|
|
||||
|
public virtual void SetConnectionString(string name, string connectionString) |
||||
|
{ |
||||
|
var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name); |
||||
|
|
||||
|
if (tenantConnectionString != null) |
||||
|
{ |
||||
|
tenantConnectionString.SetValue(connectionString); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ConnectionStrings.Add(new TenantConnectionString(Id, name, connectionString)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual void RemoveDefaultConnectionString() |
||||
|
{ |
||||
|
RemoveConnectionString(DefaultConnectionStringName); |
||||
|
} |
||||
|
|
||||
|
public virtual void RemoveConnectionString(string name) |
||||
|
{ |
||||
|
var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name); |
||||
|
|
||||
|
if (tenantConnectionString != null) |
||||
|
{ |
||||
|
ConnectionStrings.Remove(tenantConnectionString); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected internal virtual void SetName([NotNull] string name) |
||||
|
{ |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConsts.MaxNameLength); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
[Serializable] |
||||
|
[IgnoreMultiTenancy] |
||||
|
public class TenantCacheItem |
||||
|
{ |
||||
|
private const string CacheKeyFormat = "i:{0},n:{1}"; |
||||
|
|
||||
|
public TenantConfiguration Value { get; set; } |
||||
|
|
||||
|
public TenantCacheItem() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TenantCacheItem(TenantConfiguration value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public static string CalculateCacheKey(Guid? id, string name) |
||||
|
{ |
||||
|
if (id == null && name.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
throw new AbpException("Both id and name can't be invalid."); |
||||
|
} |
||||
|
|
||||
|
return string.Format(CacheKeyFormat, |
||||
|
id?.ToString() ?? "null", |
||||
|
(name.IsNullOrWhiteSpace() ? "null" : name)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities.Events; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantCacheItemInvalidator : ILocalEventHandler<EntityChangedEventData<Tenant>>, ITransientDependency |
||||
|
{ |
||||
|
protected IDistributedCache<TenantCacheItem> Cache { get; } |
||||
|
|
||||
|
public TenantCacheItemInvalidator(IDistributedCache<TenantCacheItem> cache) |
||||
|
{ |
||||
|
Cache = cache; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task HandleEventAsync(EntityChangedEventData<Tenant> eventData) |
||||
|
{ |
||||
|
await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(eventData.Entity.Id, eventData.Entity.Name), considerUow: true); |
||||
|
// 同时移除租户版本缓存
|
||||
|
await Cache.RemoveAsync(EditionCacheItem.CalculateCacheKey(eventData.Entity.Id), considerUow: true); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantConnectionString : Entity |
||||
|
{ |
||||
|
public virtual Guid TenantId { get; protected set; } |
||||
|
|
||||
|
public virtual string Name { get; protected set; } |
||||
|
|
||||
|
public virtual string Value { get; protected set; } |
||||
|
|
||||
|
protected TenantConnectionString() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public TenantConnectionString(Guid tenantId, [NotNull] string name, [NotNull] string value) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConnectionStringConsts.MaxNameLength); |
||||
|
SetValue(value); |
||||
|
} |
||||
|
|
||||
|
public virtual void SetValue([NotNull] string value) |
||||
|
{ |
||||
|
Value = Check.NotNullOrWhiteSpace(value, nameof(value), TenantConnectionStringConsts.MaxValueLength); |
||||
|
} |
||||
|
|
||||
|
public override object[] GetKeys() |
||||
|
{ |
||||
|
return new object[] { TenantId, Name }; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantManager : DomainService, ITenantManager |
||||
|
{ |
||||
|
protected ITenantRepository TenantRepository { get; } |
||||
|
|
||||
|
public TenantManager(ITenantRepository tenantRepository) |
||||
|
{ |
||||
|
TenantRepository = tenantRepository; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public virtual async Task<Tenant> CreateAsync(string name) |
||||
|
{ |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
|
||||
|
await ValidateNameAsync(name); |
||||
|
return new Tenant(GuidGenerator.Create(), name); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ChangeNameAsync(Tenant tenant, string name) |
||||
|
{ |
||||
|
Check.NotNull(tenant, nameof(tenant)); |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
|
||||
|
await ValidateNameAsync(name, tenant.Id); |
||||
|
tenant.SetName(name); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task ValidateNameAsync(string name, Guid? expectedId = null) |
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindByNameAsync(name); |
||||
|
if (tenant != null && tenant.Id != expectedId) |
||||
|
{ |
||||
|
throw new BusinessException(AbpSaasErrorCodes.DuplicateTenantName) |
||||
|
.WithData(nameof(Tenant.Name), name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,137 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
public class TenantStore : ITenantStore, ITransientDependency |
||||
|
{ |
||||
|
protected ITenantRepository TenantRepository { get; } |
||||
|
protected IObjectMapper<AbpSaasDomainModule> ObjectMapper { get; } |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected IDistributedCache<TenantCacheItem> Cache { get; } |
||||
|
|
||||
|
public TenantStore( |
||||
|
ITenantRepository tenantRepository, |
||||
|
IObjectMapper<AbpSaasDomainModule> objectMapper, |
||||
|
ICurrentTenant currentTenant, |
||||
|
IDistributedCache<TenantCacheItem> cache) |
||||
|
{ |
||||
|
TenantRepository = tenantRepository; |
||||
|
ObjectMapper = objectMapper; |
||||
|
CurrentTenant = currentTenant; |
||||
|
Cache = cache; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TenantConfiguration> FindAsync(string name) |
||||
|
{ |
||||
|
return (await GetCacheItemAsync(null, name)).Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TenantConfiguration> FindAsync(Guid id) |
||||
|
{ |
||||
|
return (await GetCacheItemAsync(id, null)).Value; |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use FindAsync method.")] |
||||
|
public virtual TenantConfiguration Find(string name) |
||||
|
{ |
||||
|
return (GetCacheItem(null, name)).Value; |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use FindAsync method.")] |
||||
|
public virtual TenantConfiguration Find(Guid id) |
||||
|
{ |
||||
|
return (GetCacheItem(id, null)).Value; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<TenantCacheItem> GetCacheItemAsync(Guid? id, string name) |
||||
|
{ |
||||
|
var cacheKey = CalculateCacheKey(id, name); |
||||
|
|
||||
|
var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); |
||||
|
if (cacheItem != null) |
||||
|
{ |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
if (id.HasValue) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindAsync(id.Value); |
||||
|
return await SetCacheAsync(cacheKey, tenant); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!name.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
||||
|
{ |
||||
|
var tenant = await TenantRepository.FindByNameAsync(name); |
||||
|
return await SetCacheAsync(cacheKey, tenant); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new AbpException("Both id and name can't be invalid."); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<TenantCacheItem> SetCacheAsync(string cacheKey, [CanBeNull] Tenant tenant) |
||||
|
{ |
||||
|
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null; |
||||
|
var cacheItem = new TenantCacheItem(tenantConfiguration); |
||||
|
await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use GetCacheItemAsync method.")] |
||||
|
protected virtual TenantCacheItem GetCacheItem(Guid? id, string name) |
||||
|
{ |
||||
|
var cacheKey = CalculateCacheKey(id, name); |
||||
|
|
||||
|
var cacheItem = Cache.Get(cacheKey, considerUow: true); |
||||
|
if (cacheItem != null) |
||||
|
{ |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
if (id.HasValue) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
||||
|
{ |
||||
|
var tenant = TenantRepository.FindById(id.Value); |
||||
|
return SetCache(cacheKey, tenant); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!name.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
||||
|
{ |
||||
|
var tenant = TenantRepository.FindByName(name); |
||||
|
return SetCache(cacheKey, tenant); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new AbpException("Both id and name can't be invalid."); |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use SetCacheAsync method.")] |
||||
|
protected virtual TenantCacheItem SetCache(string cacheKey, [CanBeNull] Tenant tenant) |
||||
|
{ |
||||
|
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null; |
||||
|
var cacheItem = new TenantCacheItem(tenantConfiguration); |
||||
|
Cache.Set(cacheKey, cacheItem, considerUow: true); |
||||
|
return cacheItem; |
||||
|
} |
||||
|
|
||||
|
protected virtual string CalculateCacheKey(Guid? id, string name) |
||||
|
{ |
||||
|
return TenantCacheItem.CalculateCacheKey(id, name); |
||||
|
} |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,67 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.EntityFrameworkCore.Modeling; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
public static class AbpSaasDbContextModelCreatingExtensions |
||||
|
{ |
||||
|
public static void ConfigureSaas( |
||||
|
this ModelBuilder builder) |
||||
|
{ |
||||
|
Check.NotNull(builder, nameof(builder)); |
||||
|
|
||||
|
if (builder.IsTenantOnlyDatabase()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
builder.Entity<Edition>(b => |
||||
|
{ |
||||
|
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "Editions", AbpSaasDbProperties.DbSchema); |
||||
|
|
||||
|
b.ConfigureByConvention(); |
||||
|
|
||||
|
b.Property(t => t.DisplayName) |
||||
|
.HasMaxLength(EditionConsts.MaxDisplayNameLength) |
||||
|
.IsRequired(); |
||||
|
|
||||
|
b.HasIndex(u => u.DisplayName); |
||||
|
|
||||
|
b.ApplyObjectExtensionMappings(); |
||||
|
}); |
||||
|
|
||||
|
builder.Entity<Tenant>(b => |
||||
|
{ |
||||
|
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "Tenants", AbpSaasDbProperties.DbSchema); |
||||
|
|
||||
|
b.ConfigureByConvention(); |
||||
|
|
||||
|
b.Property(t => t.Name).IsRequired().HasMaxLength(TenantConsts.MaxNameLength); |
||||
|
|
||||
|
b.HasMany(u => u.ConnectionStrings).WithOne().HasForeignKey(uc => uc.TenantId).IsRequired(); |
||||
|
|
||||
|
b.HasIndex(u => u.Name); |
||||
|
|
||||
|
b.ApplyObjectExtensionMappings(); |
||||
|
}); |
||||
|
|
||||
|
builder.Entity<TenantConnectionString>(b => |
||||
|
{ |
||||
|
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "TenantConnectionStrings", AbpSaasDbProperties.DbSchema); |
||||
|
|
||||
|
b.ConfigureByConvention(); |
||||
|
|
||||
|
b.HasKey(x => new { x.TenantId, x.Name }); |
||||
|
|
||||
|
b.Property(cs => cs.Name).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxNameLength); |
||||
|
b.Property(cs => cs.Value).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxValueLength); |
||||
|
|
||||
|
b.ApplyObjectExtensionMappings(); |
||||
|
}); |
||||
|
|
||||
|
builder.TryConfigureObjectExtensions<SaasDbContext>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasDomainModule))] |
||||
|
[DependsOn(typeof(AbpEntityFrameworkCoreModule))] |
||||
|
public class AbpSaasEntityFrameworkCoreModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAbpDbContext<SaasDbContext>(options => |
||||
|
{ |
||||
|
options.AddDefaultRepositories<ISaasDbContext>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpDbConnectionOptions>(options => |
||||
|
{ |
||||
|
// 将租户管理连接字符串聚合到 Saas 模块中
|
||||
|
options.Databases.Configure(AbpSaasDbProperties.ConnectionStringName, |
||||
|
database => |
||||
|
{ |
||||
|
database.MappedConnections.Add("AbpTenantManagement"); |
||||
|
database.IsUsedByTenants = false; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCoreEditionRepository : EfCoreRepository<ISaasDbContext, Edition, Guid>, IEditionRepository |
||||
|
{ |
||||
|
public EfCoreEditionRepository( |
||||
|
IDbContextProvider<ISaasDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<bool> CheckUsedByTenantAsync( |
||||
|
Guid id, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(); |
||||
|
var tenantDbSet = dbContext.Set<Tenant>(); |
||||
|
|
||||
|
return await tenantDbSet |
||||
|
.AnyAsync(x => x.EditionId == id, GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<Edition> FindByDisplayNameAsync( |
||||
|
string displayName, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefaultAsync(t => t.DisplayName == displayName, GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<Edition> FindByTenantIdAsync( |
||||
|
Guid tenantId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(); |
||||
|
var editionDbSet = dbContext.Set<Edition>(); |
||||
|
var tenantDbSet = dbContext.Set<Tenant>(); |
||||
|
|
||||
|
var queryable = from tenant in tenantDbSet |
||||
|
join edition in editionDbSet |
||||
|
on tenant.EditionId equals edition.Id |
||||
|
where tenant.Id == tenantId |
||||
|
select edition; |
||||
|
|
||||
|
return await queryable |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<long> GetCountAsync( |
||||
|
string filter = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(!filter.IsNullOrWhiteSpace(), x => x.DisplayName.Contains(filter)) |
||||
|
.CountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<Edition>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 0, |
||||
|
string filter = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(!filter.IsNullOrWhiteSpace(), x => x.DisplayName.Contains(filter)) |
||||
|
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Edition.DisplayName) : sorting) |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,226 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCoreTenantRepository : EfCoreRepository<ISaasDbContext, Tenant, Guid>, ITenantRepository |
||||
|
{ |
||||
|
public EfCoreTenantRepository(IDbContextProvider<ISaasDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public async override Task<Tenant> FindAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(); |
||||
|
var tenantDbSet = dbContext.Set<Tenant>() |
||||
|
.IncludeDetails(includeDetails); |
||||
|
|
||||
|
if (includeDetails) |
||||
|
{ |
||||
|
var editionDbSet = dbContext.Set<Edition>(); |
||||
|
var queryable = from tenant in tenantDbSet |
||||
|
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
||||
|
from e in eg.DefaultIfEmpty() |
||||
|
where tenant.Id.Equals(id) |
||||
|
orderby tenant.Id |
||||
|
select new |
||||
|
{ |
||||
|
Tenant = tenant, |
||||
|
Edition = e, |
||||
|
}; |
||||
|
var result = await queryable |
||||
|
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
||||
|
|
||||
|
if (result != null && result.Tenant != null) |
||||
|
{ |
||||
|
result.Tenant.Edition = result.Edition; |
||||
|
} |
||||
|
|
||||
|
return result?.Tenant; |
||||
|
} |
||||
|
|
||||
|
return await tenantDbSet |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefaultAsync(t => t.Id == id, GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<Tenant> FindByNameAsync( |
||||
|
string name, |
||||
|
bool includeDetails = true, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(); |
||||
|
var tenantDbSet = dbContext.Set<Tenant>() |
||||
|
.IncludeDetails(includeDetails); |
||||
|
|
||||
|
if (includeDetails) |
||||
|
{ |
||||
|
var editionDbSet = dbContext.Set<Edition>(); |
||||
|
var queryable = from tenant in tenantDbSet |
||||
|
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
||||
|
from e in eg.DefaultIfEmpty() |
||||
|
where tenant.Name.Equals(name) |
||||
|
orderby tenant.Id |
||||
|
select new |
||||
|
{ |
||||
|
Tenant = tenant, |
||||
|
Edition = e, |
||||
|
}; |
||||
|
var result = await queryable |
||||
|
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
||||
|
if (result != null && result.Tenant != null) |
||||
|
{ |
||||
|
result.Tenant.Edition = result.Edition; |
||||
|
} |
||||
|
|
||||
|
return result?.Tenant; |
||||
|
} |
||||
|
|
||||
|
return await tenantDbSet |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefaultAsync(t => t.Name == name, GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use FindByNameAsync method.")] |
||||
|
public virtual Tenant FindByName(string name, bool includeDetails = true) |
||||
|
{ |
||||
|
var tenantDbSet = DbContext.Set<Tenant>() |
||||
|
.IncludeDetails(includeDetails); |
||||
|
|
||||
|
if (includeDetails) |
||||
|
{ |
||||
|
var editionDbSet = DbContext.Set<Edition>(); |
||||
|
var queryable = from tenant in tenantDbSet |
||||
|
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
||||
|
from e in eg.DefaultIfEmpty() |
||||
|
where tenant.Name.Equals(name) |
||||
|
orderby tenant.Id |
||||
|
select new |
||||
|
{ |
||||
|
Tenant = tenant, |
||||
|
Edition = e, |
||||
|
}; |
||||
|
var result = queryable |
||||
|
.FirstOrDefault(); |
||||
|
if (result != null && result.Tenant != null) |
||||
|
{ |
||||
|
result.Tenant.Edition = result.Edition; |
||||
|
} |
||||
|
|
||||
|
return result?.Tenant; |
||||
|
} |
||||
|
|
||||
|
return tenantDbSet |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefault(t => t.Name == name); |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use FindAsync method.")] |
||||
|
public virtual Tenant FindById(Guid id, bool includeDetails = true) |
||||
|
{ |
||||
|
var tenantDbSet = DbContext.Set<Tenant>() |
||||
|
.IncludeDetails(includeDetails); |
||||
|
|
||||
|
if (includeDetails) |
||||
|
{ |
||||
|
var editionDbSet = DbContext.Set<Edition>(); |
||||
|
var queryable = from tenant in tenantDbSet |
||||
|
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
||||
|
from e in eg.DefaultIfEmpty() |
||||
|
where tenant.Id.Equals(id) |
||||
|
orderby tenant.Id |
||||
|
select new |
||||
|
{ |
||||
|
Tenant = tenant, |
||||
|
Edition = e, |
||||
|
}; |
||||
|
var result = queryable |
||||
|
.FirstOrDefault(); |
||||
|
if (result != null && result.Tenant != null) |
||||
|
{ |
||||
|
result.Tenant.Edition = result.Edition; |
||||
|
} |
||||
|
|
||||
|
return result?.Tenant; |
||||
|
} |
||||
|
|
||||
|
return tenantDbSet |
||||
|
.OrderBy(t => t.Id) |
||||
|
.FirstOrDefault(t => t.Id == id); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<Tenant>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = int.MaxValue, |
||||
|
int skipCount = 0, |
||||
|
string filter = null, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (includeDetails) |
||||
|
{ |
||||
|
var dbContext = await GetDbContextAsync(); |
||||
|
var editionDbSet = dbContext.Set<Edition>(); |
||||
|
var tenantDbSet = dbContext.Set<Tenant>() |
||||
|
.IncludeDetails(includeDetails); |
||||
|
|
||||
|
var queryable = tenantDbSet |
||||
|
.WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter)) |
||||
|
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting); |
||||
|
|
||||
|
var combinedResult = await queryable |
||||
|
.Join( |
||||
|
editionDbSet, |
||||
|
o => o.EditionId, |
||||
|
i => i.Id, |
||||
|
(tenant, edition) => new { tenant, edition }) |
||||
|
.Skip(skipCount) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
|
||||
|
return combinedResult.Select(s => |
||||
|
{ |
||||
|
s.tenant.Edition = s.edition; |
||||
|
return s.tenant; |
||||
|
}).ToList(); |
||||
|
} |
||||
|
|
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter)) |
||||
|
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting) |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<long> GetCountAsync(string filter = null, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetQueryableAsync()) |
||||
|
.WhereIf( |
||||
|
!filter.IsNullOrWhiteSpace(), |
||||
|
u => |
||||
|
u.Name.Contains(filter) |
||||
|
).CountAsync(cancellationToken: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
[Obsolete("Use WithDetailsAsync method.")] |
||||
|
public override IQueryable<Tenant> WithDetails() |
||||
|
{ |
||||
|
return GetQueryable().IncludeDetails(); |
||||
|
} |
||||
|
|
||||
|
public override async Task<IQueryable<Tenant>> WithDetailsAsync() |
||||
|
{ |
||||
|
return (await GetQueryableAsync()).IncludeDetails(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
[IgnoreMultiTenancy] |
||||
|
[ConnectionStringName(AbpSaasDbProperties.ConnectionStringName)] |
||||
|
public interface ISaasDbContext : IEfCoreDbContext |
||||
|
{ |
||||
|
DbSet<Edition> Editions { get; } |
||||
|
DbSet<Tenant> Tenants { get; } |
||||
|
DbSet<TenantConnectionString> TenantConnectionStrings { get; } |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using LINGYUN.Abp.Saas.Editions; |
||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
[IgnoreMultiTenancy] |
||||
|
[ConnectionStringName(AbpSaasDbProperties.ConnectionStringName)] |
||||
|
public class SaasDbContext : AbpDbContext<SaasDbContext>, ISaasDbContext |
||||
|
{ |
||||
|
public DbSet<Edition> Editions { get; set; } |
||||
|
|
||||
|
public DbSet<Tenant> Tenants { get; set; } |
||||
|
|
||||
|
public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; } |
||||
|
|
||||
|
public SaasDbContext(DbContextOptions<SaasDbContext> options) |
||||
|
: base(options) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void OnModelCreating(ModelBuilder builder) |
||||
|
{ |
||||
|
base.OnModelCreating(builder); |
||||
|
|
||||
|
builder.ConfigureSaas(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using LINGYUN.Abp.Saas.Tenants; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
||||
|
|
||||
|
public static class SaasEfCoreQueryableExtensions |
||||
|
{ |
||||
|
public static IQueryable<Tenant> IncludeDetails(this IQueryable<Tenant> queryable, bool include = true) |
||||
|
{ |
||||
|
if (!include) |
||||
|
{ |
||||
|
return queryable; |
||||
|
} |
||||
|
|
||||
|
return queryable |
||||
|
.Include(x => x.ConnectionStrings); |
||||
|
} |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Http.Client; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
||||
|
[DependsOn(typeof(AbpHttpClientModule))] |
||||
|
public class AbpSaasHttpApiClientModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddHttpClientProxies( |
||||
|
typeof(AbpSaasApplicationContractsModule).Assembly, |
||||
|
AbpSaasRemoteServiceConsts.RemoteServiceName |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -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 @@ |
|||||
|
<?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> |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,11 @@ |
|||||
|
using LINGYUN.Abp.Saas.Localization; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
public abstract class AbpSaasControllerBase : AbpControllerBase |
||||
|
{ |
||||
|
protected AbpSaasControllerBase() |
||||
|
{ |
||||
|
LocalizationResource = typeof(AbpSaasResource); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using LINGYUN.Abp.Saas.Localization; |
||||
|
using Localization.Resources.AbpUi; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
||||
|
[DependsOn(typeof(AbpAspNetCoreMvcModule))] |
||||
|
public class AbpSaasHttpApiModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IMvcBuilder>(mvcBuilder => |
||||
|
{ |
||||
|
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpSaasHttpApiModule).Assembly); |
||||
|
}); |
||||
|
|
||||
|
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.AddAssemblyResource( |
||||
|
typeof(AbpSaasResource), |
||||
|
typeof(AbpSaasApplicationContractsModule).Assembly); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<AbpSaasResource>() |
||||
|
.AddBaseTypes(typeof(AbpUiResource)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Editions; |
||||
|
|
||||
|
[Controller] |
||||
|
[Authorize(AbpSaasPermissions.Editions.Default)] |
||||
|
[RemoteService(Name = AbpSaasRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area(AbpSaasRemoteServiceConsts.ModuleName)] |
||||
|
[Route("api/saas/editions")] |
||||
|
public class EditionController : AbpSaasControllerBase, IEditionAppService |
||||
|
{ |
||||
|
protected IEditionAppService EditionAppService { get; } |
||||
|
|
||||
|
public EditionController(IEditionAppService editionAppService) |
||||
|
{ |
||||
|
EditionAppService = editionAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Authorize(AbpSaasPermissions.Editions.Create)] |
||||
|
public virtual Task<EditionDto> CreateAsync(EditionCreateDto input) |
||||
|
{ |
||||
|
return EditionAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(AbpSaasPermissions.Editions.Delete)] |
||||
|
public virtual Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
return EditionAppService.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}")] |
||||
|
public virtual Task<EditionDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return EditionAppService.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
public virtual Task<PagedResultDto<EditionDto>> GetListAsync(EditionGetListInput input) |
||||
|
{ |
||||
|
return EditionAppService.GetListAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(AbpSaasPermissions.Editions.Update)] |
||||
|
public virtual Task<EditionDto> UpdateAsync(Guid id, EditionUpdateDto input) |
||||
|
{ |
||||
|
return EditionAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Saas.Tenants; |
||||
|
|
||||
|
[Controller] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.Default)] |
||||
|
[RemoteService(Name = AbpSaasRemoteServiceConsts.RemoteServiceName)] |
||||
|
[Area(AbpSaasRemoteServiceConsts.ModuleName)] |
||||
|
[Route("api/saas/tenants")] |
||||
|
public class TenantController : AbpSaasControllerBase, ITenantAppService |
||||
|
{ |
||||
|
protected ITenantAppService TenantAppService { get; } |
||||
|
|
||||
|
public TenantController(ITenantAppService tenantAppService) |
||||
|
{ |
||||
|
TenantAppService = tenantAppService; |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}")] |
||||
|
public virtual Task<TenantDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
return TenantAppService.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("by-name/{name}")] |
||||
|
public virtual Task<TenantDto> GetAsync(string name) |
||||
|
{ |
||||
|
return TenantAppService.GetAsync(name); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
public virtual Task<PagedResultDto<TenantDto>> GetListAsync(TenantGetListInput input) |
||||
|
{ |
||||
|
return TenantAppService.GetListAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPost] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.Create)] |
||||
|
public virtual Task<TenantDto> CreateAsync(TenantCreateDto input) |
||||
|
{ |
||||
|
return TenantAppService.CreateAsync(input); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.Update)] |
||||
|
public virtual Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input) |
||||
|
{ |
||||
|
return TenantAppService.UpdateAsync(id, input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.Delete)] |
||||
|
public virtual Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
return TenantAppService.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}/connection-string/{name}")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual Task<TenantConnectionStringDto> GetConnectionStringAsync(Guid id, string name) |
||||
|
{ |
||||
|
return TenantAppService.GetConnectionStringAsync(id, name); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Route("{id}/connection-string")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync(Guid id) |
||||
|
{ |
||||
|
return TenantAppService.GetConnectionStringAsync(id); |
||||
|
} |
||||
|
|
||||
|
[HttpPut] |
||||
|
[Route("{id}/connection-string")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual Task<TenantConnectionStringDto> SetConnectionStringAsync(Guid id, TenantConnectionStringCreateOrUpdate input) |
||||
|
{ |
||||
|
return TenantAppService.SetConnectionStringAsync(id, input); |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Route("{id}/connection-string/{name}")] |
||||
|
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
||||
|
public virtual Task DeleteConnectionStringAsync(Guid id, string name) |
||||
|
{ |
||||
|
return TenantAppService.DeleteConnectionStringAsync(id, name); |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue