From 27768b73f242da813740579f9fe98cf7f30078f3 Mon Sep 17 00:00:00 2001 From: feijie Date: Mon, 30 Dec 2024 00:27:58 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(aspnet-core/templates/aio):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=A8=E5=8A=9F=E8=83=BD=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=87=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20.NET=208.0=20=E7=89=88=E6=9C=AC=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ageName.CompanyName.ProjectName.AIO.csproj | 30 + .../content/.template.config/template.json | 115 +++ .../.template.config/template.zh-Hans.json | 5 + .../aio/content/Directory.Build.props | 13 + .../aio/content/Directory.Packages.props | 490 +++++++++ .../templates/aio/content/NuGet.Config | 12 + aspnet-core/templates/aio/content/README.md | 129 +++ .../templates/aio/content/README.zh-CN.md | 129 +++ .../templates/aio/content/common.props | 38 + .../aio/content/configureawait.props | 9 + .../.config/dotnet-tools.json | 12 + .../.gitignore | 2 + .../AbpCookieAuthenticationHandler.cs | 89 ++ .../BackgroundJobs/NotificationPublishJob.cs | 38 + .../NotificationPublishJobArgs.cs | 22 + .../Controllers/HomeController.cs | 11 + .../Controllers/SettingMergeController.cs | 70 ++ .../Controllers/UserSettingMergeController.cs | 45 + .../Dockerfile | 19 + .../Distributed/ChatMessageEventHandler.cs | 59 ++ .../Distributed/NotificationEventHandler.cs | 470 +++++++++ .../Distributed/TenantSynchronizer.cs | 53 + .../Distributed/UserCreateEventHandler.cs | 30 + .../Distributed/WebhooksEventHandler.cs | 112 +++ .../Local/UserCreateJoinIMEventHandler.cs | 58 ++ .../UserCreateSendWelcomeEventHandler.cs | 69 ++ .../CustomIdentityResources.cs | 19 + ...rviceApplicationsSingleModule.Configure.cs | 935 ++++++++++++++++++ .../MicroServiceApplicationsSingleModule.cs | 394 ++++++++ ...eSiteCookiesServiceCollectionExtensions.cs | 67 ++ .../MultiTenancy/ITenantConfigurationCache.cs | 10 + .../MultiTenancy/TenantConfigurationCache.cs | 59 ++ .../TenantConfigurationCacheItem.cs | 19 + ...me.CompanyName.ProjectName.AIO.Host.csproj | 276 ++++++ .../PersonalInfo/Default.cshtml | 103 ++ .../PersonalInfo/Default.js | 28 + .../Pages/Account/EmailConfirm.cshtml | 17 + .../Pages/Account/EmailConfirm.cshtml.cs | 72 ++ .../Account/EmailConfirmConfirmation.cshtml | 13 + .../EmailConfirmConfirmation.cshtml.cs | 22 + .../Pages/Account/SendCode.cshtml | 26 + .../Pages/Account/SendCode.cshtml.cs | 125 +++ .../Pages/Account/SendEmailConfirm.cshtml | 16 + .../Pages/Account/SendEmailConfirm.cshtml.cs | 73 ++ .../Account/TwoFactorSupportedLoginModel.cs | 63 ++ .../Pages/Account/UseRecoveryCode.cshtml | 4 + .../Pages/Account/UseRecoveryCode.cshtml.cs | 11 + .../Account/VerifyAuthenticatorCode.cshtml | 26 + .../Account/VerifyAuthenticatorCode.cshtml.cs | 59 ++ .../Pages/Account/VerifyCode.cshtml | 29 + .../Pages/Account/VerifyCode.cshtml.cs | 90 ++ .../Pages/Index.cshtml | 36 + .../Pages/Index.cshtml.cs | 11 + .../Pages/_ViewImports.cshtml | 4 + .../Program.cs | 82 ++ .../Properties/launchSettings.json | 30 + .../TenantHeaderParamter.cs | 35 + .../Messages/TextMessageReplyContributor.cs | 21 + .../Messages/UserSubscribeEventContributor.cs | 21 + .../Messages/TextMessageReplyContributor.cs | 24 + .../appsettings.Development.json | 249 +++++ .../appsettings.json | 89 ++ .../gulpfile.js | 10 + ...panyName.ProjectName.AIO.DbMigrator.csproj | 54 + .../Program.cs | 44 + .../README.EN.md | 75 ++ .../README.md | 75 ++ .../SingleDbMigratorHostedService.cs | 51 + .../SingleDbMigratorModule.Configure.cs | 14 + .../SingleDbMigratorModule.cs | 22 + .../Usings.cs | 7 + .../appsettings.MySql.json | 228 +++++ .../appsettings.PostgreSql.json | 228 +++++ .../appsettings.SqlServer.json | 228 +++++ .../appsettings.json | 104 ++ .../FodyWeavers.xml | 4 + .../FodyWeavers.xsd | 30 + ...tName.AIO.EntityFrameworkCore.MySql.csproj | 22 + .../README.en.md | 59 ++ .../README.md | 59 ++ .../SingleMigrationsDbContextFactory.cs | 32 + ...igrationsEntityFrameworkCoreMySqlModule.cs | 24 + .../DataSeeder/ClientDataSeederContributor.cs | 469 +++++++++ .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...ProjectName.AIO.EntityFrameworkCore.csproj | 47 + .../README.EN.md | 97 ++ .../README.md | 97 ++ .../SingleDbMigrationEventHandler.cs | 242 +++++ .../SingleDbMigrationService.cs | 101 ++ .../SingleMigrationsDbContext.cs | 58 ++ ...ngleMigrationsEntityFrameworkCoreModule.cs | 48 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...e.ProjectName.Application.Contracts.csproj | 27 + .../ProjectNameFeatureDefinitionProvider.cs | 18 + .../Features/ProjectNameFeatureNames.cs | 6 + .../IProjectNameDynamicQueryableAppService.cs | 10 + ...ProjectNamePermissionDefinitionProvider.cs | 22 + .../Permissions/ProjectNamePermissions.cs | 8 + .../ProjectNameApplicationContractsModule.cs | 17 + .../ProjectNameRemoteServiceConsts.cs | 7 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...CompanyName.ProjectName.Application.csproj | 26 + .../ProjectName/ProjectNameAppServiceBase.cs | 13 + .../ProjectNameApplicationMapperProfile.cs | 10 + .../ProjectNameApplicationModule.cs | 27 + ...ojectNameDynamicQueryableAppServiceBase.cs | 19 + .../Expressions/ExpressionFuncExtensions.cs | 32 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...CompanyName.ProjectName.Dapr.Client.csproj | 19 + .../ProjectNameDaprClientModule.cs | 18 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...mpanyName.ProjectName.Domain.Shared.csproj | 30 + .../Localization/ProjectNameResource.cs | 8 + .../Localization/Resources/en.json | 8 + .../Localization/Resources/zh-Hans.json | 8 + ...ProjectNameModuleExtensionConfiguration.cs | 16 + ...ensionConfigurationDictionaryExtensions.cs | 19 + .../ProjectNameModuleExtensionConsts.cs | 11 + .../ProjectNameDomainSharedModule.cs | 32 + .../ProjectName/ProjectNameErrorCodes.cs | 6 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...Name.CompanyName.ProjectName.Domain.csproj | 27 + .../IProjectNameBasicRepository.cs | 54 + .../ProjectName/ProjectNameDbProperties.cs | 11 + .../ProjectNameDomainMapperProfile.cs | 11 + .../ProjectName/ProjectNameDomainModule.cs | 45 + .../ProjectNameSettingDefinitionProvider.cs | 10 + .../Settings/ProjectNameSettings.cs | 6 + .../Expressions/ExpressionFuncExtensions.cs | 32 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...ame.ProjectName.EntityFrameworkCore.csproj | 37 + .../EfCoreProjectNameRepository.cs | 77 ++ .../IProjectNameDbContext.cs | 9 + .../ProjectNameDbContext.cs | 21 + .../ProjectNameDbContextFactory.cs | 49 + ...ectNameDbContextModelCreatingExtensions.cs | 21 + .../ProjectNameDbMigrationEventHandler.cs | 36 + .../ProjectNameDbMigrationService.cs | 58 ++ .../ProjectNameEfCoreQueryableExtensions.cs | 6 + .../ProjectNameEntityFrameworkCoreModule.cs | 76 ++ ...ectNameModelBuilderConfigurationOptions.cs | 17 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...panyName.ProjectName.HttpApi.Client.csproj | 24 + .../ProjectNameHttpApiClientModule.cs | 18 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...ame.CompanyName.ProjectName.HttpApi.csproj | 25 + .../ProjectName/ProjectNameControllerBase.cs | 12 + ...ojectNameDynamicQueryableControllerBase.cs | 17 + .../ProjectName/ProjectNameHttpApiModule.cs | 42 + .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 + ...yName.ProjectName.SettingManagement.csproj | 27 + .../IProjectNameSettingAppService.cs | 7 + .../ProjectNameSettingAppService.cs | 106 ++ .../ProjectNameSettingController.cs | 69 ++ .../ProjectNameSettingManagementModule.cs | 22 + ...yName.ProjectName.Application.Tests.csproj | 18 + .../ProjectNameApplicationTestBase.cs | 5 + .../ProjectNameApplicationTestModule.cs | 11 + ...ompanyName.ProjectName.Domain.Tests.csproj | 18 + .../ProjectName/ProjectNameDomainTestBase.cs | 5 + .../ProjectNameDomainTestModule.cs | 11 + ...ojectName.EntityFrameworkCore.Tests.csproj | 19 + .../ProjectNameEntityFrameworkCoreTestBase.cs | 5 + ...rojectNameEntityFrameworkCoreTestModule.cs | 38 + ...me.CompanyName.ProjectName.TestBase.csproj | 23 + .../ProjectName/ProjectNameTestBase.cs | 58 ++ .../ProjectName/ProjectNameTestBaseModule.cs | 24 + 177 files changed, 9770 insertions(+) create mode 100644 aspnet-core/templates/aio/PackageName.CompanyName.ProjectName.AIO.csproj create mode 100644 aspnet-core/templates/aio/content/.template.config/template.json create mode 100644 aspnet-core/templates/aio/content/.template.config/template.zh-Hans.json create mode 100644 aspnet-core/templates/aio/content/Directory.Build.props create mode 100644 aspnet-core/templates/aio/content/Directory.Packages.props create mode 100644 aspnet-core/templates/aio/content/NuGet.Config create mode 100644 aspnet-core/templates/aio/content/README.md create mode 100644 aspnet-core/templates/aio/content/README.zh-CN.md create mode 100644 aspnet-core/templates/aio/content/common.props create mode 100644 aspnet-core/templates/aio/content/configureawait.props create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.config/dotnet-tools.json create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.gitignore create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Authentication/AbpCookieAuthenticationHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJob.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJobArgs.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/HomeController.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/SettingMergeController.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/UserSettingMergeController.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Dockerfile create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/ChatMessageEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/NotificationEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/TenantSynchronizer.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/UserCreateEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/WebhooksEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateJoinIMEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateSendWelcomeEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/IdentityResources/CustomIdentityResources.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.Configure.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/ITenantConfigurationCache.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCache.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCacheItem.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/PackageName.CompanyName.ProjectName.AIO.Host.csproj create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/TwoFactorSupportedLoginModel.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/_ViewImports.cshtml create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Program.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Properties/launchSettings.json create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/TenantHeaderParamter.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/TextMessageReplyContributor.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/UserSubscribeEventContributor.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Work/Messages/TextMessageReplyContributor.cs create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.Development.json create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.json create mode 100644 aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/gulpfile.js create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/PackageName.CompanyName.ProjectName.AIO.DbMigrator.csproj create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Program.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.EN.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorHostedService.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.Configure.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Usings.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.MySql.json create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.PostgreSql.json create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.SqlServer.json create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.json create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql.csproj create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.en.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsDbContextFactory.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsEntityFrameworkCoreMySqlModule.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.csproj create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.EN.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.md create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationService.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsDbContext.cs create mode 100644 aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName.CompanyName.ProjectName.Application.Contracts.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureDefinitionProvider.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureNames.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/IProjectNameDynamicQueryableAppService.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameApplicationContractsModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameRemoteServiceConsts.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameAppServiceBase.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableAppServiceBase.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/System/Linq/Expressions/ExpressionFuncExtensions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName.CompanyName.ProjectName.Dapr.Client.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName/CompanyName/ProjectName/ProjectNameDaprClientModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName.CompanyName.ProjectName.Domain.Shared.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/ProjectNameResource.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/en.json create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfiguration.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfigurationDictionaryExtensions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConsts.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameDomainSharedModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameErrorCodes.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/IProjectNameBasicRepository.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDbProperties.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainMapperProfile.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettingDefinitionProvider.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettings.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/System/Linq/Expressions/ExpressionFuncExtensions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/EfCoreProjectNameRepository.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextFactory.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextModelCreatingExtensions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationEventHandler.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationService.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEfCoreQueryableExtensions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameModelBuilderConfigurationOptions.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName.CompanyName.ProjectName.HttpApi.Client.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName/CompanyName/ProjectName/ProjectNameHttpApiClientModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName.CompanyName.ProjectName.HttpApi.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameControllerBase.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableControllerBase.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameHttpApiModule.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xml create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xsd create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName.CompanyName.ProjectName.SettingManagement.csproj create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/IProjectNameSettingAppService.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingAppService.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingController.cs create mode 100644 aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingManagementModule.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName.CompanyName.ProjectName.Application.Tests.csproj create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestBase.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName.CompanyName.ProjectName.Domain.Tests.csproj create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestBase.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestModule.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests.csproj create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestBase.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestModule.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName.CompanyName.ProjectName.TestBase.csproj create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBase.cs create mode 100644 aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBaseModule.cs diff --git a/aspnet-core/templates/aio/PackageName.CompanyName.ProjectName.AIO.csproj b/aspnet-core/templates/aio/PackageName.CompanyName.ProjectName.AIO.csproj new file mode 100644 index 000000000..88bfabf98 --- /dev/null +++ b/aspnet-core/templates/aio/PackageName.CompanyName.ProjectName.AIO.csproj @@ -0,0 +1,30 @@ + + + net8.0 + true + LINGYUN.Abp.AllInOne.Templates + 8.3.0 + colin.in@foxmail.com + Abp framework all-in-one template + MIT + false + https://github.com/colinin/abp-next-admin + allinone webapi cloud + Template + git + https://github.com/colinin/abp-next-admin + true + true + true + true + False + False + + + + + true + content + + + diff --git a/aspnet-core/templates/aio/content/.template.config/template.json b/aspnet-core/templates/aio/content/.template.config/template.json new file mode 100644 index 000000000..b2a850a12 --- /dev/null +++ b/aspnet-core/templates/aio/content/.template.config/template.json @@ -0,0 +1,115 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "colin.in@foxmail.com", + "classifications": ["allinone", "webapi", "cloud"], + "name": "LINGYUN.Abp.AllInOne", + "identity": "LINGYUN.Abp.AllInOne", + "description": "Abp framework all-in-one template", + "groupIdentity": "LINGYUN.Abp.Application", + "shortName": "laa", + "tags": { + "language": "C#", + "type": "project" + }, + "sources": [ + { + "modifiers": [ + { + "exclude": [ + "**/[Bb]in/**", + "**/[Oo]bj/**", + "**/[Ll]ocalNuget/**", + ".template.config/**/*", + ".vs/**/*" + ] + } + ] + } + ], + "sourceName": "ProjectName", + "preferNameDirectory": true, + "symbols": { + "AuthenticationScheme": { + "type": "parameter", + "description": "Authentication Scheme", + "datatype": "choice", + "defaultValue": "IdentityServer4", + "isRequired": false, + "choices": [ + { + "choice": "IdentityServer4", + "description": "IdentityServer4" + }, + { + "choice": "OpenIddict", + "description": "OpenIddict" + } + ] + }, + "DatabaseManagement": { + "type": "parameter", + "description": "Database Management", + "dataType": "choice", + "defaultValue": "MySQL", + "isRequired": false, + "choices": [ + { + "choice": "SqlServer", + "description": "Sql Server" + }, + { + "choice": "MySQL", + "description": "My SQL" + }, + { + "choice": "Sqlite", + "description": "Sqlite" + }, + { + "choice": "Oracle", + "description": "Oracle" + }, + { + "choice": "OracleDevart", + "description": "Oracle Devart Driver" + }, + { + "choice": "PostgreSql", + "description": "Postgre Sql" + } + ] + }, + "SqlServer": { + "type": "computed", + "value": "(DatabaseManagement == \"SqlServer\")" + }, + "MySQL": { + "type": "computed", + "value": "(DatabaseManagement == \"MySQL\")" + }, + "Sqlite": { + "type": "computed", + "value": "(DatabaseManagement == \"Sqlite\")" + }, + "Oracle": { + "type": "computed", + "value": "(DatabaseManagement == \"Oracle\")" + }, + "OracleDevart": { + "type": "computed", + "value": "(DatabaseManagement == \"Oracle.Devart\")" + }, + "PostgreSql": { + "type": "computed", + "value": "(DatabaseManagement == \"PostgreSql\")" + }, + "IdentityServer4": { + "type": "computed", + "value": "(AuthenticationScheme == \"IdentityServer4\")" + }, + "OpenIddict": { + "type": "computed", + "value": "(AuthenticationScheme == \"OpenIddict\")" + } + } +} diff --git a/aspnet-core/templates/aio/content/.template.config/template.zh-Hans.json b/aspnet-core/templates/aio/content/.template.config/template.zh-Hans.json new file mode 100644 index 000000000..f0339e4e4 --- /dev/null +++ b/aspnet-core/templates/aio/content/.template.config/template.zh-Hans.json @@ -0,0 +1,5 @@ +{ + "description": "适用于abp框架的微服务模板项目", + "symbols/AuthenticationScheme/description": "认证服务体系, 可选项为: IdentityServer4、OpenIddict, 默认使用IdentityServer4.", + "symbols/DatabaseManagement/description": "数据库管理提供者, 可选项为: SqlServer、MySQL、Sqlite、Oracle、OracleDevart、PostgreSql, 默认使用MySQL." +} diff --git a/aspnet-core/templates/aio/content/Directory.Build.props b/aspnet-core/templates/aio/content/Directory.Build.props new file mode 100644 index 000000000..6f2501679 --- /dev/null +++ b/aspnet-core/templates/aio/content/Directory.Build.props @@ -0,0 +1,13 @@ + + + true + + + + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/aspnet-core/templates/aio/content/Directory.Packages.props b/aspnet-core/templates/aio/content/Directory.Packages.props new file mode 100644 index 000000000..433f06afb --- /dev/null +++ b/aspnet-core/templates/aio/content/Directory.Packages.props @@ -0,0 +1,490 @@ + + + 8.2.0 + 2.14.1 + 8.3.0 + 8.3.0 + 8.0.0 + 8.0.0 + 8.0.0 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/NuGet.Config b/aspnet-core/templates/aio/content/NuGet.Config new file mode 100644 index 000000000..ddde6944c --- /dev/null +++ b/aspnet-core/templates/aio/content/NuGet.Config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/README.md b/aspnet-core/templates/aio/content/README.md new file mode 100644 index 000000000..7b7520aef --- /dev/null +++ b/aspnet-core/templates/aio/content/README.md @@ -0,0 +1,129 @@ +# LINGYUN.Abp.Templates + +[English](README.md) | [中文](README.zh-CN.md) + +## Introduction + +LINGYUN.Abp.Templates provides two types of project templates based on ABP Framework: + +1. **Microservice Template**: A complete microservice architecture template with distributed services. +2. **All-in-One Template**: A single-application template that combines all services into one project. + +## Features + +### Common Features + +- Integrated authentication (IdentityServer4/OpenIddict) +- Database integration (multiple databases supported) +- Unified configuration management +- Distributed event bus support +- Background job processing + +### Microservice Template Features + +- Complete microservice project structure +- Service discovery and registration +- Distributed deployment support + +### All-in-One Template Features + +- Simplified deployment +- Easier maintenance +- Lower resource requirements + +## How to Use + +### Install Templates + +```bash +# Install Microservice Template +dotnet new install LINGYUN.Abp.MicroService.Templates + +# Install All-in-One Template +dotnet new install LINGYUN.Abp.AllInOne.Templates +``` + +### Create New Project + +#### For Microservice Project + +```bash +# Short name: lam (LINGYUN Abp Microservice) +dotnet new lam -n YourCompanyName.YourProjectName -pk YourPackageName -o /path/to/output --dbms MySql --cs "Server=127.0.0.1;Database=YourDatabase;User Id=your_user;Password=your_password;SslMode=None" --no-random-port +``` + +#### For All-in-One Project + +```bash +# Short name: laa (LINGYUN Abp AllInOne) +dotnet new laa -n YourCompanyName.YourProjectName -pk YourPackageName -o /path/to/output --dbms MySql --cs "Server=127.0.0.1;Database=YourDatabase;User Id=your_user;Password=your_password;SslMode=None" --no-random-port +``` + +## How to Run + +After creating your project, you can run it using the following command: + +### For Microservice Project + +```bash +cd /path/to/output/host/YourPackageName.YourCompanyName.YourProjectName.HttpApi.Host +dotnet run --launch-profile "YourPackageName.YourCompanyName.YourProjectName.Development" +``` + +### For All-in-One Project + +```bash +cd /path/to/output/host/YourPackageName.YourCompanyName.YourProjectName.AIO.Host +dotnet run --launch-profile "YourPackageName.YourCompanyName.YourProjectName.Development" +``` + +## How to Package and Publish + +1. Clone the Project + +```bash +git clone +cd /aspnet-core/templates/content +``` + +2. Modify Version + Edit the project files to update versions: + - For Microservice: `../PackageName.CompanyName.ProjectName.csproj` + - For All-in-One: `../PackageName.CompanyName.ProjectName.AIO.csproj` + +```xml +8.3.0 +``` + +3. Execute Packaging Script + +```powershell +# Windows PowerShell +.\pack.ps1 + +# PowerShell Core (Windows/Linux/macOS) +pwsh pack.ps1 +``` + +The script will prompt you to choose which template to package: + +1. Microservice Template +2. All-in-One Template +3. Both Templates + +## Supported Databases + +- SqlServer +- MySQL +- PostgreSQL +- Oracle +- SQLite + +## Notes + +- Ensure .NET SDK 8.0 or higher is installed +- Choose the appropriate template based on your needs: + - Microservice Template: For large-scale distributed applications + - All-in-One Template: For smaller applications or simpler deployment requirements +- Pay attention to NuGet publish address and key when packaging +- Complete testing is recommended before publishing diff --git a/aspnet-core/templates/aio/content/README.zh-CN.md b/aspnet-core/templates/aio/content/README.zh-CN.md new file mode 100644 index 000000000..b93d76220 --- /dev/null +++ b/aspnet-core/templates/aio/content/README.zh-CN.md @@ -0,0 +1,129 @@ +# LINGYUN.Abp.Templates + +[English](README.md) | [中文](README.zh-CN.md) + +## 简介 + +LINGYUN.Abp.Templates 基于 ABP Framework 提供两种项目模板: + +1. **微服务模板**:完整的分布式微服务架构模板 +2. **单体应用模板**:将所有服务集成到一个项目中的单体应用模板 + +## 特性 + +### 共同特性 + +- 集成身份认证(支持 IdentityServer4/OpenIddict) +- 数据库集成(支持多种数据库) +- 统一配置管理 +- 分布式事件总线支持 +- 后台作业处理 + +### 微服务模板特性 + +- 完整的微服务项目结构 +- 服务发现与注册 +- 支持分布式部署 + +### 单体应用模板特性 + +- 简化的部署流程 +- 更容易的维护 +- 更低的资源需求 + +## 使用方法 + +### 安装模板 + +```bash +# 安装微服务模板 +dotnet new install LINGYUN.Abp.MicroService.Templates + +# 安装单体应用模板 +dotnet new install LINGYUN.Abp.AllInOne.Templates +``` + +### 创建新项目 + +#### 创建微服务项目 + +```bash +# 简写名称:lam (LINGYUN Abp Microservice) +dotnet new lam -n YourCompanyName.YourProjectName -pk YourPackageName -o /path/to/output --dbms MySql --cs "Server=127.0.0.1;Database=YourDatabase;User Id=your_user;Password=your_password;SslMode=None" --no-random-port +``` + +#### 创建单体应用项目 + +```bash +# 简写名称:laa (LINGYUN Abp AllInOne) +labp create MyCompanyName.MyProjectName -pk MyPackageName -t laa -o /Users/feijie/Projects/Tests --dbms MySql --cs "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None" --no-random-port +``` + +## 运行项目 + +创建项目后,可以使用以下命令运行: + +### 运行微服务项目 + +```bash +cd /path/to/output/host/YourPackageName.YourCompanyName.YourProjectName.HttpApi.Host +dotnet run --launch-profile "YourPackageName.YourCompanyName.YourProjectName.Development" +``` + +### 运行单体应用项目 + +```bash +cd /path/to/output/host/YourPackageName.YourCompanyName.YourProjectName.AIO.Host +dotnet run --launch-profile "YourPackageName.YourCompanyName.YourProjectName.Development" +``` + +## 打包与发布 + +1. 克隆项目 + +```bash +git clone +cd /aspnet-core/templates/content +``` + +2. 修改版本号 + 编辑项目文件更新版本号: + - 微服务模板:`../PackageName.CompanyName.ProjectName.csproj` + - 单体应用模板:`../PackageName.CompanyName.ProjectName.AIO.csproj` + +```xml +8.3.0 +``` + +3. 执行打包脚本 + +```powershell +# Windows PowerShell +.\pack.ps1 + +# PowerShell Core (Windows/Linux/macOS) +pwsh pack.ps1 +``` + +脚本会提示您选择要打包的模板: + +1. 微服务模板 +2. 单体应用模板 +3. 两种模板都打包 + +## 支持的数据库 + +- SqlServer +- MySQL +- PostgreSQL +- Oracle +- SQLite + +## 注意事项 + +- 确保已安装 .NET SDK 8.0 或更高版本 +- 根据需求选择合适的模板: + - 微服务模板:适用于大规模分布式应用 + - 单体应用模板:适用于小型应用或简单部署需求 +- 打包时注意 NuGet 发布地址和密钥 +- 发布前建议进行完整测试 diff --git a/aspnet-core/templates/aio/content/common.props b/aspnet-core/templates/aio/content/common.props new file mode 100644 index 000000000..d7d7622cf --- /dev/null +++ b/aspnet-core/templates/aio/content/common.props @@ -0,0 +1,38 @@ + + + latest + 8.2.1 + colin + $(NoWarn);CS1591;CS0436;CS8618;NU1803 + https://github.com/colinin/abp-next-admin + $(SolutionDir)LocalNuget + 8.2.1 + MIT + git + https://github.com/colinin/abp-next-admin + true + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)LocalNuget + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/configureawait.props b/aspnet-core/templates/aio/content/configureawait.props new file mode 100644 index 000000000..3caa88c04 --- /dev/null +++ b/aspnet-core/templates/aio/content/configureawait.props @@ -0,0 +1,9 @@ + + + + + All + runtime; build; native; contentfiles; analyzers + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.config/dotnet-tools.json b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.config/dotnet-tools.json new file mode 100644 index 000000000..6b93cca86 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "7.0.3", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.gitignore b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.gitignore new file mode 100644 index 000000000..7b6f60857 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/.gitignore @@ -0,0 +1,2 @@ +wwwroot +package*.json \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Authentication/AbpCookieAuthenticationHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Authentication/AbpCookieAuthenticationHandler.cs new file mode 100644 index 000000000..34517090f --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Authentication/AbpCookieAuthenticationHandler.cs @@ -0,0 +1,89 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Options; +using System.Text.Encodings.Web; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Authentication; + +public class AbpCookieAuthenticationHandler : CookieAuthenticationHandler +{ + public AbpCookieAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) : base(options, logger, encoder) + { + } + + public AbpCookieAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected const string XRequestFromHeader = "X-Request-From"; + protected const string DontRedirectRequestFromHeader = "vben"; + protected override Task InitializeEventsAsync() + { + var events = new CookieAuthenticationEvents + { + OnRedirectToLogin = ctx => + { + if (string.Equals(ctx.Request.Headers[XRequestFromHeader], DontRedirectRequestFromHeader, StringComparison.Ordinal)) + { + // ctx.Response.Headers.Location = ctx.RedirectUri; + ctx.Response.StatusCode = 401; + } + else + { + ctx.Response.Redirect(ctx.RedirectUri); + } + return Task.CompletedTask; + }, + OnRedirectToAccessDenied = ctx => + { + if (string.Equals(ctx.Request.Headers[XRequestFromHeader], DontRedirectRequestFromHeader, StringComparison.Ordinal)) + { + // ctx.Response.Headers.Location = ctx.RedirectUri; + ctx.Response.StatusCode = 401; + } + else + { + ctx.Response.Redirect(ctx.RedirectUri); + } + return Task.CompletedTask; + }, + OnRedirectToLogout = ctx => + { + if (string.Equals(ctx.Request.Headers[XRequestFromHeader], DontRedirectRequestFromHeader, StringComparison.Ordinal)) + { + // ctx.Response.Headers.Location = ctx.RedirectUri; + ctx.Response.StatusCode = 401; + } + else + { + ctx.Response.Redirect(ctx.RedirectUri); + } + return Task.CompletedTask; + }, + OnRedirectToReturnUrl = ctx => + { + if (string.Equals(ctx.Request.Headers[XRequestFromHeader], DontRedirectRequestFromHeader, StringComparison.Ordinal)) + { + // ctx.Response.Headers.Location = ctx.RedirectUri; + ctx.Response.StatusCode = 401; + } + else + { + ctx.Response.Redirect(ctx.RedirectUri); + } + return Task.CompletedTask; + } + }; + + Events = events; + + return Task.CompletedTask; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJob.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJob.cs new file mode 100644 index 000000000..c9db9977a --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJob.cs @@ -0,0 +1,38 @@ +using LINGYUN.Abp.Notifications; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.BackgroundJobs; + +public class NotificationPublishJob : AsyncBackgroundJob, ITransientDependency +{ + protected AbpNotificationsPublishOptions Options { get; } + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected INotificationDataSerializer NotificationDataSerializer { get; } + public NotificationPublishJob( + IOptions options, + IServiceScopeFactory serviceScopeFactory, + INotificationDataSerializer notificationDataSerializer) + { + Options = options.Value; + ServiceScopeFactory = serviceScopeFactory; + NotificationDataSerializer = notificationDataSerializer; + } + + public override async Task ExecuteAsync(NotificationPublishJobArgs args) + { + var providerType = Type.GetType(args.ProviderType); + using (var scope = ServiceScopeFactory.CreateScope()) + { + if (scope.ServiceProvider.GetRequiredService(providerType) is INotificationPublishProvider publishProvider) + { + var store = scope.ServiceProvider.GetRequiredService(); + var notification = await store.GetNotificationOrNullAsync(args.TenantId, args.NotificationId); + notification.Data = NotificationDataSerializer.Serialize(notification.Data); + + await publishProvider.PublishAsync(notification, args.UserIdentifiers); + } + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJobArgs.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJobArgs.cs new file mode 100644 index 000000000..e5f077d65 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/BackgroundJobs/NotificationPublishJobArgs.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.Notifications; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.BackgroundJobs; + +public class NotificationPublishJobArgs +{ + public Guid? TenantId { get; set; } + public long NotificationId { get; set; } + public string ProviderType { get; set; } + public List UserIdentifiers { get; set; } + public NotificationPublishJobArgs() + { + UserIdentifiers = new List(); + } + public NotificationPublishJobArgs(long id, string providerType, List userIdentifiers, Guid? tenantId = null) + { + NotificationId = id; + ProviderType = providerType; + UserIdentifiers = userIdentifiers; + TenantId = tenantId; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/HomeController.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/HomeController.cs new file mode 100644 index 000000000..e693a0917 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/HomeController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Controllers; + +public class HomeController : Controller +{ + public IActionResult Index() + { + return Redirect("/swagger"); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/SettingMergeController.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/SettingMergeController.cs new file mode 100644 index 000000000..11e91bd88 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/SettingMergeController.cs @@ -0,0 +1,70 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Controllers; + +[ExposeServices( + typeof(SettingController), + typeof(SettingMergeController))] +public class SettingMergeController : SettingController +{ + private readonly SettingManagementMergeOptions _mergeOptions; + public SettingMergeController( + ISettingAppService settingAppService, + ISettingTestAppService settingTestAppService, + IOptions mergeOptions) + : base(settingAppService, settingTestAppService) + { + _mergeOptions = mergeOptions.Value; + } + + [HttpGet] + [Route("by-current-tenant")] + public async override Task GetAllForCurrentTenantAsync() + { + var result = new SettingGroupResult(); + var markTypeMap = new List + { + typeof(SettingMergeController), + }; + foreach (var serviceType in _mergeOptions.GlobalSettingProviders + .Where(type => !markTypeMap.Any(markType => type.IsAssignableFrom(markType)))) + { + var settingService = LazyServiceProvider.LazyGetRequiredService(serviceType).As(); + var currentResult = await settingService.GetAllForCurrentTenantAsync(); + foreach (var group in currentResult.Items) + { + result.AddGroup(group); + } + markTypeMap.Add(serviceType); + } + + return result; + } + + [HttpGet] + [Route("by-global")] + public async override Task GetAllForGlobalAsync() + { + var result = new SettingGroupResult(); + var markTypeMap = new List + { + typeof(SettingMergeController), + }; + foreach (var serviceType in _mergeOptions.GlobalSettingProviders + .Where(type => !markTypeMap.Any(markType => type.IsAssignableFrom(markType)))) + { + var settingService = LazyServiceProvider.LazyGetRequiredService(serviceType).As(); + var currentResult = await settingService.GetAllForGlobalAsync(); + foreach (var group in currentResult.Items) + { + result.AddGroup(group); + } + markTypeMap.Add(serviceType); + } + + return result; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/UserSettingMergeController.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/UserSettingMergeController.cs new file mode 100644 index 000000000..5091a8a5e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Controllers/UserSettingMergeController.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Controllers; + +[ExposeServices( + typeof(UserSettingController), + typeof(UserSettingMergeController))] +public class UserSettingMergeController : UserSettingController +{ + private readonly SettingManagementMergeOptions _mergeOptions; + public UserSettingMergeController( + IUserSettingAppService service, + IOptions mergeOptions) + : base(service) + { + _mergeOptions = mergeOptions.Value; + } + + [HttpGet] + [Route("by-current-user")] + public async override Task GetAllForCurrentUserAsync() + { + var result = new SettingGroupResult(); + var markTypeMap = new List + { + typeof(UserSettingMergeController), + }; + foreach (var serviceType in _mergeOptions.UserSettingProviders + .Where(type => !markTypeMap.Any(markType => type.IsAssignableFrom(markType)))) + { + var settingService = LazyServiceProvider.LazyGetRequiredService(serviceType).As(); + var currentResult = await settingService.GetAllForCurrentUserAsync(); + foreach (var group in currentResult.Items) + { + result.AddGroup(group); + } + markTypeMap.Add(serviceType); + } + + return result; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Dockerfile b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Dockerfile new file mode 100644 index 000000000..aee09fd66 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +LABEL maintainer="colin.in@foxmail.com" +WORKDIR /app + +COPY . /app + +#东8区 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone + +EXPOSE 80/tcp +VOLUME [ "./app/blobs" ] +VOLUME [ "./app/Logs" ] +VOLUME [ "./app/Modules" ] + +RUN apt update +RUN apt install wget -y + +ENTRYPOINT ["dotnet", "LY.MicroService.Applications.Single.dll"] diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/ChatMessageEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/ChatMessageEventHandler.cs new file mode 100644 index 000000000..a08702a0a --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/ChatMessageEventHandler.cs @@ -0,0 +1,59 @@ +using LINGYUN.Abp.IM; +using LINGYUN.Abp.IM.Messages; +using LINGYUN.Abp.RealTime; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Distributed +{ + public class ChatMessageEventHandler : IDistributedEventHandler>, ITransientDependency + { + /// + /// Reference to . + /// + public ILogger Logger { get; set; } + /// + /// Reference to . + /// + protected AbpIMOptions Options { get; } + + protected IMessageStore MessageStore { get; } + protected IMessageBlocker MessageBlocker { get; } + protected IMessageSenderProviderManager MessageSenderProviderManager { get; } + + public ChatMessageEventHandler( + IOptions options, + IMessageStore messageStore, + IMessageBlocker messageBlocker, + IMessageSenderProviderManager messageSenderProviderManager) + { + Options = options.Value; + MessageStore = messageStore; + MessageBlocker = messageBlocker; + MessageSenderProviderManager = messageSenderProviderManager; + + Logger = NullLogger.Instance; + } + + public async virtual Task HandleEventAsync(RealTimeEto eventData) + { + Logger.LogDebug($"Persistent chat message."); + + var message = eventData.Data; + // 消息拦截 + // 扩展敏感词汇过滤 + await MessageBlocker.InterceptAsync(message); + + await MessageStore.StoreMessageAsync(message); + + // 发送消息 + foreach (var provider in MessageSenderProviderManager.Providers) + { + Logger.LogDebug($"Sending message with provider {provider.Name}"); + await provider.SendMessageAsync(message); + } + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/NotificationEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/NotificationEventHandler.cs new file mode 100644 index 000000000..cb871c6ef --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/NotificationEventHandler.cs @@ -0,0 +1,470 @@ +using LINGYUN.Abp.Notifications; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using PackageName.CompanyName.ProjectName.AIO.Host.BackgroundJobs; +using PackageName.CompanyName.ProjectName.AIO.Host.MultiTenancy; +using System.Globalization; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Json; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TextTemplating; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Distributed +{ + /// + /// 订阅通知发布事件,统一发布消息 + /// + /// + /// 作用在于SignalR客户端只会与一台服务器建立连接, + /// 只有启用了SignlR服务端的才能真正将消息发布到客户端 + /// + public class NotificationEventHandler : + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency + { + /// + /// Reference to . + /// + public ILogger Logger { get; set; } + /// + /// Reference to . + /// + protected AbpNotificationsPublishOptions Options { get; } + /// + /// Reference to . + /// + protected ICurrentTenant CurrentTenant { get; } + /// + /// Reference to . + /// + protected ITenantConfigurationCache TenantConfigurationCache { get; } + /// + /// Reference to . + /// + protected IJsonSerializer JsonSerializer { get; } + /// + /// Reference to . + /// + protected IBackgroundJobManager BackgroundJobManager { get; } + /// + /// Reference to . + /// + protected ITemplateRenderer TemplateRenderer { get; } + /// + /// Reference to . + /// + protected INotificationStore NotificationStore { get; } + /// + /// Reference to . + /// + protected IStringLocalizerFactory StringLocalizerFactory { get; } + /// + /// Reference to . + /// + protected INotificationDataSerializer NotificationDataSerializer { get; } + /// + /// Reference to . + /// + protected INotificationDefinitionManager NotificationDefinitionManager { get; } + /// + /// Reference to . + /// + protected INotificationSubscriptionManager NotificationSubscriptionManager { get; } + /// + /// Reference to . + /// + protected INotificationPublishProviderManager NotificationPublishProviderManager { get; } + + /// + /// Initializes a new instance of the class. + /// + public NotificationEventHandler( + ICurrentTenant currentTenant, + ITenantConfigurationCache tenantConfigurationCache, + IJsonSerializer jsonSerializer, + ITemplateRenderer templateRenderer, + IBackgroundJobManager backgroundJobManager, + IStringLocalizerFactory stringLocalizerFactory, + IOptions options, + INotificationStore notificationStore, + INotificationDataSerializer notificationDataSerializer, + INotificationDefinitionManager notificationDefinitionManager, + INotificationSubscriptionManager notificationSubscriptionManager, + INotificationPublishProviderManager notificationPublishProviderManager) + { + Options = options.Value; + TenantConfigurationCache = tenantConfigurationCache; + CurrentTenant = currentTenant; + JsonSerializer = jsonSerializer; + TemplateRenderer = templateRenderer; + BackgroundJobManager = backgroundJobManager; + StringLocalizerFactory = stringLocalizerFactory; + NotificationStore = notificationStore; + NotificationDataSerializer = notificationDataSerializer; + NotificationDefinitionManager = notificationDefinitionManager; + NotificationSubscriptionManager = notificationSubscriptionManager; + NotificationPublishProviderManager = notificationPublishProviderManager; + + Logger = NullLogger.Instance; + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(NotificationEto eventData) + { + var notification = await NotificationDefinitionManager.GetOrNullAsync(eventData.Name); + if (notification == null) + { + return; + } + + var culture = eventData.Data.Culture; + if (culture.IsNullOrWhiteSpace()) + { + culture = CultureInfo.CurrentCulture.Name; + } + using (CultureHelper.Use(culture, culture)) + { + if (notification.NotificationType == NotificationType.System) + { + using (CurrentTenant.Change(null)) + { + await SendToTenantAsync(null, notification, eventData); + + var allActiveTenants = await TenantConfigurationCache.GetTenantsAsync(); + + foreach (var activeTenant in allActiveTenants) + { + await SendToTenantAsync(activeTenant.Id, notification, eventData); + } + } + } + else + { + await SendToTenantAsync(eventData.TenantId, notification, eventData); + } + } + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(NotificationEto eventData) + { + var notification = await NotificationDefinitionManager.GetOrNullAsync(eventData.Name); + if (notification == null) + { + return; + } + + if (notification.NotificationType == NotificationType.System) + { + using (CurrentTenant.Change(null)) + { + await SendToTenantAsync(null, notification, eventData); + + var allActiveTenants = await TenantConfigurationCache.GetTenantsAsync(); + + foreach (var activeTenant in allActiveTenants) + { + await SendToTenantAsync(activeTenant.Id, notification, eventData); + } + } + } + else + { + await SendToTenantAsync(eventData.TenantId, notification, eventData); + } + } + + protected async virtual Task SendToTenantAsync( + Guid? tenantId, + NotificationDefinition notification, + NotificationEto eventData) + { + using (CurrentTenant.Change(tenantId)) + { + var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers); + + // 过滤用户指定提供者 + if (eventData.UseProviders.Any()) + { + providers = providers.Where(p => eventData.UseProviders.Contains(p.Name)); + } + else if (notification.Providers.Any()) + { + providers = providers.Where(p => notification.Providers.Contains(p.Name)); + } + + var notificationInfo = new NotificationInfo + { + Name = notification.Name, + TenantId = tenantId, + Severity = eventData.Severity, + Type = notification.NotificationType, + ContentType = notification.ContentType, + CreationTime = eventData.CreationTime, + Lifetime = notification.NotificationLifetime, + }; + notificationInfo.SetId(eventData.Id); + + var title = notification.DisplayName.Localize(StringLocalizerFactory); + var message = ""; + + try + { + // 由于模板通知受租户影响, 格式化失败的消息将被丢弃. + message = await TemplateRenderer.RenderAsync( + templateName: eventData.Data.Name, + model: eventData.Data.ExtraProperties, + cultureName: eventData.Data.Culture, + globalContext: new Dictionary + { + // 模板不支持 $ 字符, 改为普通关键字 + { NotificationKeywords.Name, notification.Name }, + { NotificationKeywords.FormUser, eventData.Data.FormUser }, + { NotificationKeywords.Id, eventData.Id }, + { NotificationKeywords.Title, title.ToString() }, + { NotificationKeywords.CreationTime, eventData.CreationTime.ToString(Options.DateTimeFormat) }, + }); + } + catch(Exception ex) + { + Logger.LogWarning("Formatting template notification failed, message will be discarded, cause :{message}", ex.Message); + return; + } + + var notificationData = new NotificationData(); + notificationData.WriteStandardData( + title: title.ToString(), + message: message, + createTime: eventData.CreationTime, + formUser: eventData.Data.FormUser); + notificationData.ExtraProperties.AddIfNotContains(eventData.Data.ExtraProperties); + + notificationInfo.Data = notificationData; + + var subscriptionUsers = await GerSubscriptionUsersAsync( + notificationInfo.Name, + eventData.Users, + tenantId); + + await PersistentNotificationAsync( + notificationInfo, + subscriptionUsers, + providers); + + if (subscriptionUsers.Any()) + { + // 发布通知 + foreach (var provider in providers) + { + await PublishToSubscriberAsync(provider, notificationInfo, subscriptionUsers); + } + } + } + } + + protected async virtual Task SendToTenantAsync( + Guid? tenantId, + NotificationDefinition notification, + NotificationEto eventData) + { + using (CurrentTenant.Change(tenantId)) + { + var providers = Enumerable.Reverse(NotificationPublishProviderManager.Providers); + + // 过滤用户指定提供者 + if (eventData.UseProviders.Any()) + { + providers = providers.Where(p => eventData.UseProviders.Contains(p.Name)); + } + else if (notification.Providers.Any()) + { + providers = providers.Where(p => notification.Providers.Contains(p.Name)); + } + + var notificationInfo = new NotificationInfo + { + Name = notification.Name, + CreationTime = eventData.CreationTime, + Data = eventData.Data, + Severity = eventData.Severity, + Lifetime = notification.NotificationLifetime, + TenantId = tenantId, + Type = notification.NotificationType, + ContentType = notification.ContentType, + }; + notificationInfo.SetId(eventData.Id); + + notificationInfo.Data = NotificationDataSerializer.Serialize(notificationInfo.Data); + + // 获取用户订阅 + var subscriptionUsers = await GerSubscriptionUsersAsync( + notificationInfo.Name, + eventData.Users, + tenantId); + + // 持久化通知 + await PersistentNotificationAsync( + notificationInfo, + subscriptionUsers, + providers); + + if (subscriptionUsers.Any()) + { + // 发布订阅通知 + foreach (var provider in providers) + { + await PublishToSubscriberAsync(provider, notificationInfo, subscriptionUsers); + } + } + } + } + /// + /// 获取用户订阅列表 + /// + /// 通知名称 + /// 接收用户列表 + /// 租户标识 + /// 用户订阅列表 + protected async Task> GerSubscriptionUsersAsync( + string notificationName, + IEnumerable sendToUsers, + Guid? tenantId = null) + { + try + { + // 获取用户订阅列表 + var userSubscriptions = await NotificationSubscriptionManager.GetUsersSubscriptionsAsync( + tenantId, + notificationName, + sendToUsers); + + return userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName)); + } + catch(Exception ex) + { + Logger.LogWarning("Failed to get user subscription, message will not be received by the user, reason: {message}", ex.Message); + } + + return new List(); + } + /// + /// 持久化通知并返回订阅用户列表 + /// + /// 通知实体 + /// 订阅用户列表 + /// 通知发送提供者 + /// 返回订阅者列表 + protected async Task PersistentNotificationAsync( + NotificationInfo notificationInfo, + IEnumerable subscriptionUsers, + IEnumerable sendToProviders) + { + try + { + // 持久化通知 + await NotificationStore.InsertNotificationAsync(notificationInfo); + + if (!subscriptionUsers.Any()) + { + return; + } + + // 持久化用户通知 + await NotificationStore.InsertUserNotificationsAsync(notificationInfo, subscriptionUsers.Select(u => u.UserId)); + + if (notificationInfo.Lifetime == NotificationLifetime.OnlyOne) + { + // 一次性通知取消用户订阅 + await NotificationStore.DeleteUserSubscriptionAsync( + notificationInfo.TenantId, + subscriptionUsers, + notificationInfo.Name); + } + } + catch (Exception ex) + { + Logger.LogWarning("Failed to persistent notification failed, reason: {message}", ex.Message); + + foreach (var provider in sendToProviders) + { + // 处理持久化失败进入后台队列 + await ProcessingFailedToQueueAsync(provider, notificationInfo, subscriptionUsers); + } + } + } + /// + /// 发布订阅者通知 + /// + /// 通知发布者 + /// 通知信息 + /// 订阅用户列表 + /// + protected async Task PublishToSubscriberAsync( + INotificationPublishProvider provider, + NotificationInfo notificationInfo, + IEnumerable subscriptionUsers) + { + try + { + Logger.LogDebug($"Sending notification with provider {provider.Name}"); + + // 2024-10-10: 框架层面应该取消通知数据转换,而是交给提供商来实现 + //var notifacationDataMapping = Options.NotificationDataMappings + // .GetMapItemOrDefault(provider.Name, notificationInfo.Name); + //if (notifacationDataMapping != null) + //{ + // notificationInfo.Data = notifacationDataMapping.MappingFunc(notificationInfo.Data); + //} + + // 发布 + await provider.PublishAsync(notificationInfo, subscriptionUsers); + + Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful"); + } + catch (Exception ex) + { + Logger.LogWarning($"Send notification error with provider {provider.Name}"); + Logger.LogWarning($"Error message:{ex.Message}"); + Logger.LogDebug($"Failed to send notification {notificationInfo.Name}. Try to push notification to background job"); + // 发送失败的消息进入后台队列 + await ProcessingFailedToQueueAsync(provider, notificationInfo, subscriptionUsers); + } + } + /// + /// 处理失败的消息进入后台队列 + /// + /// + /// 注: 如果入队失败,消息将被丢弃. + /// + /// + /// + /// + /// + protected async Task ProcessingFailedToQueueAsync( + INotificationPublishProvider provider, + NotificationInfo notificationInfo, + IEnumerable subscriptionUsers) + { + try + { + // 发送失败的消息进入后台队列 + await BackgroundJobManager.EnqueueAsync( + new NotificationPublishJobArgs( + notificationInfo.GetId(), + provider.GetType().AssemblyQualifiedName, + subscriptionUsers.ToList(), + notificationInfo.TenantId)); + } + catch(Exception ex) + { + Logger.LogWarning("Failed to push to background job, notification will be discarded, error cause: {message}", ex.Message); + } + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/TenantSynchronizer.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/TenantSynchronizer.cs new file mode 100644 index 000000000..3e07d5be9 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/TenantSynchronizer.cs @@ -0,0 +1,53 @@ +using LINGYUN.Abp.Saas.Tenants; +using PackageName.CompanyName.ProjectName.AIO.Host.MultiTenancy; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Distributed +{ + public class TenantSynchronizer : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler, + ITransientDependency + { + protected IDataSeeder DataSeeder { get; } + protected ITenantConfigurationCache TenantConfigurationCache { get; } + + public TenantSynchronizer( + IDataSeeder dataSeeder, + ITenantConfigurationCache tenantConfigurationCache) + { + DataSeeder = dataSeeder; + TenantConfigurationCache = tenantConfigurationCache; + } + + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await TenantConfigurationCache.RefreshAsync(); + + await DataSeeder.SeedAsync(eventData.Entity.Id); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await TenantConfigurationCache.RefreshAsync(); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await TenantConfigurationCache.RefreshAsync(); + } + + public async virtual Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData) + { + await TenantConfigurationCache.RefreshAsync(); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/UserCreateEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/UserCreateEventHandler.cs new file mode 100644 index 000000000..ee5ab700b --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/UserCreateEventHandler.cs @@ -0,0 +1,30 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Local; +using Volo.Abp.Users; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Distributed +{ + public class UserCreateEventHandler : IDistributedEventHandler>, ITransientDependency + { + private readonly ILocalEventBus _localEventBus; + public UserCreateEventHandler( + ILocalEventBus localEventBus) + { + _localEventBus = localEventBus; + } + /// + /// 接收添加用户事件,发布本地事件 + /// + /// + /// + public async Task HandleEventAsync(EntityCreatedEto eventData) + { + var localUserCreateEventData = new EntityCreatedEventData(eventData.Entity); + + await _localEventBus.PublishAsync(localUserCreateEventData); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/WebhooksEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/WebhooksEventHandler.cs new file mode 100644 index 000000000..eeb0fcdb7 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Distributed/WebhooksEventHandler.cs @@ -0,0 +1,112 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.Webhooks.EventBus; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Distributed; + +public class WebhooksEventHandler : + IDistributedEventHandler, + ITransientDependency +{ + public IWebhookEventStore WebhookEventStore { get; set; } + + private readonly ICurrentTenant _currentTenant; + private readonly IBackgroundJobManager _backgroundJobManager; + private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; + + public WebhooksEventHandler( + IWebhookSubscriptionManager webhookSubscriptionManager, + ICurrentTenant currentTenant, + IBackgroundJobManager backgroundJobManager) + { + _currentTenant = currentTenant; + _backgroundJobManager = backgroundJobManager; + _webhookSubscriptionManager = webhookSubscriptionManager; + + WebhookEventStore = NullWebhookEventStore.Instance; + } + + public async virtual Task HandleEventAsync(WebhooksEventData eventData) + { + var subscriptions = await _webhookSubscriptionManager + .GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync( + eventData.TenantIds, + eventData.WebhookName); + + await PublishAsync(eventData.WebhookName, eventData.Data, subscriptions, eventData.SendExactSameData, eventData.Headers); + } + + protected async virtual Task PublishAsync( + string webhookName, + string data, + List webhookSubscriptions, + bool sendExactSameData = false, + WebhookHeader headers = null) + { + if (webhookSubscriptions.IsNullOrEmpty()) + { + return; + } + + var subscriptionsGroupedByTenant = webhookSubscriptions.GroupBy(x => x.TenantId); + + foreach (var subscriptionGroupedByTenant in subscriptionsGroupedByTenant) + { + var webhookInfo = await SaveAndGetWebhookAsync(subscriptionGroupedByTenant.Key, webhookName, data); + + foreach (var webhookSubscription in subscriptionGroupedByTenant) + { + var headersToSend = webhookSubscription.Headers; + if (headers != null) + { + if (headers.UseOnlyGivenHeaders)//do not use the headers defined in subscription + { + headersToSend = headers.Headers; + } + else + { + //use the headers defined in subscription. If additional headers has same header, use additional headers value. + foreach (var additionalHeader in headers.Headers) + { + headersToSend[additionalHeader.Key] = additionalHeader.Value; + } + } + } + + await _backgroundJobManager.EnqueueAsync(new WebhookSenderArgs + { + TenantId = webhookSubscription.TenantId, + WebhookEventId = webhookInfo.Id, + Data = webhookInfo.Data, + WebhookName = webhookInfo.WebhookName, + WebhookSubscriptionId = webhookSubscription.Id, + Headers = headersToSend, + Secret = webhookSubscription.Secret, + WebhookUri = webhookSubscription.WebhookUri, + SendExactSameData = sendExactSameData + }); + } + } + } + + protected async virtual Task SaveAndGetWebhookAsync( + Guid? tenantId, + string webhookName, + string data) + { + var webhookInfo = new WebhookEvent + { + WebhookName = webhookName, + Data = data, + TenantId = tenantId + }; + + var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo); + webhookInfo.Id = webhookId; + + return webhookInfo; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateJoinIMEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateJoinIMEventHandler.cs new file mode 100644 index 000000000..2b78e5841 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateJoinIMEventHandler.cs @@ -0,0 +1,58 @@ +using LINGYUN.Abp.MessageService.Chat; +using LINGYUN.Abp.MessageService.Notifications; +using LINGYUN.Abp.Notifications; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; +using Volo.Abp.Uow; +using Volo.Abp.Users; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Local +{ + public class UserCreateJoinIMEventHandler : ILocalEventHandler>, ITransientDependency + { + private readonly IChatDataSeeder _chatDataSeeder; + private readonly INotificationSubscriptionManager _notificationSubscriptionManager; + public UserCreateJoinIMEventHandler( + IChatDataSeeder chatDataSeeder, + INotificationSubscriptionManager notificationSubscriptionManager) + { + _chatDataSeeder = chatDataSeeder; + _notificationSubscriptionManager = notificationSubscriptionManager; + } + /// + /// 接收添加用户事件,初始化IM用户种子 + /// + /// + /// + [UnitOfWork] + public async virtual Task HandleEventAsync(EntityCreatedEventData eventData) + { + await SeedChatDataAsync(eventData.Entity); + + await SeedUserSubscriptionNotifiersAsync(eventData.Entity); + } + + protected async virtual Task SeedChatDataAsync(IUserData user) + { + await _chatDataSeeder.SeedAsync(user); + } + + protected async virtual Task SeedUserSubscriptionNotifiersAsync(IUserData user) + { + var userIdentifier = new UserIdentifier(user.Id, user.UserName); + + await _notificationSubscriptionManager + .SubscribeAsync( + user.TenantId, + userIdentifier, + MessageServiceNotificationNames.IM.FriendValidation); + + await _notificationSubscriptionManager + .SubscribeAsync( + user.TenantId, + userIdentifier, + MessageServiceNotificationNames.IM.NewFriend); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateSendWelcomeEventHandler.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateSendWelcomeEventHandler.cs new file mode 100644 index 000000000..8176df498 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/EventBus/Local/UserCreateSendWelcomeEventHandler.cs @@ -0,0 +1,69 @@ +using LINGYUN.Abp.Notifications; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; +using Volo.Abp.Users; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.EventBus.Local +{ + public class UserCreateSendWelcomeEventHandler : ILocalEventHandler>, ITransientDependency + { + private readonly INotificationSender _notificationSender; + private readonly INotificationSubscriptionManager _notificationSubscriptionManager; + public UserCreateSendWelcomeEventHandler( + INotificationSender notificationSender, + INotificationSubscriptionManager notificationSubscriptionManager + ) + { + _notificationSender = notificationSender; + _notificationSubscriptionManager = notificationSubscriptionManager; + } + + public async Task HandleEventAsync(EntityCreatedEventData eventData) + { + var userIdentifer = new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName); + // 订阅用户欢迎消息 + await SubscribeInternalNotifers(userIdentifer, eventData.Entity.TenantId); + + await _notificationSender.SendNofiterAsync( + UserNotificationNames.WelcomeToApplication, + new NotificationTemplate( + UserNotificationNames.WelcomeToApplication, + formUser: eventData.Entity.UserName, + data: new Dictionary + { + { "name", eventData.Entity.UserName }, + }), + userIdentifer, + eventData.Entity.TenantId, + NotificationSeverity.Info); + } + + private async Task SubscribeInternalNotifers(UserIdentifier userIdentifer, Guid? tenantId = null) + { + // 订阅内置通知 + await _notificationSubscriptionManager + .SubscribeAsync( + tenantId, + userIdentifer, + DefaultNotifications.SystemNotice); + await _notificationSubscriptionManager + .SubscribeAsync( + tenantId, + userIdentifer, + DefaultNotifications.OnsideNotice); + await _notificationSubscriptionManager + .SubscribeAsync( + tenantId, + userIdentifer, + DefaultNotifications.ActivityNotice); + + // 订阅用户欢迎消息 + await _notificationSubscriptionManager + .SubscribeAsync( + tenantId, + userIdentifer, + UserNotificationNames.WelcomeToApplication); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/IdentityResources/CustomIdentityResources.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/IdentityResources/CustomIdentityResources.cs new file mode 100644 index 000000000..99fce8c22 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/IdentityResources/CustomIdentityResources.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.Identity; +using IdentityServer4.Models; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.IdentityResources; + +public class CustomIdentityResources +{ + public class AvatarUrl : IdentityResource + { + public AvatarUrl() + { + Name = IdentityConsts.ClaimType.Avatar.Name; + DisplayName = IdentityConsts.ClaimType.Avatar.DisplayName; + Description = IdentityConsts.ClaimType.Avatar.Description; + Emphasize = true; + UserClaims = new string[] { IdentityConsts.ClaimType.Avatar.Name }; + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.Configure.cs new file mode 100644 index 000000000..cbc4e883a --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.Configure.cs @@ -0,0 +1,935 @@ +using Elsa; +using Elsa.Options; +using LINGYUN.Abp.Aliyun.Localization; +using LINGYUN.Abp.BackgroundTasks; +using LINGYUN.Abp.DataProtectionManagement; +using LINGYUN.Abp.ExceptionHandling; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.Exporter.MiniExcel; +using LINGYUN.Abp.Idempotent; +using LINGYUN.Abp.Identity.Session; +using LINGYUN.Abp.IdentityServer.IdentityResources; +using LINGYUN.Abp.Localization.CultureMap; +using LINGYUN.Abp.Notifications; +using LINGYUN.Abp.OpenIddict.AspNetCore.Session; +using LINGYUN.Abp.OpenIddict.LinkUser; +using LINGYUN.Abp.OpenIddict.Permissions; +using LINGYUN.Abp.OpenIddict.Portal; +using LINGYUN.Abp.OpenIddict.Sms; +using LINGYUN.Abp.OpenIddict.WeChat; +using LINGYUN.Abp.Saas; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.Tencent.Localization; +using LINGYUN.Abp.TextTemplating; +using LINGYUN.Abp.WebhooksManagement; +using LINGYUN.Abp.WeChat.Common.Messages.Handlers; +using LINGYUN.Abp.WeChat.Localization; +using LINGYUN.Abp.WeChat.Work; +using LINGYUN.Abp.Wrapper; +using LINGYUN.Platform.Localization; +using PackageName.CompanyName.ProjectName.AIO.Host.Microsoft.Extensions.DependencyInjection; +using Medallion.Threading; +using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.IdentityModel.Logging; +using Microsoft.OpenApi.Models; +using MiniExcelLibs.Attributes; +using OpenIddict.Server; +using OpenIddict.Server.AspNetCore; +using PackageName.CompanyName.ProjectName.AIO.Host.Authentication; +using PackageName.CompanyName.ProjectName.AIO.Host.IdentityResources; +using PackageName.CompanyName.ProjectName.AIO.Host.WeChat.Official.Messages; +using Quartz; +using StackExchange.Redis; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text.Encodings.Web; +using System.Text.Unicode; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.AntiForgery; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Auditing; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.BlobStoring; +using Volo.Abp.BlobStoring.FileSystem; +using Volo.Abp.Caching; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.FeatureManagement; +using Volo.Abp.Features; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Http.Client; +using Volo.Abp.Identity.Localization; +using Volo.Abp.IdentityServer; +using Volo.Abp.IdentityServer.Localization; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.OpenIddict; +using Volo.Abp.OpenIddict.Localization; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Quartz; +using Volo.Abp.Security.Claims; +using Volo.Abp.SettingManagement; +using Volo.Abp.SettingManagement.Localization; +using Volo.Abp.Threading; +using Volo.Abp.UI.Navigation.Urls; +using Volo.Abp.VirtualFileSystem; +using VoloAbpExceptionHandlingOptions = Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingOptions; + +namespace PackageName.CompanyName.ProjectName.AIO.Host; + +public partial class MicroServiceApplicationsSingleModule +{ + protected const string DefaultCorsPolicyName = "Default"; + public static string ApplicationName { get; set; } = "MicroService-Applications-Single"; + private readonly static OneTimeRunner OneTimeRunner = new(); + + private void PreConfigureFeature() + { + OneTimeRunner.Run(() => + { + GlobalFeatureManager.Instance.Modules.Editions().EnableAll(); + }); + } + + private void PreConfigureApp(IConfiguration configuration) + { + AbpSerilogEnrichersConsts.ApplicationName = ApplicationName; + + PreConfigure(options => + { + // 以开放端口区别,应在0-31之间 + options.SnowflakeIdOptions.WorkerId = 1; + options.SnowflakeIdOptions.WorkerIdBits = 5; + options.SnowflakeIdOptions.DatacenterId = 1; + }); + + if (configuration.GetValue("App:ShowPii")) + { + IdentityModelEventSource.ShowPII = true; + } + } + + private void PreConfigureAuthServer(IConfiguration configuration) + { + PreConfigure(builder => + { + builder.AddValidation(options => + { + //options.AddAudiences("lingyun-abp-application"); + + options.UseLocalServer(); + + options.UseAspNetCore(); + + options.UseDataProtection(); + }); + }); + } + + private void PreConfigureIdentity() + { + PreConfigure(builder => + { + builder.AddDefaultTokenProviders(); + }); + } + + private void PreConfigureCertificate(IConfiguration configuration, IWebHostEnvironment environment) + { + var cerConfig = configuration.GetSection("Certificates"); + if (environment.IsProduction() && cerConfig.Exists()) + { + // 开发环境下存在证书配置 + // 且证书文件存在则使用自定义的证书文件来启动Ids服务器 + var cerPath = Path.Combine(environment.ContentRootPath, cerConfig["CerPath"]); + if (File.Exists(cerPath)) + { + var certificate = new X509Certificate2(cerPath, cerConfig["Password"]); + + if (configuration.GetValue("AuthServer:UseOpenIddict")) + { + PreConfigure(options => + { + //https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html + options.AddDevelopmentEncryptionAndSigningCertificate = false; + }); + + PreConfigure(builder => + { + builder.AddSigningCertificate(certificate); + builder.AddEncryptionCertificate(certificate); + + builder.UseDataProtection(); + + // 禁用https + builder.UseAspNetCore() + .DisableTransportSecurityRequirement(); + }); + } + else + { + PreConfigure(options => + { + options.AddDeveloperSigningCredential = false; + }); + + PreConfigure(builder => + { + builder.AddSigningCredential(certificate); + }); + } + } + } + else + { + if (configuration.GetValue("AuthServer:UseOpenIddict")) + { + PreConfigure(options => + { + //https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html + options.AddDevelopmentEncryptionAndSigningCertificate = false; + }); + + PreConfigure(builder => + { + //https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html + using (var algorithm = RSA.Create(keySizeInBits: 2048)) + { + var subject = new X500DistinguishedName("CN=Fabrikam Encryption Certificate"); + var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); + builder.AddSigningCertificate(certificate); + } + + using (var algorithm = RSA.Create(keySizeInBits: 2048)) + { + var subject = new X500DistinguishedName("CN=Fabrikam Signing Certificate"); + var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); + builder.AddEncryptionCertificate(certificate); + } + + builder.UseDataProtection(); + + // 禁用https + builder.UseAspNetCore() + .DisableTransportSecurityRequirement(); + }); + } + } + } + + private void PreConfigureQuartz(IConfiguration configuration) + { + PreConfigure(options => + { + // 如果使用持久化存储, 则配置quartz持久层 + if (configuration.GetSection("Quartz:UsePersistentStore").Get()) + { + var settings = configuration.GetSection("Quartz:Properties").Get>(); + if (settings != null) + { + foreach (var setting in settings) + { + options.Properties[setting.Key] = setting.Value; + } + } + + options.Configurator += (config) => + { + config.UsePersistentStore(store => + { + store.UseProperties = false; + store.UseNewtonsoftJsonSerializer(); + }); + }; + } + }); + } + + private void PreConfigureElsa(IServiceCollection services, IConfiguration configuration) + { + var elsaSection = configuration.GetSection("Elsa"); + var startups = new[] + { + typeof(Elsa.Activities.Console.Startup), + typeof(Elsa.Activities.Http.Startup), + typeof(Elsa.Activities.UserTask.Startup), + typeof(Elsa.Activities.Temporal.Quartz.Startup), + typeof(Elsa.Activities.Email.Startup), + typeof(Elsa.Scripting.JavaScript.Startup), + typeof(Elsa.Activities.Webhooks.Startup), + }; + + PreConfigure(elsa => + { + elsa + .AddActivitiesFrom() + .AddWorkflowsFrom() + .AddFeatures(startups, configuration) + .ConfigureWorkflowChannels(options => elsaSection.GetSection("WorkflowChannels").Bind(options)); + + elsa.DistributedLockingOptionsBuilder + .UseProviderFactory(sp => name => + { + var provider = sp.GetRequiredService(); + + return provider.CreateLock(name); + }); + }); + + services.AddNotificationHandlersFrom(); + + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(Elsa.Webhooks.Api.Endpoints.List).Assembly); + }); + } + + private void ConfigureAuthServer(IConfiguration configuration) + { + Configure(builder => + { + builder.DisableTransportSecurityRequirement(); + }); + + Configure(options => + { + options.DisableTransportSecurityRequirement = true; + }); + + Configure(options => + { + var lifetime = configuration.GetSection("OpenIddict:Lifetime"); + options.AuthorizationCodeLifetime = lifetime.GetValue("AuthorizationCode", options.AuthorizationCodeLifetime); + options.AccessTokenLifetime = lifetime.GetValue("AccessToken", options.AccessTokenLifetime); + options.DeviceCodeLifetime = lifetime.GetValue("DeviceCode", options.DeviceCodeLifetime); + options.IdentityTokenLifetime = lifetime.GetValue("IdentityToken", options.IdentityTokenLifetime); + options.RefreshTokenLifetime = lifetime.GetValue("RefreshToken", options.RefreshTokenLifetime); + options.RefreshTokenReuseLeeway = lifetime.GetValue("RefreshTokenReuseLeeway", options.RefreshTokenReuseLeeway); + options.UserCodeLifetime = lifetime.GetValue("UserCode", options.UserCodeLifetime); + }); + Configure(options => + { + options.PersistentSessionGrantTypes.Add(SmsTokenExtensionGrantConsts.GrantType); + options.PersistentSessionGrantTypes.Add(PortalTokenExtensionGrantConsts.GrantType); + options.PersistentSessionGrantTypes.Add(LinkUserTokenExtensionGrantConsts.GrantType); + options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.OfficialGrantType); + options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.MiniProgramGrantType); + options.PersistentSessionGrantTypes.Add(AbpWeChatWorkGlobalConsts.GrantType); + }); + } + + private void ConfigureEndpoints(IServiceCollection services) + { + // 不需要 + //Configure(options => + //{ + // options.EndpointConfigureActions.Add( + // (context) => + // { + // context.Endpoints.MapFallbackToPage("/_Host"); + // }); + //}); + var preActions = services.GetPreConfigureActions(); + + services.AddAbpApiVersioning(options => + { + options.ReportApiVersions = true; + options.AssumeDefaultVersionWhenUnspecified = true; + + //options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too + //options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too + }, mvcOptions => + { + mvcOptions.ConfigureAbp(preActions.Configure()); + }); + + //services.AddApiVersioning(config => + //{ + // // Specify the default API Version as 1.0 + // config.DefaultApiVersion = new ApiVersion(1, 0); + // // Advertise the API versions supported for the particular endpoint (through 'api-supported-versions' response header which lists all available API versions for that endpoint) + // config.ReportApiVersions = true; + //}); + + //services.AddVersionedApiExplorer(options => + //{ + // // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // // note: the specified format code will format the version as "'v'major[.minor][-status]" + // options.GroupNameFormat = "'v'VVV"; + + // // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // // can also be used to control the format of the API version in route templates + // options.SubstituteApiVersionInUrl = true; + //}); + } + + private void ConfigureKestrelServer() + { + Configure(options => + { + options.Limits.MaxRequestBodySize = null; + options.Limits.MaxRequestBufferSize = null; + }); + } + + private void ConfigureBlobStoring(IConfiguration configuration) + { + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseFileSystem(fileSystem => + { + fileSystem.BasePath = Path.Combine(Directory.GetCurrentDirectory(), "blobs"); + }); + + //containerConfiguration.UseMinio(minio => + //{ + // configuration.GetSection("Minio").Bind(minio); + //}); + }); + }); + } + + private void ConfigureBackgroundTasks() + { + Configure(options => + { + options.NodeName = ApplicationName; + options.JobCleanEnabled = true; + options.JobFetchEnabled = true; + options.JobCheckEnabled = true; + }); + } + + private void ConfigureTextTemplating(IConfiguration configuration) + { + if (configuration.GetValue("TextTemplating:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicTemplateDefinitionStoreEnabled = true; + }); + } + } + + private void ConfigureFeatureManagement(IConfiguration configuration) + { + if (configuration.GetValue("FeatureManagement:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicFeatureStoreEnabled = true; + }); + } + Configure(options => + { + options.ProviderPolicies[EditionFeatureValueProvider.ProviderName] = AbpSaasPermissions.Editions.ManageFeatures; + options.ProviderPolicies[TenantFeatureValueProvider.ProviderName] = AbpSaasPermissions.Tenants.ManageFeatures; + }); + } + + private void ConfigureSettingManagement(IConfiguration configuration) + { + if (configuration.GetValue("SettingManagement:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicSettingStoreEnabled = true; + }); + } + } + + private void ConfigureWebhooksManagement(IConfiguration configuration) + { + if (configuration.GetValue("WebhooksManagement:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicWebhookStoreEnabled = true; + }); + } + } + /// + /// 配置数据导出 + /// + private void ConfigureExporter() + { + Configure(options => + { + // options.MapExportSetting(typeof(BookDto), config => + // { + // config.DynamicColumns = new[] + // { + // // 忽略某些字段 + // new DynamicExcelColumn(nameof(BookDto.AuthorId)){ Ignore = true }, + // new DynamicExcelColumn(nameof(BookDto.LastModificationTime)){ Ignore = true }, + // new DynamicExcelColumn(nameof(BookDto.LastModifierId)){ Ignore = true }, + // new DynamicExcelColumn(nameof(BookDto.CreationTime)){ Ignore = true }, + // new DynamicExcelColumn(nameof(BookDto.CreatorId)){ Ignore = true }, + // new DynamicExcelColumn(nameof(BookDto.Id)){ Ignore = true }, + // }; + // }); + }); + } + /// + /// 配置数据权限 + /// + private void ConfigureEntityDataProtected() + { + // Configure(options => + // { + // options.AddEntities(typeof(DemoResource), + // new[] + // { + // typeof(Book), + // }); + // }); + } + + private void ConfigurePermissionManagement(IConfiguration configuration) + { + if (configuration.GetValue("PermissionManagement:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicPermissionStoreEnabled = true; + }); + } + Configure(options => + { + // Rename IdentityServer.Client.ManagePermissions + // See https://github.com/abpframework/abp/blob/dev/modules/identityserver/src/Volo.Abp.PermissionManagement.Domain.IdentityServer/Volo/Abp/PermissionManagement/IdentityServer/AbpPermissionManagementDomainIdentityServerModule.cs + options.ProviderPolicies[ClientPermissionValueProvider.ProviderName] = AbpOpenIddictPermissions.Applications.ManagePermissions; + + //if (configuration.GetValue("AuthServer:UseOpenIddict")) + //{ + // options.ProviderPolicies[ClientPermissionValueProvider.ProviderName] = AbpOpenIddictPermissions.Applications.ManagePermissions; + //} + //else + //{ + // options.ProviderPolicies[ClientPermissionValueProvider.ProviderName] = AbpIdentityServerPermissions.Clients.ManagePermissions; + //} + }); + } + + private void ConfigureNotificationManagement(IConfiguration configuration) + { + if (configuration.GetValue("NotificationsManagement:IsDynamicStoreEnabled")) + { + Configure(options => + { + options.IsDynamicNotificationsStoreEnabled = true; + }); + } + } + + private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration) + { + var distributedLockEnabled = configuration["DistributedLock:IsEnabled"]; + if (distributedLockEnabled.IsNullOrEmpty() || bool.Parse(distributedLockEnabled)) + { + var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]); + services.AddSingleton(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase())); + } + } + + private void ConfigureVirtualFileSystem() + { + Configure(options => + { + options.FileSets.AddEmbedded("LY.MicroService.Applications.Single"); + }); + } + + private void ConfigureIdempotent() + { + Configure(options => + { + options.IsEnabled = true; + options.DefaultTimeout = 0; + }); + } + + private void ConfigureDbContext() + { + Configure(options => + { + // AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);//解决PostgreSql设置为utc时间后无法写入local时区的问题 + // options.UseNpgsql(); + + options.UseMySQL(); + }); + } + + private void ConfigureDataSeeder() + { + Configure(options => + { + options.Resources.Add(new CustomIdentityResources.AvatarUrl()); + }); + } + + private void ConfigureExceptionHandling() + { + // 自定义需要处理的异常 + Configure(options => + { + // 加入需要处理的异常类型 + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + }); + // 自定义需要发送邮件通知的异常类型 + Configure(options => + { + // 是否发送堆栈信息 + options.SendStackTrace = true; + // 未指定异常接收者的默认接收邮件 + // 指定自己的邮件地址 + }); + + Configure(options => + { + options.SendStackTraceToClients = false; + options.SendExceptionsDetailsToClients = false; + }); + } + + private void ConfigureJsonSerializer(IConfiguration configuration) + { + // 统一时间日期格式 + Configure(options => + { + var jsonConfiguration = configuration.GetSection("Json"); + if (jsonConfiguration.Exists()) + { + jsonConfiguration.Bind(options); + } + }); + // 中文序列化的编码问题 + Configure(options => + { + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); + } + + private void ConfigureCaching(IConfiguration configuration) + { + Configure(options => + { + configuration.GetSection("DistributedCache").Bind(options); + }); + + Configure(options => + { + var redisConfig = ConfigurationOptions.Parse(options.Configuration); + options.ConfigurationOptions = redisConfig; + options.InstanceName = configuration["Redis:InstanceName"]; + }); + } + + private void ConfigureMultiTenancy(IConfiguration configuration) + { + // 多租户 + Configure(options => + { + options.IsEnabled = true; + }); + + var tenantResolveCfg = configuration.GetSection("App:Domains"); + if (tenantResolveCfg.Exists()) + { + Configure(options => + { + var domains = tenantResolveCfg.Get(); + foreach (var domain in domains) + { + options.AddDomainTenantResolver(domain); + } + }); + } + } + + private void ConfigureAuditing(IConfiguration configuration) + { + Configure(options => + { + options.ApplicationName = ApplicationName; + // 是否启用实体变更记录 + var allEntitiesSelectorIsEnabled = configuration["Auditing:AllEntitiesSelector"]; + if (allEntitiesSelectorIsEnabled.IsNullOrWhiteSpace() || + (bool.TryParse(allEntitiesSelectorIsEnabled, out var enabled) && enabled)) + { + options.EntityHistorySelectors.AddAllEntities(); + } + }); + } + + private void ConfigureSwagger(IServiceCollection services) + { + // Swagger + services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "App API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Scheme = "bearer", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new string[] { } + } + }); + options.OperationFilter(); + }); + } + + private void ConfigureIdentity(IConfiguration configuration) + { + // 增加配置文件定义,在新建租户时需要 + Configure(options => + { + var identityConfiguration = configuration.GetSection("Identity"); + if (identityConfiguration.Exists()) + { + identityConfiguration.Bind(options); + } + }); + Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); + Configure(options => + { + options.IsCleanupEnabled = true; + }); + } + + private void ConfigureMvcUiTheme() + { + Configure(options => + { + //options.StyleBundles.Configure( + // LeptonXLiteThemeBundles.Styles.Global, + // bundle => + // { + // bundle.AddFiles("/global-styles.css"); + // } + //); + }); + } + + private void ConfigureLocalization() + { + Configure(options => + { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + + options + .AddLanguagesMapOrUpdate( + "vue-admin-element-ui", + new NameValue("zh-Hans", "zh"), + new NameValue("en", "en")); + + // vben admin 语言映射 + options + .AddLanguagesMapOrUpdate( + "vben-admin-ui", + new NameValue("zh_CN", "zh-Hans")); + + options.Resources.Get() + .AddBaseTypes( + typeof(IdentityResource), + typeof(AliyunResource), + typeof(TencentCloudResource), + typeof(WeChatResource), + typeof(PlatformResource), + typeof(AbpOpenIddictResource), + typeof(AbpIdentityServerResource)); + + options.UseAllPersistence(); + }); + + Configure(options => + { + var zhHansCultureMapInfo = new CultureMapInfo + { + TargetCulture = "zh-Hans", + SourceCultures = new string[] { "zh", "zh_CN", "zh-CN" } + }; + + options.CulturesMaps.Add(zhHansCultureMapInfo); + options.UiCulturesMaps.Add(zhHansCultureMapInfo); + }); + } + + private void ConfigureWrapper() + { + Configure(options => + { + options.IsEnabled = true; + // options.IsWrapUnauthorizedEnabled = true; + options.IgnoreNamespaces.Add("Elsa"); + }); + } + + private void PreConfigureWrapper() + { + //PreConfigure(options => + //{ + // options.ProxyRequestActions.Add( + // (appid, httprequestmessage) => + // { + // httprequestmessage.Headers.TryAddWithoutValidation(AbpHttpWrapConsts.AbpDontWrapResult, "true"); + // }); + //}); + + PreConfigure(options => + { + options.ProxyClientActions.Add( + (_, _, client) => + { + client.DefaultRequestHeaders.TryAddWithoutValidation(AbpHttpWrapConsts.AbpDontWrapResult, "true"); + }); + }); + } + + private void ConfigureAuditing() + { + Configure(options => + { + // options.IsEnabledForGetRequests = true; + options.ApplicationName = ApplicationName; + }); + } + + private void ConfigureUrls(IConfiguration configuration) + { + Configure(options => + { + var applicationConfiguration = configuration.GetSection("App:Urls:Applications"); + foreach (var appConfig in applicationConfiguration.GetChildren()) + { + options.Applications[appConfig.Key].RootUrl = appConfig["RootUrl"]; + foreach (var urlsConfig in appConfig.GetSection("Urls").GetChildren()) + { + options.Applications[appConfig.Key].Urls[urlsConfig.Key] = urlsConfig.Value; + } + } + }); + } + + private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false) + { + Configure(options => + { + options.AutoValidate = false; + }); + + services.Replace(ServiceLifetime.Scoped); + + services.AddAuthentication() + .AddAbpJwtBearer(options => + { + configuration.GetSection("AuthServer").Bind(options); + + options.Events ??= new JwtBearerEvents(); + options.Events.OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && + (path.StartsWithSegments("/api/files"))) + { + context.Token = accessToken; + } + return Task.CompletedTask; + }; + }); + + if (!isDevelopment) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + services + .AddDataProtection() + .SetApplicationName("LINGYUN.Abp.Application") + .PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys"); + } + + services.AddSameSiteCookiePolicy(); + } + + private void ConfigureCors(IServiceCollection services, IConfiguration configuration) + { + services.AddCors(options => + { + options.AddPolicy(DefaultCorsPolicyName, builder => + { + builder + .WithOrigins( + configuration["App:CorsOrigins"] + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .WithAbpWrapExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + } + + private void ConfigureWeChat() + { + Configure(options => + { + // 回复文本消息 + options.MapMessage< + LINGYUN.Abp.WeChat.Official.Messages.Models.TextMessage, + TextMessageReplyContributor>(); + // 处理关注事件 + options.MapEvent< + LINGYUN.Abp.WeChat.Official.Messages.Models.UserSubscribeEvent, + UserSubscribeEventContributor>(); + + options.MapMessage< + LINGYUN.Abp.WeChat.Work.Common.Messages.Models.TextMessage, + WeChat.Work.Messages.TextMessageReplyContributor>(); + }); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.cs new file mode 100644 index 000000000..ceb61611a --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MicroServiceApplicationsSingleModule.cs @@ -0,0 +1,394 @@ +using LINGYUN.Abp.Account; +using LINGYUN.Abp.Account.Templates; +using LINGYUN.Abp.Aliyun.SettingManagement; +using LINGYUN.Abp.AspNetCore.HttpOverrides; +using LINGYUN.Abp.AspNetCore.Mvc.Idempotent.Wrapper; +using LINGYUN.Abp.AspNetCore.Mvc.Localization; +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; +using LINGYUN.Abp.Auditing; +using LINGYUN.Abp.AuditLogging.EntityFrameworkCore; +using LINGYUN.Abp.Authentication.QQ; +using LINGYUN.Abp.Authentication.WeChat; +using LINGYUN.Abp.Authorization.OrganizationUnits; +using LINGYUN.Abp.BackgroundTasks; +using LINGYUN.Abp.BackgroundTasks.Activities; +using LINGYUN.Abp.BackgroundTasks.DistributedLocking; +using LINGYUN.Abp.BackgroundTasks.EventBus; +using LINGYUN.Abp.BackgroundTasks.ExceptionHandling; +using LINGYUN.Abp.BackgroundTasks.Jobs; +using LINGYUN.Abp.BackgroundTasks.Notifications; +using LINGYUN.Abp.BackgroundTasks.Quartz; +using LINGYUN.Abp.CachingManagement; +using LINGYUN.Abp.CachingManagement.StackExchangeRedis; +using LINGYUN.Abp.Dapr.Client; +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.DataProtectionManagement; +using LINGYUN.Abp.DataProtectionManagement.EntityFrameworkCore; +// using LINGYUN.Abp.Demo; +// using LINGYUN.Abp.Demo.EntityFrameworkCore; +using LINGYUN.Abp.ExceptionHandling; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.Exporter.MiniExcel; +using LINGYUN.Abp.FeatureManagement; +using LINGYUN.Abp.FeatureManagement.HttpApi; +using LINGYUN.Abp.Features.LimitValidation; +using LINGYUN.Abp.Features.LimitValidation.Redis.Client; +using LINGYUN.Abp.Http.Client.Wrapper; +using LINGYUN.Abp.Identity; +using LINGYUN.Abp.Identity.AspNetCore.Session; +using LINGYUN.Abp.Identity.EntityFrameworkCore; +using LINGYUN.Abp.Identity.Notifications; +using LINGYUN.Abp.Identity.OrganizaztionUnits; +using LINGYUN.Abp.Identity.Session.AspNetCore; +using LINGYUN.Abp.Identity.WeChat; +using LINGYUN.Abp.IdGenerator; +using LINGYUN.Abp.IM.SignalR; +using LINGYUN.Abp.Localization.CultureMap; +using LINGYUN.Abp.Localization.Persistence; +using LINGYUN.Abp.LocalizationManagement; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.MessageService; +using LINGYUN.Abp.MessageService.EntityFrameworkCore; +using LINGYUN.Abp.MultiTenancy.Editions; +using LINGYUN.Abp.Notifications; +using LINGYUN.Abp.Notifications.Common; +using LINGYUN.Abp.Notifications.Emailing; +using LINGYUN.Abp.Notifications.EntityFrameworkCore; +using LINGYUN.Abp.Notifications.SignalR; +using LINGYUN.Abp.Notifications.WeChat.MiniProgram; +using LINGYUN.Abp.OpenApi.Authorization; +using LINGYUN.Abp.OpenIddict; +using LINGYUN.Abp.OpenIddict.AspNetCore; +using LINGYUN.Abp.OpenIddict.AspNetCore.Session; +using LINGYUN.Abp.OpenIddict.Portal; +using LINGYUN.Abp.OpenIddict.Sms; +using LINGYUN.Abp.OpenIddict.WeChat; +using LINGYUN.Abp.OpenIddict.WeChat.Work; +using LINGYUN.Abp.OssManagement; +using LINGYUN.Abp.OssManagement.FileSystem; +// using LINGYUN.Abp.OssManagement.Imaging; +using LINGYUN.Abp.OssManagement.SettingManagement; +using LINGYUN.Abp.PermissionManagement; +using LINGYUN.Abp.PermissionManagement.HttpApi; +using LINGYUN.Abp.PermissionManagement.OrganizationUnits; +using LINGYUN.Abp.Saas; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.SettingManagement; +using LINGYUN.Abp.Sms.Aliyun; +using LINGYUN.Abp.TaskManagement; +using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.Tencent.QQ; +using LINGYUN.Abp.Tencent.SettingManagement; +using LINGYUN.Abp.TextTemplating; +using LINGYUN.Abp.TextTemplating.EntityFrameworkCore; +using LINGYUN.Abp.UI.Navigation; +using LINGYUN.Abp.UI.Navigation.VueVbenAdmin; +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.Webhooks.EventBus; +using LINGYUN.Abp.Webhooks.Identity; +using LINGYUN.Abp.Webhooks.Saas; +using LINGYUN.Abp.WebhooksManagement; +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using LINGYUN.Abp.WeChat.MiniProgram; +using LINGYUN.Abp.WeChat.Official; +using LINGYUN.Abp.WeChat.Official.Handlers; +using LINGYUN.Abp.WeChat.SettingManagement; +using LINGYUN.Abp.WeChat.Work; +using LINGYUN.Abp.WeChat.Work.Handlers; +using LINGYUN.Platform; +using LINGYUN.Platform.EntityFrameworkCore; +using LINGYUN.Platform.HttpApi; +using LINGYUN.Platform.Settings.VueVbenAdmin; +using LINGYUN.Platform.Theme.VueVbenAdmin; +using Volo.Abp; +using Volo.Abp.Account.Web; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.Autofac; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore.PostgreSql; +using Volo.Abp.EventBus; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Imaging; +using Volo.Abp.Modularity; +using Volo.Abp.OpenIddict.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.Identity; +using Volo.Abp.PermissionManagement.OpenIddict; +using Volo.Abp.SettingManagement; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Threading; +// using LINGYUN.Abp.Elsa.EntityFrameworkCore.MySql; +using Volo.Abp.EntityFrameworkCore.MySQL; + +namespace PackageName.CompanyName.ProjectName.AIO.Host; + +[DependsOn( + typeof(AbpAccountApplicationModule), + typeof(AbpAccountHttpApiModule), + typeof(AbpAccountWebOpenIddictModule), + typeof(AbpAuditingApplicationModule), + typeof(AbpAuditingHttpApiModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpCachingManagementStackExchangeRedisModule), + typeof(AbpCachingManagementApplicationModule), + typeof(AbpCachingManagementHttpApiModule), + typeof(AbpIdentityAspNetCoreSessionModule), + typeof(AbpIdentitySessionAspNetCoreModule), + typeof(AbpIdentityNotificationsModule), + typeof(AbpIdentityDomainModule), + typeof(AbpIdentityApplicationModule), + typeof(AbpIdentityHttpApiModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementDomainModule), + typeof(AbpLocalizationManagementApplicationModule), + typeof(AbpLocalizationManagementHttpApiModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpSerilogEnrichersApplicationModule), + typeof(AbpSerilogEnrichersUniqueIdModule), + typeof(AbpMessageServiceDomainModule), + typeof(AbpMessageServiceApplicationModule), + typeof(AbpMessageServiceHttpApiModule), + typeof(AbpMessageServiceEntityFrameworkCoreModule), + typeof(AbpNotificationsDomainModule), + typeof(AbpNotificationsApplicationModule), + typeof(AbpNotificationsHttpApiModule), + typeof(AbpNotificationsEntityFrameworkCoreModule), + + //typeof(AbpIdentityServerSessionModule), + //typeof(AbpIdentityServerApplicationModule), + //typeof(AbpIdentityServerHttpApiModule), + //typeof(AbpIdentityServerEntityFrameworkCoreModule), + + typeof(AbpOpenIddictAspNetCoreModule), + typeof(AbpOpenIddictAspNetCoreSessionModule), + typeof(AbpOpenIddictApplicationModule), + typeof(AbpOpenIddictHttpApiModule), + typeof(AbpOpenIddictEntityFrameworkCoreModule), + typeof(AbpOpenIddictSmsModule), + typeof(AbpOpenIddictPortalModule), + typeof(AbpOpenIddictWeChatModule), + typeof(AbpOpenIddictWeChatWorkModule), + + //typeof(AbpOssManagementMinioModule), // 取消注释以使用Minio + typeof(AbpOssManagementFileSystemModule), + // typeof(AbpOssManagementImagingModule), + typeof(AbpOssManagementDomainModule), + typeof(AbpOssManagementApplicationModule), + typeof(AbpOssManagementHttpApiModule), + typeof(AbpOssManagementSettingManagementModule), + typeof(AbpImagingImageSharpModule), + + typeof(PlatformDomainModule), + typeof(PlatformApplicationModule), + typeof(PlatformHttpApiModule), + typeof(PlatformEntityFrameworkCoreModule), + typeof(PlatformSettingsVueVbenAdminModule), + typeof(PlatformThemeVueVbenAdminModule), + typeof(AbpUINavigationVueVbenAdminModule), + + typeof(AbpSaasDomainModule), + typeof(AbpSaasApplicationModule), + typeof(AbpSaasHttpApiModule), + typeof(AbpSaasEntityFrameworkCoreModule), + + typeof(TaskManagementDomainModule), + typeof(TaskManagementApplicationModule), + typeof(TaskManagementHttpApiModule), + typeof(TaskManagementEntityFrameworkCoreModule), + + typeof(AbpTextTemplatingDomainModule), + typeof(AbpTextTemplatingApplicationModule), + typeof(AbpTextTemplatingHttpApiModule), + typeof(AbpTextTemplatingEntityFrameworkCoreModule), + + typeof(AbpWebhooksModule), + typeof(AbpWebhooksEventBusModule), + typeof(AbpWebhooksIdentityModule), + typeof(AbpWebhooksSaasModule), + typeof(WebhooksManagementDomainModule), + typeof(WebhooksManagementApplicationModule), + typeof(WebhooksManagementHttpApiModule), + typeof(WebhooksManagementEntityFrameworkCoreModule), + + typeof(AbpFeatureManagementApplicationModule), + typeof(AbpFeatureManagementHttpApiModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + + typeof(AbpSettingManagementDomainModule), + typeof(AbpSettingManagementApplicationModule), + typeof(AbpSettingManagementHttpApiModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + + typeof(AbpPermissionManagementApplicationModule), + typeof(AbpPermissionManagementHttpApiModule), + typeof(AbpPermissionManagementDomainIdentityModule), + typeof(AbpPermissionManagementDomainOpenIddictModule), + // typeof(AbpPermissionManagementDomainIdentityServerModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementDomainOrganizationUnitsModule), // 组织机构权限管理 + + // typeof(AbpEntityFrameworkCorePostgreSqlModule), + typeof(AbpEntityFrameworkCoreMySQLModule), + + typeof(AbpAliyunSmsModule), + typeof(AbpAliyunSettingManagementModule), + + typeof(AbpAuthenticationQQModule), + typeof(AbpAuthenticationWeChatModule), + typeof(AbpAuthorizationOrganizationUnitsModule), + typeof(AbpIdentityOrganizaztionUnitsModule), + + typeof(AbpBackgroundTasksModule), + typeof(AbpBackgroundTasksActivitiesModule), + typeof(AbpBackgroundTasksDistributedLockingModule), + typeof(AbpBackgroundTasksEventBusModule), + typeof(AbpBackgroundTasksExceptionHandlingModule), + typeof(AbpBackgroundTasksJobsModule), + typeof(AbpBackgroundTasksNotificationsModule), + typeof(AbpBackgroundTasksQuartzModule), + + typeof(AbpDataProtectionManagementApplicationModule), + typeof(AbpDataProtectionManagementHttpApiModule), + typeof(AbpDataProtectionManagementEntityFrameworkCoreModule), + + // typeof(AbpDemoApplicationModule), + // typeof(AbpDemoHttpApiModule), + // typeof(AbpDemoEntityFrameworkCoreModule), + + typeof(AbpDaprClientModule), + typeof(AbpExceptionHandlingModule), + typeof(AbpEmailingExceptionHandlingModule), + typeof(AbpFeaturesLimitValidationModule), + typeof(AbpFeaturesValidationRedisClientModule), + typeof(AbpAspNetCoreMvcLocalizationModule), + + typeof(AbpLocalizationCultureMapModule), + typeof(AbpLocalizationPersistenceModule), + + typeof(AbpOpenApiAuthorizationModule), + + typeof(AbpIMSignalRModule), + + typeof(AbpNotificationsModule), + typeof(AbpNotificationsCommonModule), + typeof(AbpNotificationsSignalRModule), + typeof(AbpNotificationsEmailingModule), + typeof(AbpMultiTenancyEditionsModule), + + typeof(AbpTencentQQModule), + typeof(AbpTencentCloudSettingManagementModule), + + typeof(AbpIdentityWeChatModule), + typeof(AbpNotificationsWeChatMiniProgramModule), + typeof(AbpWeChatMiniProgramModule), + typeof(AbpWeChatOfficialModule), + typeof(AbpWeChatOfficialApplicationModule), + typeof(AbpWeChatOfficialHttpApiModule), + typeof(AbpWeChatWorkModule), + typeof(AbpWeChatWorkApplicationModule), + typeof(AbpWeChatWorkHttpApiModule), + typeof(AbpWeChatOfficialHandlersModule), + typeof(AbpWeChatWorkHandlersModule), + typeof(AbpWeChatSettingManagementModule), + + typeof(AbpDataDbMigratorModule), + typeof(AbpIdGeneratorModule), + typeof(AbpUINavigationModule), + typeof(AbpAccountTemplatesModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpCachingStackExchangeRedisModule), + // typeof(AbpElsaModule), + // typeof(AbpElsaServerModule), + // typeof(AbpElsaActivitiesModule), + // typeof(AbpElsaEntityFrameworkCoreModule), + // typeof(AbpElsaEntityFrameworkCorePostgreSqlModule), + // typeof(AbpElsaModule), + // typeof(AbpElsaServerModule), + // typeof(AbpElsaActivitiesModule), + // typeof(AbpElsaEntityFrameworkCoreModule), + // typeof(AbpElsaEntityFrameworkCoreMySqlModule), + + typeof(AbpExporterMiniExcelModule), + typeof(AbpAspNetCoreMvcUiMultiTenancyModule), + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpHttpClientWrapperModule), + typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpAspNetCoreMvcIdempotentWrapperModule), + typeof(AbpAspNetCoreHttpOverridesModule), + typeof(AbpAspNetCoreMvcUiBasicThemeModule), + typeof(AbpEventBusModule), + typeof(AbpAutofacModule) + )] +public partial class MicroServiceApplicationsSingleModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + PreConfigureWrapper(); + PreConfigureFeature(); + PreConfigureIdentity(); + PreConfigureApp(configuration); + PreConfigureQuartz(configuration); + PreConfigureAuthServer(configuration); + PreConfigureElsa(context.Services, configuration); + PreConfigureCertificate(configuration, hostingEnvironment); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + ConfigureWeChat(); + ConfigureWrapper(); + ConfigureExporter(); + ConfigureAuditing(); + ConfigureDbContext(); + ConfigureIdempotent(); + ConfigureMvcUiTheme(); + ConfigureDataSeeder(); + ConfigureLocalization(); + ConfigureKestrelServer(); + ConfigureBackgroundTasks(); + ConfigureExceptionHandling(); + ConfigureVirtualFileSystem(); + ConfigureEntityDataProtected(); + ConfigureUrls(configuration); + ConfigureCaching(configuration); + ConfigureAuditing(configuration); + ConfigureIdentity(configuration); + ConfigureAuthServer(configuration); + ConfigureSwagger(context.Services); + ConfigureEndpoints(context.Services); + ConfigureBlobStoring(configuration); + ConfigureMultiTenancy(configuration); + ConfigureJsonSerializer(configuration); + ConfigureTextTemplating(configuration); + ConfigureFeatureManagement(configuration); + ConfigureSettingManagement(configuration); + ConfigureWebhooksManagement(configuration); + ConfigurePermissionManagement(configuration); + ConfigureNotificationManagement(configuration); + ConfigureCors(context.Services, configuration); + ConfigureDistributedLock(context.Services, configuration); + ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(async () => await OnApplicationInitializationAsync(context)); + } + + public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + await context.ServiceProvider.GetRequiredService().SeedAsync(); ; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs new file mode 100644 index 000000000..42108b0eb --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs @@ -0,0 +1,67 @@ +namespace PackageName.CompanyName.ProjectName.AIO.Host.Microsoft.Extensions.DependencyInjection +{ + public static class SameSiteCookiesServiceCollectionExtensions + { + public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services) + { + services.Configure(options => + { + options.MinimumSameSitePolicy = SameSiteMode.Unspecified; + options.OnAppendCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + options.OnDeleteCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + }); + + return services; + } + + private static void CheckSameSite(HttpContext httpContext, CookieOptions options) + { + if (options.SameSite == SameSiteMode.None) + { + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + if (!httpContext.Request.IsHttps || DisallowsSameSiteNone(userAgent)) + { + // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1) + options.SameSite = SameSiteMode.Unspecified; + } + } + } + + private static bool DisallowsSameSiteNone(string userAgent) + { + // Cover all iOS based browsers here. This includes: + // - Safari on iOS 12 for iPhone, iPod Touch, iPad + // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad + // - Chrome on iOS 12 for iPhone, iPod Touch, iPad + // All of which are broken by SameSite=None, because they use the iOS networking stack + if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")) + { + return true; + } + + // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: + // - Safari on Mac OS X. + // This does not include: + // - Chrome on Mac OS X + // Because they do not use the Mac OS networking stack. + if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && + userAgent.Contains("Version/") && userAgent.Contains("Safari")) + { + return true; + } + + // Cover Chrome 50-69, because some versions are broken by SameSite=None, + // and none in this range require it. + // Note: this covers some pre-Chromium Edge versions, + // but pre-Chromium Edge does not require SameSite=None. + if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/ITenantConfigurationCache.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/ITenantConfigurationCache.cs new file mode 100644 index 000000000..daabe255d --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/ITenantConfigurationCache.cs @@ -0,0 +1,10 @@ +using Volo.Abp.MultiTenancy; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.MultiTenancy; + +public interface ITenantConfigurationCache +{ + Task RefreshAsync(); + + Task> GetTenantsAsync(); +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCache.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCache.cs new file mode 100644 index 000000000..61f939f97 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCache.cs @@ -0,0 +1,59 @@ +using LINGYUN.Abp.Saas.Tenants; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.MultiTenancy; + +public class TenantConfigurationCache : ITenantConfigurationCache, ITransientDependency +{ + protected ITenantRepository TenantRepository { get; } + protected IDistributedCache TenantCache { get; } + + public TenantConfigurationCache( + ITenantRepository tenantRepository, + IDistributedCache tenantCache) + { + TenantRepository = tenantRepository; + TenantCache = tenantCache; + } + + public async virtual Task RefreshAsync() + { + var cacheKey = GetCacheKey(); + + await TenantCache.RemoveAsync(cacheKey); + } + + public async virtual Task> GetTenantsAsync() + { + return (await GetForCacheItemAsync()).Tenants; + } + + protected async virtual Task GetForCacheItemAsync() + { + var cacheKey = GetCacheKey(); + var cacheItem = await TenantCache.GetAsync(cacheKey); + if (cacheItem == null) + { + var allActiveTenants = await TenantRepository.GetListAsync(); + + cacheItem = new TenantConfigurationCacheItem( + allActiveTenants + .Where(t => t.IsActive) + .Select(t => new TenantConfiguration(t.Id, t.Name) + { + IsActive = t.IsActive, + }).ToList()); + + await TenantCache.SetAsync(cacheKey, cacheItem); + } + + return cacheItem; + } + + protected virtual string GetCacheKey() + { + return "_Abp_Tenant_Configuration"; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCacheItem.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCacheItem.cs new file mode 100644 index 000000000..ac10549e5 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/MultiTenancy/TenantConfigurationCacheItem.cs @@ -0,0 +1,19 @@ +using Volo.Abp.MultiTenancy; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.MultiTenancy; + +[IgnoreMultiTenancy] +public class TenantConfigurationCacheItem +{ + public List Tenants { get; set; } + + public TenantConfigurationCacheItem() + { + Tenants = new List(); + } + + public TenantConfigurationCacheItem(List tenants) + { + Tenants = tenants; + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/PackageName.CompanyName.ProjectName.AIO.Host.csproj b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/PackageName.CompanyName.ProjectName.AIO.Host.csproj new file mode 100644 index 000000000..136d24605 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/PackageName.CompanyName.ProjectName.AIO.Host.csproj @@ -0,0 +1,276 @@ + + + net8.0 + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml new file mode 100644 index 000000000..aed9202eb --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml @@ -0,0 +1,103 @@ +@using Volo.Abp.Account.Localization +@using Volo.Abp.Users +@using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo +@using Volo.Abp.AspNetCore.Mvc.UI.Theming +@using Volo.Abp.Data +@using Volo.Abp.Identity.Settings +@using Volo.Abp.Localization +@using Volo.Abp.Settings +@using Volo.Abp.ObjectExtending +@inject IHtmlLocalizer L +@inject ICurrentUser CurrentUser +@inject ISettingProvider SettingManager +@inject IThemeManager ThemeManager +@inject IStringLocalizerFactory StringLocalizerFactory +@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel +@{ + var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true", + StringComparison.OrdinalIgnoreCase); + + var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true", + StringComparison.OrdinalIgnoreCase); +} + +

@L["PersonalSettings"]


+
+ + + + + + + + + + + + + + + + + + + + @if (CurrentUser.EmailVerified) + { + + } + else + { + @**@ + @L["Validation"].Value + } + + + + + + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + var isAllowed = propertyInfo.Configuration.GetOrDefault(IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit); + + if (isAllowed == null || !isAllowed.Equals(true)) + { + continue; + } + + if (!propertyInfo.Name.EndsWith("_Text")) + { + if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty()) + { + if (propertyInfo.Type.IsEnum) + { + Model.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type); + } + + + } + else + { + + } + } + } + + + diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js new file mode 100644 index 000000000..55a88e52e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js @@ -0,0 +1,28 @@ +(function ($) { + $(function () { + var l = abp.localization.getResource("AbpAccount"); + + $('#PersonalSettingsForm').submit(function (e) { + e.preventDefault(); + + if (!$('#PersonalSettingsForm').valid()) { + return false; + } + + var input = $('#PersonalSettingsForm').serializeFormToObject(); + + volo.abp.account.profile.update(input).then(function (result) { + abp.notify.success(l('PersonalSettingsSaved')); + updateConcurrencyStamp(); + }); + }); + }); + + abp.event.on('passwordChanged', updateConcurrencyStamp); + + function updateConcurrencyStamp(){ + volo.abp.account.profile.get().then(function(profile){ + $("#ConcurrencyStamp").val(profile.concurrencyStamp); + }); + } +})(jQuery); diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml new file mode 100644 index 000000000..d4d8cda0f --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml @@ -0,0 +1,17 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.EmailConfirmModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +
+
+

@L["EmailConfirm"]

+
+ + + @L["Cancel"] + + +
+
diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml.cs new file mode 100644 index 000000000..b2ed2b28e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirm.cshtml.cs @@ -0,0 +1,72 @@ +using LINGYUN.Abp.Account; +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.Identity; +using Volo.Abp.Validation; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class EmailConfirmModel : AccountPageModel + { + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid UserId { get; set; } + + [Required] + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ConfirmToken { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + public IMyProfileAppService MyProfileAppService { get; set; } + + public EmailConfirmModel() + { + LocalizationResourceType = typeof(AccountResource); + } + + public async virtual Task OnPostAsync() + { + try + { + ValidateModel(); + + await MyProfileAppService.ConfirmEmailAsync( + new ConfirmEmailInput + { + ConfirmToken = ConfirmToken, + }); + } + catch (AbpIdentityResultException e) + { + if (!string.IsNullOrWhiteSpace(e.Message)) + { + Alerts.Warning(GetLocalizeExceptionMessage(e)); + return Page(); + } + + throw; + } + catch (AbpValidationException) + { + return Page(); + } + + return RedirectToPage("./ConfirmEmailConfirmation", new + { + returnUrl = ReturnUrl, + returnUrlHash = ReturnUrlHash + }); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml new file mode 100644 index 000000000..8ffc6586b --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml @@ -0,0 +1,13 @@ +@page +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.EmailConfirmConfirmationModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +
+
+

@L["EmailConfirm"]

+

@L["YourEmailIsSuccessfullyConfirm"]

+ @L["GoToTheApplication"] +
+
diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml.cs new file mode 100644 index 000000000..01f1f0f01 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/EmailConfirmConfirmation.cshtml.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Account.Web.Pages.Account; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account; + +[AllowAnonymous] +public class EmailConfirmConfirmationModel : AccountPageModel +{ + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + public async virtual Task OnGetAsync() + { + ReturnUrl = await GetRedirectUrlAsync(ReturnUrl, ReturnUrlHash); + + return Page(); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml new file mode 100644 index 000000000..8d55fdd34 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.SendCodeModel +@inject IHtmlLocalizer L + +
+
+

@L["TwoFactor"]

+
+ + + +
+ +
+
+ @L["SendVerifyCode"] +
+ + @L["Login"] + + +
+
+ diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml.cs new file mode 100644 index 000000000..8155e20f1 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendCode.cshtml.cs @@ -0,0 +1,125 @@ +using LINGYUN.Abp.Account.Emailing; +using LINGYUN.Abp.Identity.Settings; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Volo.Abp; +using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.Sms; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class SendCodeModel : AccountPageModel + { + [BindProperty] + public SendCodeInputModel Input { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public bool RememberMe { get; set; } + + public IEnumerable Providers { get; set; } + + protected ISmsSender SmsSender { get; } + + protected IAccountEmailVerifySender AccountEmailVerifySender { get; } + + public SendCodeModel( + ISmsSender smsSender, + IAccountEmailVerifySender accountEmailVerifySender) + { + SmsSender = smsSender; + AccountEmailVerifySender = accountEmailVerifySender; + + LocalizationResourceType = typeof(AccountResource); + } + + public virtual async Task OnGetAsync() + { + Input = new SendCodeInputModel(); + + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + // ˫������Ϣ��֤ʧ��,һ�㶼�dz�ʱ�˻����û���Ϣ��� + Alerts.Warning(L["TwoFactorAuthenticationInvaidUser"]); + return Page(); + } + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(user); + Providers = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + + return Page(); + } + + public virtual async Task OnPostAsync() + { + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + Alerts.Warning(L["TwoFactorAuthenticationInvaidUser"]); + return Page(); + } + + if (Input.SelectedProvider == "Authenticator") + { + // �û�ͨ���ʼ�/�������ӽ�����Ȩҳ�� + return RedirectToPage("VerifyAuthenticatorCode", new + { + returnUrl = ReturnUrl, + returnUrlHash = ReturnUrlHash, + rememberMe = RememberMe + }); + } + // ������֤�� + var code = await UserManager.GenerateTwoFactorTokenAsync(user, Input.SelectedProvider); + if (string.IsNullOrWhiteSpace(code)) + { + Alerts.Warning(L["InvaidGenerateTwoFactorToken"]); + return Page(); + } + + if (Input.SelectedProvider == "Email") + { + await AccountEmailVerifySender + .SendMailLoginVerifyCodeAsync( + code, + user.UserName, + user.Email); + } + else if (Input.SelectedProvider == "Phone") + { + var phoneNumber = await UserManager.GetPhoneNumberAsync(user); + var templateCode = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); + Check.NotNullOrWhiteSpace(templateCode, nameof(IdentitySettingNames.User.SmsUserSignin)); + + // TODO: �Ժ���չ����ģ�巢�� + var smsMessage = new SmsMessage(phoneNumber, code); + smsMessage.Properties.Add("code", code); + smsMessage.Properties.Add("TemplateCode", templateCode); + + await SmsSender.SendAsync(smsMessage); + } + + return RedirectToPage("VerifyCode", new + { + provider = Input.SelectedProvider, + returnUrl = ReturnUrl, + returnUrlHash = ReturnUrlHash, + rememberMe = RememberMe + }); + } + } + + public class SendCodeInputModel + { + public string SelectedProvider { get; set; } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml new file mode 100644 index 000000000..8f0dd7030 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml @@ -0,0 +1,16 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.SendEmailConfirmModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +
+
+

@L["EmailConfirm"]

+
+ + @L["Cancel"] + + +
+
diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml.cs new file mode 100644 index 000000000..9d14d5710 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/SendEmailConfirm.cshtml.cs @@ -0,0 +1,73 @@ +using LINGYUN.Abp.Account; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.Identity; +using Volo.Abp.Validation; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class SendEmailConfirmModel : AccountPageModel + { + [BindProperty(SupportsGet = true)] + public string Email { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + public IMyProfileAppService MyProfileAppService { get; set; } + + public SendEmailConfirmModel() + { + LocalizationResourceType = typeof(AccountResource); + } + + public virtual Task OnGetAsync() + { + Email = CurrentUser.Email; + + return Task.FromResult(Page()); + } + + public async virtual Task OnPostAsync() + { + try + { + ValidateModel(); + + await MyProfileAppService.SendEmailConfirmLinkAsync( + new SendEmailConfirmCodeDto + { + Email = Email, + AppName = "MVC", + ReturnUrl = ReturnUrl, + ReturnUrlHash = ReturnUrlHash + }); + } + catch (AbpIdentityResultException e) + { + if (!string.IsNullOrWhiteSpace(e.Message)) + { + Alerts.Warning(GetLocalizeExceptionMessage(e)); + return Page(); + } + + throw; + } + catch (AbpValidationException) + { + return Page(); + } + + return RedirectToPage("~/Account/Manage", new + { + returnUrl = ReturnUrl + }); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/TwoFactorSupportedLoginModel.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/TwoFactorSupportedLoginModel.cs new file mode 100644 index 000000000..166f827e9 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/TwoFactorSupportedLoginModel.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.Account.Web; +using Volo.Abp.Account.Web.Pages.Account; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.OpenIddict; +using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + /// + /// 重写登录模型,实现双因素登录 + /// + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(LoginModel), typeof(OpenIddictSupportedLoginModel))] + public class TwoFactorSupportedLoginModel : OpenIddictSupportedLoginModel + { + public TwoFactorSupportedLoginModel( + IAuthenticationSchemeProvider schemeProvider, + IOptions accountOptions, + IOptions identityOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, + AbpOpenIddictRequestHelper openIddictRequestHelper) + : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache, openIddictRequestHelper) + { + + } + + protected async override Task> GetExternalProviders() + { + var providers = await base.GetExternalProviders(); + + foreach (var provider in providers) + { + var localizedDisplayName = L[provider.DisplayName]; + if (localizedDisplayName.ResourceNotFound) + { + localizedDisplayName = L["AuthenticationScheme:" + provider.DisplayName]; + } + + if (!localizedDisplayName.ResourceNotFound) + { + provider.DisplayName = localizedDisplayName.Value; + } + } + + return providers; + } + + protected override Task TwoFactorLoginResultAsync() + { + // 重定向双因素认证页面 + return Task.FromResult(RedirectToPage("SendCode", new + { + returnUrl = ReturnUrl, + returnUrlHash = ReturnUrlHash, + rememberMe = LoginInput.RememberMe + })); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml new file mode 100644 index 000000000..1936197c0 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml @@ -0,0 +1,4 @@ +@page +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.UseRecoveryCodeModel +@{ +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml.cs new file mode 100644 index 000000000..c8ccca339 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/UseRecoveryCode.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class UseRecoveryCodeModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml new file mode 100644 index 000000000..2b14e2168 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.VerifyAuthenticatorCodeModel +@inject IHtmlLocalizer L +
+
+
+ + +
+ + + +
+
+ + +
+ @L["VerifyAuthenticatorCode"] + + @L["Login"] + +
+
+
\ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml.cs new file mode 100644 index 000000000..899e3df30 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyAuthenticatorCode.cshtml.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Account.Web.Pages.Account; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class VerifyAuthenticatorCodeModel : AccountPageModel + { + [BindProperty] + public VerifyAuthenticatorCodeInputModel Input { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty(SupportsGet = true)] + public bool RememberBrowser { get; set; } + + [HiddenInput] + public bool RememberMe { get; set; } + + public virtual IActionResult OnGet() + { + Input = new VerifyAuthenticatorCodeInputModel(); + + return Page(); + } + + public virtual async Task OnPostAsync() + { + var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(Input.VerifyCode, RememberMe, RememberBrowser); + if (result.Succeeded) + { + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); + } + if (result.IsLockedOut) + { + Logger.LogWarning(7, "User account locked out."); + Alerts.Warning(L["UserLockedOutMessage"]); + return Page(); + } + else + { + Alerts.Danger(L["TwoFactorAuthenticationInvaidUser"]);// TODO: ����״̬��Ľ�� + return Page(); + } + } + } + + public class VerifyAuthenticatorCodeInputModel + { + [Required] + public string VerifyCode { get; set; } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml new file mode 100644 index 000000000..44d45b36e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml @@ -0,0 +1,29 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account.VerifyCodeModel +
+
+
+ + + + +
+ +
+ + + + + +
+ @L["VerifyAuthenticatorCode"] +
+ + @L["ReSendVerifyCode"] + +
+
+
diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml.cs new file mode 100644 index 000000000..169c48eda --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Account/VerifyCode.cshtml.cs @@ -0,0 +1,90 @@ +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Account.Localization; +using Volo.Abp.Account.Web.Pages.Account; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages.Account +{ + public class VerifyCodeModel : AccountPageModel + { + [BindProperty] + public VerifyCodeInputModel Input { get; set; } + /// + /// ˫������֤�ṩ���� + /// + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string Provider { get; set; } + /// + /// �ض���Url + /// + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + /// + /// + /// + [HiddenInput] + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + /// + /// �Ƿ��ס��¼״̬ + /// + [HiddenInput] + [BindProperty(SupportsGet = true)] + public bool RememberMe { get; set; } + + public VerifyCodeModel() + { + LocalizationResourceType = typeof(AccountResource); + } + + public virtual IActionResult OnGet() + { + Input = new VerifyCodeInputModel(); + + return Page(); + } + + public virtual async Task OnPostAsync() + { + // ��֤�û���¼״̬ + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + Alerts.Warning(L["TwoFactorAuthenticationInvaidUser"]); + return Page(); + } + // ˫���ص�¼ + var result = await SignInManager.TwoFactorSignInAsync(Provider, Input.VerifyCode, RememberMe, Input.RememberBrowser); + if (result.Succeeded) + { + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); + } + if (result.IsLockedOut) + { + Logger.LogWarning(7, "User account locked out."); + Alerts.Warning(L["UserLockedOutMessage"]); + return Page(); + } + else + { + Alerts.Danger(L["TwoFactorAuthenticationInvaidUser"]);// TODO: ����״̬��Ľ�� + return Page(); + } + } + } + + public class VerifyCodeInputModel + { + /// + /// �Ƿ���������м�ס��¼״̬ + /// + public bool RememberBrowser { get; set; } + /// + /// ���͵���֤�� + /// + [Required] + public string VerifyCode { get; set; } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml new file mode 100644 index 000000000..c149bd95d --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml @@ -0,0 +1,36 @@ +@page +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Alert +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Grid +@using Volo.Abp.Users +@model PackageName.CompanyName.ProjectName.AIO.Host.Pages.IndexModel +@inject ICurrentUser CurrentUser +@if (CurrentUser.IsAuthenticated) +{ +
+ + + + Logout + + +

@CurrentUser.UserName

+
@CurrentUser.Email
+
+ Roles: @CurrentUser.Roles.JoinAsString(", ") +
+ Claims:
+ @Html.Raw(CurrentUser.GetAllClaims().Select(c => $"{c.Type}={c.Value}").JoinAsString("
")) +
+
+
+
+} + +@if (!CurrentUser.IsAuthenticated) +{ +
+

+ Login +
+} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml.cs new file mode 100644 index 000000000..7047bc406 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/Index.cshtml.cs @@ -0,0 +1,11 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.Pages +{ + public class IndexModel : AbpPageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/_ViewImports.cshtml b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/_ViewImports.cshtml new file mode 100644 index 000000000..c1da1f5f1 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Pages/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Program.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Program.cs new file mode 100644 index 000000000..3daf07fe4 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Program.cs @@ -0,0 +1,82 @@ +using LINGYUN.Abp.Identity.Session.AspNetCore; +using PackageName.CompanyName.ProjectName.AIO.Host; +using Microsoft.AspNetCore.Cors; +using Serilog; +using Volo.Abp.IO; +using Volo.Abp.Modularity.PlugIns; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy + .WithOrigins( + builder.Configuration["App:CorsOrigins"] + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .WithAbpWrapExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); +}); +builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .UseSerilog((context, provider, config) => + { + config.ReadFrom.Configuration(context.Configuration); + }); + +await builder.AddApplicationAsync(options => +{ + MicroServiceApplicationsSingleModule.ApplicationName = Environment.GetEnvironmentVariable("APPLICATION_NAME") + ?? MicroServiceApplicationsSingleModule.ApplicationName; + options.ApplicationName = MicroServiceApplicationsSingleModule.ApplicationName; + options.Configuration.UserSecretsId = Environment.GetEnvironmentVariable("APPLICATION_USER_SECRETS_ID"); + options.Configuration.UserSecretsAssembly = typeof(MicroServiceApplicationsSingleModule).Assembly; + var pluginFolder = Path.Combine( + Directory.GetCurrentDirectory(), "Modules"); + DirectoryHelper.CreateIfNotExists(pluginFolder); + options.PlugInSources.AddFolder( + pluginFolder, + SearchOption.AllDirectories); +}); + +var app = builder.Build(); + +await app.InitializeApplicationAsync(); + +app.UseForwardedHeaders(); +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +// app.UseAbpExceptionHandling(); +app.UseCookiePolicy(); +app.UseMapRequestLocalization(); +app.UseCorrelationId(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseCors(); +app.UseAuthentication(); +app.UseMultiTenancy(); +app.UseUnitOfWork(); +app.UseAbpOpenIddictValidation(); +app.UseAbpSession(); +app.UseDynamicClaims(); +app.UseAuthorization(); +app.UseSwagger(); +app.UseSwaggerUI(options => +{ + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support App API"); +}); +app.UseAuditing(); +app.UseAbpSerilogEnrichers(); +app.UseConfiguredEndpoints(); + +await app.RunAsync(); diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Properties/launchSettings.json b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Properties/launchSettings.json new file mode 100644 index 000000000..337677308 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:19139", + "sslPort": 0 + } + }, + "profiles": { + "LY.MicroService.Applications.Single": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://0.0.0.0:30001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + } + }, + "LY.MicroService.Applications.Single.Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://0.0.0.0:30000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/TenantHeaderParamter.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/TenantHeaderParamter.cs new file mode 100644 index 000000000..a4218a9c4 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/TenantHeaderParamter.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; + +namespace PackageName.CompanyName.ProjectName.AIO.Host; + +public class TenantHeaderParamter : IOperationFilter +{ + private readonly AbpMultiTenancyOptions _multiTenancyOptions; + private readonly AbpAspNetCoreMultiTenancyOptions _aspNetCoreMultiTenancyOptions; + public TenantHeaderParamter( + IOptions multiTenancyOptions, + IOptions aspNetCoreMultiTenancyOptions) + { + _multiTenancyOptions = multiTenancyOptions.Value; + _aspNetCoreMultiTenancyOptions = aspNetCoreMultiTenancyOptions.Value; + } + + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (_multiTenancyOptions.IsEnabled) + { + operation.Parameters = operation.Parameters ?? new List(); + operation.Parameters.Add(new OpenApiParameter + { + Name = _aspNetCoreMultiTenancyOptions.TenantKey, + In = ParameterLocation.Header, + Description = "Tenant Id in http header", + Required = false + }); + } + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/TextMessageReplyContributor.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/TextMessageReplyContributor.cs new file mode 100644 index 000000000..31aafda51 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/TextMessageReplyContributor.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.WeChat.Common.Messages.Handlers; +using LINGYUN.Abp.WeChat.Official.Messages.Models; +using LINGYUN.Abp.WeChat.Official.Services; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.WeChat.Official.Messages; +/// +/// 文本消息客服回复 +/// +public class TextMessageReplyContributor : IMessageHandleContributor +{ + public async virtual Task HandleAsync(MessageHandleContext context) + { + var messageSender = context.ServiceProvider.GetRequiredService(); + + await messageSender.SendAsync( + new LINGYUN.Abp.WeChat.Official.Services.Models.TextMessageModel( + context.Message.FromUserName, + new LINGYUN.Abp.WeChat.Official.Services.Models.TextMessage( + context.Message.Content))); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/UserSubscribeEventContributor.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/UserSubscribeEventContributor.cs new file mode 100644 index 000000000..806302c0d --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Official/Messages/UserSubscribeEventContributor.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.WeChat.Common.Messages.Handlers; +using LINGYUN.Abp.WeChat.Official.Messages.Models; +using LINGYUN.Abp.WeChat.Official.Services; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.WeChat.Official.Messages; +/// +/// 用户关注回复消息 +/// +public class UserSubscribeEventContributor : IEventHandleContributor +{ + public async virtual Task HandleAsync(MessageHandleContext context) + { + var messageSender = context.ServiceProvider.GetRequiredService(); + + await messageSender.SendAsync( + new LINGYUN.Abp.WeChat.Official.Services.Models.TextMessageModel( + context.Message.FromUserName, + new LINGYUN.Abp.WeChat.Official.Services.Models.TextMessage( + "感谢您的关注, 点击菜单了解更多."))); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Work/Messages/TextMessageReplyContributor.cs b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Work/Messages/TextMessageReplyContributor.cs new file mode 100644 index 000000000..f85f858a1 --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/WeChat/Work/Messages/TextMessageReplyContributor.cs @@ -0,0 +1,24 @@ +using LINGYUN.Abp.WeChat.Common.Messages.Handlers; +using LINGYUN.Abp.WeChat.Work.Common.Messages.Models; +using LINGYUN.Abp.WeChat.Work.Messages; + +namespace PackageName.CompanyName.ProjectName.AIO.Host.WeChat.Work.Messages; +/// +/// 文本消息客服回复 +/// +public class TextMessageReplyContributor : IMessageHandleContributor +{ + public async virtual Task HandleAsync(MessageHandleContext context) + { + var messageSender = context.ServiceProvider.GetRequiredService(); + + await messageSender.SendAsync( + new LINGYUN.Abp.WeChat.Work.Messages.Models.WeChatWorkTextMessage( + context.Message.AgentId.ToString(), + new LINGYUN.Abp.WeChat.Work.Messages.Models.TextMessage( + context.Message.Content)) + { + ToUser = context.Message.FromUserName, + }); + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.Development.json b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.Development.json new file mode 100644 index 000000000..4f753c08e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.Development.json @@ -0,0 +1,249 @@ +{ + "App": { + "ShowPii": true, + "SelfUrl": "http://127.0.0.1:30001/", + "CorsOrigins": "http://127.0.0.1:3100,http://127.0.0.1:30001", + "Urls": { + "Applications": { + "MVC": { + "RootUrl": "http://127.0.0.1:30001/", + "Urls": { + "Abp.Account.EmailConfirm": "Account/EmailConfirm", + "Abp.Account.EmailVerifyLogin": "Account/VerifyCode" + } + }, + "STS": { + "RootUrl": "http://127.0.0.1:30001/" + }, + "VueVbenAdmin": { + "RootUrl": "http://127.0.0.1:3100", + "Urls": { + "Abp.Account.EmailConfirm": "account/email-confirm" + } + } + } + } + }, + "Auditing": { + "AllEntitiesSelector": true + }, + "DistributedCache": { + "HideErrors": true, + "KeyPrefix": "LINGYUN.Abp.Application", + "GlobalCacheEntryOptions": { + "SlidingExpiration": "30:00:00", + "AbsoluteExpirationRelativeToNow": "60:00:00" + } + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None"//Mysql +// "Default": "Host=127.0.0.1;Database=Platform-V70;Username=postgres;Password=123456;"//Postgres + }, + "DistributedLock": { + "IsEnabled": true, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=14" + } + }, + "Elsa": { + "Features": { + "DefaultPersistence": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + }, + "Console": true, + "Http": true, + "Email": true, + "TemporalQuartz": true, + "JavaScriptActivities": true, + "UserTask": true, + "Conductor": true, + "Telnyx": true, + "BlobStoring": true, + "Emailing": true, + "Notification": true, + "Sms": true, + "IM": true, + "PublishWebhook": true, + "Webhooks": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + }, + "WorkflowSettings": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + } + }, + "Server": { + "BaseUrl": "http://127.0.0.1:30000" + } + }, + "Quartz": { + "UsePersistentStore": false, + "Properties": { + "quartz.jobStore.dataSource": "tkm", + "quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX,Quartz", + "quartz.dataSource.tkm.connectionStringName": "Default", + "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.MySQLDelegate,Quartz", + "quartz.dataSource.tkm.connectionString": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456", + "quartz.dataSource.tkm.provider": "MySqlConnector", +// "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.PostgreSQLDelegate,Quartz", +// "quartz.dataSource.tkm.connectionString": "Host=127.0.0.1;Database=Platform-V70;Username=postgres;Password=123456;", +// "quartz.dataSource.tkm.provider": "Npgsql", + "quartz.jobStore.clustered": "true", + "quartz.serializer.type": "json" + } + }, + "Redis": { + "IsEnabled": true, + "Configuration": "127.0.0.1,defaultDatabase=15", + "InstanceName": "LINGYUN.Abp.Application" + }, + "Features": { + "Validation": { + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=13", + "InstanceName": "LINGYUN.Abp.Application" + } + } + }, + "AuthServer": { + "UseOpenIddict": true, + "Authority": "http://127.0.0.1:30001/", + "Audience": "lingyun-abp-application", + "RequireHttpsMetadata": false, + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "IdentityServer": { + "Clients": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "OpenIddict": { + "Applications": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + }, + "Lifetime": { + "AuthorizationCode": "00:05:00", + "AccessToken": "14:00:00", + "DeviceCode": "00:10:00", + "IdentityToken": "00:20:00", + "RefreshToken": "14:00:00", + "RefreshTokenReuseLeeway": "00:00:30", + "UserCode": "00:10:00" + } + }, + "Identity": { + "Password": { + "RequiredLength": 6, + "RequiredUniqueChars": 0, + "RequireNonAlphanumeric": false, + "RequireLowercase": false, + "RequireUppercase": false, + "RequireDigit": false + }, + "Lockout": { + "AllowedForNewUsers": false, + "LockoutDuration": 5, + "MaxFailedAccessAttempts": 5 + }, + "SignIn": { + "RequireConfirmedEmail": false, + "RequireConfirmedPhoneNumber": false + } + }, + "FeatureManagement": { + "IsDynamicStoreEnabled": true + }, + "SettingManagement": { + "IsDynamicStoreEnabled": true + }, + "PermissionManagement": { + "IsDynamicStoreEnabled": true + }, + "TextTemplating": { + "IsDynamicStoreEnabled": true + }, + "WebhooksManagement": { + "IsDynamicStoreEnabled": true + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Minio": { + "WithSSL": false, + "BucketName": "blobs", + "EndPoint": "127.0.0.1:19000", + "AccessKey": "ZD43kNpimiJf9mCuomTP", + "SecretKey": "w8IqMgi4Tnz0DGzN8jZ7IJWq7OEdbUnAU0jlZxQK", + "CreateBucketIfNotExists": false + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.json b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.json new file mode 100644 index 000000000..ff9beea3e --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/appsettings.json @@ -0,0 +1,89 @@ +{ + "Clock": { + "Kind": "Local" + }, + "Forwarded": { + "ForwardedHeaders": "XForwardedFor,XForwardedProto" + }, + "StringEncryption": { + "DefaultPassPhrase": "s46c5q55nxpeS8Ra", + "InitVectorBytes": "s83ng0abvd02js84", + "DefaultSalt": "sf&5)s3#" + }, + "Json": { + "OutputDateTimeFormat": "yyyy-MM-dd HH:mm:ss", + "InputDateTimeFormats": [ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-ddTHH:mm:ss" + ] + }, + "AllowedHosts": "*", + "Hosting": { + "BasePath": "" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Debug-.log", + "restrictedToMinimumLevel": "Debug", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Info-.log", + "restrictedToMinimumLevel": "Information", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Warn-.log", + "restrictedToMinimumLevel": "Warning", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Error-.log", + "restrictedToMinimumLevel": "Error", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Fatal-.log", + "restrictedToMinimumLevel": "Fatal", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/gulpfile.js b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/gulpfile.js new file mode 100644 index 000000000..bec4d578f --- /dev/null +++ b/aspnet-core/templates/aio/content/host/PackageName.CompanyName.ProjectName.AIO.Host/gulpfile.js @@ -0,0 +1,10 @@ +"use strict"; + +var gulp = require("gulp"), + path = require('path'), + copyResources = require('./node_modules/@abp/aspnetcore.mvc.ui/gulp/copy-resources.js'); + +exports.default = function(done){ + copyResources(path.resolve('./')); + done(); +}; \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/PackageName.CompanyName.ProjectName.AIO.DbMigrator.csproj b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/PackageName.CompanyName.ProjectName.AIO.DbMigrator.csproj new file mode 100644 index 000000000..3fe9a4c81 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/PackageName.CompanyName.ProjectName.AIO.DbMigrator.csproj @@ -0,0 +1,54 @@ + + + Exe + net8.0 + enable + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + + PreserveNewest + + + + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Program.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Program.cs new file mode 100644 index 000000000..7153c46d4 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Program.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; + +namespace PackageName.CompanyName.ProjectName.AIO.DbMigrator; + +public class Program +{ + public async static Task Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning) +#if DEBUG + .MinimumLevel.Override("LY.MicroService.Applications.Single.DbMigrator", LogEventLevel.Debug) +#else + .MinimumLevel.Override("LY.MicroService.Applications.Single.DbMigrator", LogEventLevel.Information) +#endif + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("Logs/migrations.txt") + .CreateLogger(); + await CreateHostBuilder(args).RunConsoleAsync(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .AddAppSettingsSecretsJson() + // .ConfigureAppConfiguration((context, builder) => + // { + // builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + // .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true); + // } ) + .ConfigureLogging((context, logging) => logging.ClearProviders()) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.EN.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.EN.md new file mode 100644 index 000000000..e60db3ab6 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.EN.md @@ -0,0 +1,75 @@ +# LY.MicroService.Applications.Single.DbMigrator + +Single application database migration tool for automatically executing database migrations and initializing data. + +[简体中文](./README.md) + +## Features + +* Automatic database migration execution +* Multi-environment configuration support +* Integrated Serilog logging +* Data migration environment configuration support +* Automatic database migration check and application +* Console application, easy to integrate into CI/CD pipelines + +## Configuration + +```json +{ + "ConnectionStrings": { + "Default": "your-database-connection-string" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Volo.Abp": "Warning" + } + } + } +} +``` + +## Basic Usage + +1. Configure Database Connection + * Configure database connection string in appsettings.json + * Use appsettings.{Environment}.json for different environment configurations + +2. Run Migration Tool + ```bash + dotnet run + ``` + +3. View Migration Logs + * Console output + * Logs/migrations.txt file + +## Environment Variables + +* `ASPNETCORE_ENVIRONMENT`: Set runtime environment (Development, Staging, Production, etc.) +* `DOTNET_ENVIRONMENT`: Same as above, for compatibility + +## Notes + +* Ensure database connection string includes sufficient permissions +* Recommended to backup database before executing migrations +* Check migrations.txt log file for migration details +* If migration fails, check error messages in logs + +## Development and Debugging + +1. Set Environment Variables + ```bash + export ASPNETCORE_ENVIRONMENT=Development + ``` + +2. Debug with Visual Studio or Visual Studio Code + * Set breakpoints + * View detailed migration process + +3. Customize Migration Logic + * Modify SingleDbMigrationService class + * Add new data seeds diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.md new file mode 100644 index 000000000..1a997f51b --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/README.md @@ -0,0 +1,75 @@ +# LY.MicroService.Applications.Single.DbMigrator + +单体应用数据库迁移工具,用于自动执行数据库迁移和初始化数据。 + +[English](./README.EN.md) + +## 功能特性 + +* 自动执行数据库迁移 +* 支持多环境配置 +* 集成Serilog日志记录 +* 支持数据迁移环境配置 +* 自动检查和应用数据库迁移 +* 控制台应用程序,方便集成到CI/CD流程 + +## 配置项 + +```json +{ + "ConnectionStrings": { + "Default": "你的数据库连接字符串" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Volo.Abp": "Warning" + } + } + } +} +``` + +## 基本用法 + +1. 配置数据库连接 + * 在appsettings.json中配置数据库连接字符串 + * 可以通过appsettings.{Environment}.json配置不同环境的连接字符串 + +2. 运行迁移工具 + ```bash + dotnet run + ``` + +3. 查看迁移日志 + * 控制台输出 + * Logs/migrations.txt文件 + +## 环境变量 + +* `ASPNETCORE_ENVIRONMENT`: 设置运行环境(Development、Staging、Production等) +* `DOTNET_ENVIRONMENT`: 同上,用于兼容性 + +## 注意事项 + +* 确保数据库连接字符串中包含足够的权限 +* 建议在执行迁移前备份数据库 +* 查看migrations.txt日志文件以了解迁移详情 +* 如果迁移失败,检查日志中的错误信息 + +## 开发调试 + +1. 设置环境变量 + ```bash + export ASPNETCORE_ENVIRONMENT=Development + ``` + +2. 使用Visual Studio或Visual Studio Code进行调试 + * 可以设置断点 + * 查看详细的迁移过程 + +3. 自定义迁移逻辑 + * 修改SingleDbMigrationService类 + * 添加新的数据种子 diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorHostedService.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorHostedService.cs new file mode 100644 index 000000000..2c15b65f1 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorHostedService.cs @@ -0,0 +1,51 @@ +using LY.MicroService.Applications.Single.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Volo.Abp; +using Volo.Abp.Data; + +namespace PackageName.CompanyName.ProjectName.AIO.DbMigrator; + +public class SingleDbMigratorHostedService : IHostedService +{ + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly IConfiguration _configuration; + + public SingleDbMigratorHostedService( + IHostApplicationLifetime hostApplicationLifetime, + IConfiguration configuration) + { + _hostApplicationLifetime = hostApplicationLifetime; + _configuration = configuration; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using var application = await AbpApplicationFactory + .CreateAsync(options => + { + options.Services.ReplaceConfiguration(_configuration); + options.UseAutofac(); + options.Services.AddLogging(c => c.AddSerilog()); + options.AddDataMigrationEnvironment(); + }); + await application.InitializeAsync(); + + await application + .ServiceProvider + .GetRequiredService() + .CheckAndApplyDatabaseMigrationsAsync(); + + await application.ShutdownAsync(); + + _hostApplicationLifetime.StopApplication(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} + diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.Configure.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.Configure.cs new file mode 100644 index 000000000..0c4baaacb --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.Configure.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using Volo.Abp.Timing; + +namespace PackageName.CompanyName.ProjectName.AIO.DbMigrator; +public partial class SingleDbMigratorModule +{ + private void ConfigureTiming(IConfiguration configuration) + { + Configure(options => + { + configuration.GetSection("Clock").Bind(options); + }); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.cs new file mode 100644 index 000000000..f730fe211 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/SingleDbMigratorModule.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.UI.Navigation.VueVbenAdmin; +using Microsoft.Extensions.DependencyInjection; +using PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName.AIO.DbMigrator; + +[DependsOn( + typeof(AbpUINavigationVueVbenAdminModule), + // typeof(SingleMigrationsEntityFrameworkCorePostgreSqlModule), + typeof(SingleMigrationsEntityFrameworkCoreMySqlModule), + typeof(AbpAutofacModule) + )] +public partial class SingleDbMigratorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + ConfigureTiming(configuration); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Usings.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Usings.cs new file mode 100644 index 000000000..c79c834ca --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/Usings.cs @@ -0,0 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; +using LINGYUN.Abp; diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.MySql.json b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.MySql.json new file mode 100644 index 000000000..d6bbaf791 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.MySql.json @@ -0,0 +1,228 @@ +{ + "App": { + "ShowPii": true, + "SelfUrl": "http://127.0.0.1:30001/", + "CorsOrigins": "http://127.0.0.1:3100,http://127.0.0.1:30001", + "Urls": { + "Applications": { + "MVC": { + "RootUrl": "http://127.0.0.1:30001/", + "Urls": { + "Abp.Account.EmailConfirm": "Account/EmailConfirm", + "Abp.Account.EmailVerifyLogin": "Account/VerifyCode" + } + }, + "STS": { + "RootUrl": "http://127.0.0.1:30001/" + }, + "VueVbenAdmin": { + "RootUrl": "http://127.0.0.1:3100", + "Urls": { + "Abp.Account.EmailConfirm": "account/email-confirm" + } + } + } + } + }, + "Auditing": { + "AllEntitiesSelector": true + }, + "DistributedCache": { + "HideErrors": true, + "KeyPrefix": "LINGYUN.Abp.Application", + "GlobalCacheEntryOptions": { + "SlidingExpiration": "30:00:00", + "AbsoluteExpirationRelativeToNow": "60:00:00" + } + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None" + }, + "DistributedLock": { + "IsEnabled": true, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=14" + } + }, + "Elsa": { + "Features": { + "DefaultPersistence": { + "Enabled": true, + "ConnectionStringIdentifier": "Workflow", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + }, + "Console": true, + "Http": true, + "Email": true, + "TemporalQuartz": true, + "JavaScriptActivities": true, + "UserTask": true, + "Conductor": true, + "Telnyx": true, + "BlobStoring": true, + "Emailing": true, + "Notification": true, + "Sms": true, + "IM": true, + "PublishWebhook": true, + "Webhooks": { + "Enabled": true, + "ConnectionStringIdentifier": "Workflow", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + }, + "WorkflowSettings": { + "Enabled": true, + "ConnectionStringIdentifier": "Workflow", + "EntityFrameworkCore": { + "MySql": { + "Enabled": true + } + } + } + }, + "Server": { + "BaseUrl": "http://127.0.0.1:30000" + } + }, + "Quartz": { + "UsePersistentStore": false, + "Properties": { + "quartz.jobStore.dataSource": "tkm", + "quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX,Quartz", + "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.MySQLDelegate,Quartz", + "quartz.dataSource.tkm.connectionString": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456", + "quartz.dataSource.tkm.connectionStringName": "TaskManagement", + "quartz.dataSource.tkm.provider": "MySqlConnector", + "quartz.jobStore.clustered": "true", + "quartz.serializer.type": "json" + } + }, + "Redis": { + "IsEnabled": true, + "Configuration": "127.0.0.1,defaultDatabase=15", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "UseOpenIddict": true, + "Authority": "http://127.0.0.1:30001/", + "ApiName": "lingyun-abp-application", + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "IdentityServer": { + "Clients": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "OpenIddict": { + "Applications": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + }, + "Lifetime": { + "AuthorizationCode": "00:05:00", + "AccessToken": "14:00:00", + "DeviceCode": "00:10:00", + "IdentityToken": "00:20:00", + "RefreshToken": "14:00:00", + "RefreshTokenReuseLeeway": "00:00:30", + "UserCode": "00:10:00" + } + }, + "Identity": { + "Password": { + "RequiredLength": 6, + "RequiredUniqueChars": 0, + "RequireNonAlphanumeric": false, + "RequireLowercase": false, + "RequireUppercase": false, + "RequireDigit": false + }, + "Lockout": { + "AllowedForNewUsers": false, + "LockoutDuration": 5, + "MaxFailedAccessAttempts": 5 + }, + "SignIn": { + "RequireConfirmedEmail": false, + "RequireConfirmedPhoneNumber": false + } + }, + "FeatureManagement": { + "IsDynamicStoreEnabled": true + }, + "SettingManagement": { + "IsDynamicStoreEnabled": true + }, + "PermissionManagement": { + "IsDynamicStoreEnabled": true + }, + "TextTemplating": { + "IsDynamicStoreEnabled": true + }, + "WebhooksManagement": { + "IsDynamicStoreEnabled": true + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.PostgreSql.json b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.PostgreSql.json new file mode 100644 index 000000000..6858f5041 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.PostgreSql.json @@ -0,0 +1,228 @@ +{ + "App": { + "ShowPii": true, + "SelfUrl": "http://127.0.0.1:30001/", + "CorsOrigins": "http://127.0.0.1:3100,http://127.0.0.1:30001", + "Urls": { + "Applications": { + "MVC": { + "RootUrl": "http://127.0.0.1:30001/", + "Urls": { + "Abp.Account.EmailConfirm": "Account/EmailConfirm", + "Abp.Account.EmailVerifyLogin": "Account/VerifyCode" + } + }, + "STS": { + "RootUrl": "http://127.0.0.1:30001/" + }, + "VueVbenAdmin": { + "RootUrl": "http://127.0.0.1:3100", + "Urls": { + "Abp.Account.EmailConfirm": "account/email-confirm" + } + } + } + } + }, + "Auditing": { + "AllEntitiesSelector": true + }, + "DistributedCache": { + "HideErrors": true, + "KeyPrefix": "LINGYUN.Abp.Application", + "GlobalCacheEntryOptions": { + "SlidingExpiration": "30:00:00", + "AbsoluteExpirationRelativeToNow": "60:00:00" + } + }, + "ConnectionStrings": { + "Default": "Host=127.0.0.1;Database=Platform-V70;Username=postgres;Password=123456;SslMode=Prefer" + }, + "DistributedLock": { + "IsEnabled": true, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=14" + } + }, + "Elsa": { + "Features": { + "DefaultPersistence": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "PostgreSql": { + "Enabled": true + } + } + }, + "Console": true, + "Http": true, + "Email": true, + "TemporalQuartz": true, + "JavaScriptActivities": true, + "UserTask": true, + "Conductor": true, + "Telnyx": true, + "BlobStoring": true, + "Emailing": true, + "Notification": true, + "Sms": true, + "IM": true, + "PublishWebhook": true, + "Webhooks": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "PostgreSql": { + "Enabled": true + } + } + }, + "WorkflowSettings": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "PostgreSql": { + "Enabled": true + } + } + } + }, + "Server": { + "BaseUrl": "http://127.0.0.1:30000" + } + }, + "Quartz": { + "UsePersistentStore": false, + "Properties": { + "quartz.jobStore.dataSource": "tkm", + "quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", + "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.PostgreSQLDelegate, Quartz", + "quartz.dataSource.tkm.connectionString": "Host=127.0.0.1;Database=Platform-V70;Username=postgres;Password=123456;", + "quartz.dataSource.tkm.provider": "Npgsql", + "quartz.jobStore.clustered": "true", + "quartz.serializer.type": "json", + "quartz.dataSource.tkm.connectionStringName": "TaskManagement" + } + }, + "Redis": { + "IsEnabled": true, + "Configuration": "127.0.0.1,defaultDatabase=15", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "UseOpenIddict": true, + "Authority": "http://127.0.0.1:30001/", + "ApiName": "lingyun-abp-application", + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "IdentityServer": { + "Clients": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "OpenIddict": { + "Applications": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + }, + "Lifetime": { + "AuthorizationCode": "00:05:00", + "AccessToken": "14:00:00", + "DeviceCode": "00:10:00", + "IdentityToken": "00:20:00", + "RefreshToken": "14:00:00", + "RefreshTokenReuseLeeway": "00:00:30", + "UserCode": "00:10:00" + } + }, + "Identity": { + "Password": { + "RequiredLength": 6, + "RequiredUniqueChars": 0, + "RequireNonAlphanumeric": false, + "RequireLowercase": false, + "RequireUppercase": false, + "RequireDigit": false + }, + "Lockout": { + "AllowedForNewUsers": false, + "LockoutDuration": 5, + "MaxFailedAccessAttempts": 5 + }, + "SignIn": { + "RequireConfirmedEmail": false, + "RequireConfirmedPhoneNumber": false + } + }, + "FeatureManagement": { + "IsDynamicStoreEnabled": true + }, + "SettingManagement": { + "IsDynamicStoreEnabled": true + }, + "PermissionManagement": { + "IsDynamicStoreEnabled": true + }, + "TextTemplating": { + "IsDynamicStoreEnabled": true + }, + "WebhooksManagement": { + "IsDynamicStoreEnabled": true + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.SqlServer.json b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.SqlServer.json new file mode 100644 index 000000000..555dab9fb --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.SqlServer.json @@ -0,0 +1,228 @@ +{ + "App": { + "ShowPii": true, + "SelfUrl": "http://127.0.0.1:30001/", + "CorsOrigins": "http://127.0.0.1:3100,http://127.0.0.1:30001", + "Urls": { + "Applications": { + "MVC": { + "RootUrl": "http://127.0.0.1:30001/", + "Urls": { + "Abp.Account.EmailConfirm": "Account/EmailConfirm", + "Abp.Account.EmailVerifyLogin": "Account/VerifyCode" + } + }, + "STS": { + "RootUrl": "http://127.0.0.1:30001/" + }, + "VueVbenAdmin": { + "RootUrl": "http://127.0.0.1:3100", + "Urls": { + "Abp.Account.EmailConfirm": "account/email-confirm" + } + } + } + } + }, + "Auditing": { + "AllEntitiesSelector": true + }, + "DistributedCache": { + "HideErrors": true, + "KeyPrefix": "LINGYUN.Abp.Application", + "GlobalCacheEntryOptions": { + "SlidingExpiration": "30:00:00", + "AbsoluteExpirationRelativeToNow": "60:00:00" + } + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform-V70;User Id=sa;Password=yourStrong(!)Password;TrustServerCertificate=True" + }, + "DistributedLock": { + "IsEnabled": true, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=14" + } + }, + "Elsa": { + "Features": { + "DefaultPersistence": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "SqlServer": { + "Enabled": true + } + } + }, + "Console": true, + "Http": true, + "Email": true, + "TemporalQuartz": true, + "JavaScriptActivities": true, + "UserTask": true, + "Conductor": true, + "Telnyx": true, + "BlobStoring": true, + "Emailing": true, + "Notification": true, + "Sms": true, + "IM": true, + "PublishWebhook": true, + "Webhooks": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "SqlServer": { + "Enabled": true + } + } + }, + "WorkflowSettings": { + "Enabled": true, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "SqlServer": { + "Enabled": true + } + } + } + }, + "Server": { + "BaseUrl": "http://127.0.0.1:30000" + } + }, + "Quartz": { + "UsePersistentStore": false, + "Properties": { + "quartz.jobStore.dataSource": "tkm", + "quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", + "quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz", + "quartz.dataSource.tkm.connectionString": "Server=127.0.0.1;Database=Platform-V70;User Id=sa;Password=yourStrong(!)Password;", + "quartz.dataSource.tkm.provider": "SqlServer", + "quartz.jobStore.clustered": "true", + "quartz.serializer.type": "json", + "quartz.dataSource.tkm.connectionStringName": "TaskManagement" + } + }, + "Redis": { + "IsEnabled": true, + "Configuration": "127.0.0.1,defaultDatabase=15", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "UseOpenIddict": true, + "Authority": "http://127.0.0.1:30001/", + "ApiName": "lingyun-abp-application", + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "IdentityServer": { + "Clients": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "OpenIddict": { + "Applications": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:3100/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + }, + "Lifetime": { + "AuthorizationCode": "00:05:00", + "AccessToken": "14:00:00", + "DeviceCode": "00:10:00", + "IdentityToken": "00:20:00", + "RefreshToken": "14:00:00", + "RefreshTokenReuseLeeway": "00:00:30", + "UserCode": "00:10:00" + } + }, + "Identity": { + "Password": { + "RequiredLength": 6, + "RequiredUniqueChars": 0, + "RequireNonAlphanumeric": false, + "RequireLowercase": false, + "RequireUppercase": false, + "RequireDigit": false + }, + "Lockout": { + "AllowedForNewUsers": false, + "LockoutDuration": 5, + "MaxFailedAccessAttempts": 5 + }, + "SignIn": { + "RequireConfirmedEmail": false, + "RequireConfirmedPhoneNumber": false + } + }, + "FeatureManagement": { + "IsDynamicStoreEnabled": true + }, + "SettingManagement": { + "IsDynamicStoreEnabled": true + }, + "PermissionManagement": { + "IsDynamicStoreEnabled": true + }, + "TextTemplating": { + "IsDynamicStoreEnabled": true + }, + "WebhooksManagement": { + "IsDynamicStoreEnabled": true + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.json b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.json new file mode 100644 index 000000000..4a3dae71a --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.DbMigrator/appsettings.json @@ -0,0 +1,104 @@ +{ + "Clock": { + "Kind": "Local" + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None"//MySql +// "Default": "Host=127.0.0.1;Database=Platform-V70;Username=postgres;Password=123456;SslMode=Prefer"//PostgreSql + }, + "StringEncryption": { + "DefaultPassPhrase": "s46c5q55nxpeS8Ra", + "InitVectorBytes": "s83ng0abvd02js84", + "DefaultSalt": "sf&5)s3#" + }, + "AuthServer": { + "UseOpenIddict": true + }, + "IdentityServer": { + "Clients": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:40080/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "OpenIddict": { + "Applications": { + "VueAdmin": { + "ClientId": "vue-admin-client", + "RootUrl": "http://127.0.0.1:40080/" + }, + "InternalService": { + "ClientId": "InternalServiceClient" + } + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Debug-.log", + "restrictedToMinimumLevel": "Debug", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Info-.log", + "restrictedToMinimumLevel": "Information", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Warn-.log", + "restrictedToMinimumLevel": "Warning", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Error-.log", + "restrictedToMinimumLevel": "Error", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Fatal-.log", + "restrictedToMinimumLevel": "Fatal", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xml b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xml new file mode 100644 index 000000000..be68c182b --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xsd b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql.csproj b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql.csproj new file mode 100644 index 000000000..5af36315b --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql.csproj @@ -0,0 +1,22 @@ + + + + + + + net8.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.en.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.en.md new file mode 100644 index 000000000..e1e97c34f --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.en.md @@ -0,0 +1,59 @@ +# MySQL Database Migration Guide + +This guide will help you manage MySQL database migrations using the migration scripts. + +## Prerequisites + +1. Ensure .NET Core SDK is installed +2. Ensure Entity Framework Core tools are installed + ```powershell + dotnet tool install --global dotnet-ef + ``` +3. Ensure MySQL connection string is properly configured + +## Usage Instructions + +### 1. Create New Migration + +1. Run the migration script in the `aspnet-core/migrations` directory: + ```powershell + # Use English version + .\MigrateEn.ps1 + + # Or use Chinese version + .\Migrate.ps1 + ``` + +2. Select MySQL database context from the menu: + ``` + [1] LY.MicroService.Applications.Single.EntityFrameworkCore.MySql + ``` + +3. Enter migration name (optional): + - Press Enter to use default name: `AddNewMigration_yyyyMMdd_HHmmss` + - Or enter custom name, e.g.: `AddNewFeature` + +### 2. Generate SQL Script + +After creating the migration, the script will ask if you want to generate SQL script: + +1. Choose whether to generate SQL script (Y/N) +2. If Y is selected, following options will be available: + - `[A]` - Generate SQL script for all migrations + - `[L]` - Generate SQL script for latest migration only + - `[0-9]` - Generate from specified migration version + +Generated SQL scripts will be saved in: +``` +aspnet-core/InitSql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/ +``` + +### 3. Apply Migration + +Generated SQL scripts can be applied to database through: + +1. Using MySQL client tools to execute SQL script directly +2. Or using command line: + ```powershell + mysql -u your_username -p your_database < your_script.sql + ``` diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.md new file mode 100644 index 000000000..295e1e512 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/README.md @@ -0,0 +1,59 @@ +# MySQL 数据库迁移指南 + +本指南将帮助您使用迁移脚本来管理 MySQL 数据库的迁移操作。 + +## 前置条件 + +1. 确保已安装 .NET Core SDK +2. 确保已安装 Entity Framework Core 工具 + ```powershell + dotnet tool install --global dotnet-ef + ``` +3. 确保已正确配置 MySQL 连接字符串 + +## 使用说明 + +### 1. 创建新的迁移 + +1. 在 `aspnet-core/migrations` 目录下运行迁移脚本: + ```powershell + # 使用中文版本 + .\Migrate.ps1 + + # 或使用英文版本 + .\MigrateEn.ps1 + ``` + +2. 在菜单中选择 MySQL 数据库上下文: + ``` + [1] LY.MicroService.Applications.Single.EntityFrameworkCore.MySql + ``` + +3. 输入迁移名称(可选): + - 直接回车将使用默认名称:`AddNewMigration_yyyyMMdd_HHmmss` + - 或输入自定义名称,如:`AddNewFeature` + +### 2. 生成 SQL 脚本 + +在创建迁移后,脚本会询问是否需要生成 SQL 脚本: + +1. 选择是否生成 SQL 脚本 (Y/N) +2. 如果选择 Y,将提供以下选项: + - `[A]` - 生成所有迁移的 SQL 脚本 + - `[L]` - 仅生成最新迁移的 SQL 脚本 + - `[0-9]` - 从指定的迁移版本开始生成 + +生成的 SQL 脚本将保存在: +``` +aspnet-core/InitSql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/ +``` + +### 3. 应用迁移 + +生成的 SQL 脚本可以通过以下方式应用到数据库: + +1. 使用 MySQL 客户端工具直接执行 SQL 脚本 +2. 或使用命令行: + ```powershell + mysql -u your_username -p your_database < your_script.sql + ``` diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsDbContextFactory.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsDbContextFactory.cs new file mode 100644 index 000000000..d34ba6dd0 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsDbContextFactory.cs @@ -0,0 +1,32 @@ +using LY.MicroService.Applications.Single.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql; + +public class SingleMigrationsDbContextFactory : IDesignTimeDbContextFactory +{ + public SingleMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var connectionString = configuration.GetConnectionString("Default"); + + var builder = new DbContextOptionsBuilder() + .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), b => b.MigrationsAssembly("LY.MicroService.Applications.Single.EntityFrameworkCore.MySql")); + + return new SingleMigrationsDbContext(builder!.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), + "../LY.MicroService.Applications.Single.DbMigrator/")) + .AddJsonFile("appsettings.json", optional: false) + .AddJsonFile("appsettings.MySql.json", optional: true); + + return builder.Build(); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsEntityFrameworkCoreMySqlModule.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsEntityFrameworkCoreMySqlModule.cs new file mode 100644 index 000000000..e15058880 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql/SingleMigrationsEntityFrameworkCoreMySqlModule.cs @@ -0,0 +1,24 @@ +using LY.MicroService.Applications.Single.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.MySQL; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.MySql; + +[DependsOn( + typeof(AbpEntityFrameworkCoreMySQLModule), + typeof(SingleMigrationsEntityFrameworkCoreModule) + )] +public class SingleMigrationsEntityFrameworkCoreMySqlModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(); + + Configure(options => + { + options.UseMySQL(); + }); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs new file mode 100644 index 000000000..d3cfdd062 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs @@ -0,0 +1,469 @@ +using LINGYUN.Abp.IdentityServer.IdentityResources; +using Microsoft.Extensions.Configuration; +using OpenIddict.Abstractions; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.ApiScopes; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.IdentityResources; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore.DataSeeder; + +public class ClientDataSeederContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictScopeManager _scopeManager; + + private readonly IClientRepository _clientRepository; + private readonly IApiResourceRepository _apiResourceRepository; + private readonly IApiScopeRepository _apiScopeRepository; + private readonly ICustomIdentityResourceDataSeeder _customIdentityResourceDataSeeder; + private readonly IIdentityResourceDataSeeder _identityResourceDataSeeder; + + private readonly IGuidGenerator _guidGenerator; + private readonly IPermissionDataSeeder _permissionDataSeeder; + private readonly IConfiguration _configuration; + private readonly ICurrentTenant _currentTenant; + + public ClientDataSeederContributor( + IOpenIddictApplicationManager applicationManager, + IOpenIddictScopeManager scopeManager, + IClientRepository clientRepository, + IApiResourceRepository apiResourceRepository, + IApiScopeRepository apiScopeRepository, + ICustomIdentityResourceDataSeeder customIdentityResourceDataSeeder, + IIdentityResourceDataSeeder identityResourceDataSeeder, + IGuidGenerator guidGenerator, + IPermissionDataSeeder permissionDataSeeder, + IConfiguration configuration, + ICurrentTenant currentTenant) + { + _applicationManager = applicationManager; + _scopeManager = scopeManager; + _clientRepository = clientRepository; + _apiResourceRepository = apiResourceRepository; + _apiScopeRepository = apiScopeRepository; + _customIdentityResourceDataSeeder = customIdentityResourceDataSeeder; + _identityResourceDataSeeder = identityResourceDataSeeder; + _guidGenerator = guidGenerator; + _permissionDataSeeder = permissionDataSeeder; + _configuration = configuration; + _currentTenant = currentTenant; + } + + public async virtual Task SeedAsync(DataSeedContext context) + { + using (_currentTenant.Change(context.TenantId)) + { + if (_configuration.GetValue("AuthServer:UseOpenIddict")) + { + await SeedOpenIddictAsync(); + return; + } + + await SeedIdentityServerAsync(); + } + } + + #region OpenIddict + + private async Task SeedOpenIddictAsync() + { + await CreateScopeAsync("lingyun-abp-application"); + await CreateApplicationAsync("lingyun-abp-application"); + } + + private async Task CreateScopeAsync(string scope) + { + if (await _scopeManager.FindByNameAsync(scope) == null) + { + await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor() + { + Name = scope, + DisplayName = scope + " access", + DisplayNames = + { + [CultureInfo.GetCultureInfo("zh-Hans")] = "Abp API 应用程序访问", + [CultureInfo.GetCultureInfo("en")] = "Abp API Application Access" + }, + Resources = + { + scope + } + }); + } + } + + private async Task CreateApplicationAsync(string scope) + { + var configurationSection = _configuration.GetSection("OpenIddict:Applications"); + + var vueClientId = configurationSection["VueAdmin:ClientId"]; + if (!vueClientId.IsNullOrWhiteSpace()) + { + var vueClientRootUrl = configurationSection["VueAdmin:RootUrl"].EnsureEndsWith('/'); + + if (await _applicationManager.FindByClientIdAsync(vueClientId) == null) + { + await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor + { + ClientId = vueClientId, + ClientSecret = "1q2w3e*", + ApplicationType = OpenIddictConstants.ApplicationTypes.Web, + ConsentType = OpenIddictConstants.ConsentTypes.Explicit, + DisplayName = "Abp Vue Admin Client", + PostLogoutRedirectUris = + { + new Uri(vueClientRootUrl + "signout-callback-oidc"), + new Uri(vueClientRootUrl) + }, + RedirectUris = + { + new Uri(vueClientRootUrl + "/signin-oidc"), + new Uri(vueClientRootUrl) + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Device, + OpenIddictConstants.Permissions.Endpoints.Introspection, + OpenIddictConstants.Permissions.Endpoints.Revocation, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.Implicit, + OpenIddictConstants.Permissions.GrantTypes.Password, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.GrantTypes.DeviceCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + + OpenIddictConstants.Permissions.ResponseTypes.Code, + OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, + OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken, + OpenIddictConstants.Permissions.ResponseTypes.CodeToken, + OpenIddictConstants.Permissions.ResponseTypes.IdToken, + OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken, + OpenIddictConstants.Permissions.ResponseTypes.None, + OpenIddictConstants.Permissions.ResponseTypes.Token, + + OpenIddictConstants.Permissions.Scopes.Roles, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Address, + OpenIddictConstants.Permissions.Scopes.Phone, + OpenIddictConstants.Permissions.Prefixes.Scope + scope + } + }); + + var vueClientPermissions = new string[1] + { + "AbpIdentity.UserLookup" + }; + await _permissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, vueClientId, vueClientPermissions); + } + } + + var internalServiceClientId = configurationSection["InternalService:ClientId"]; + if (!internalServiceClientId.IsNullOrWhiteSpace()) + { + if (await _applicationManager.FindByClientIdAsync(internalServiceClientId) == null) + { + await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor + { + ClientId = internalServiceClientId, + ClientSecret = "1q2w3e*", + ClientType = OpenIddictConstants.ClientTypes.Confidential, + ConsentType = OpenIddictConstants.ConsentTypes.Explicit, + ApplicationType = OpenIddictConstants.ApplicationTypes.Native, + DisplayName = "Abp Vue Admin Client", + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Device, + OpenIddictConstants.Permissions.Endpoints.Introspection, + OpenIddictConstants.Permissions.Endpoints.Revocation, + OpenIddictConstants.Permissions.Endpoints.Logout, + + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.GrantTypes.Implicit, + OpenIddictConstants.Permissions.GrantTypes.Password, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.GrantTypes.DeviceCode, + OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + + OpenIddictConstants.Permissions.ResponseTypes.Code, + OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, + OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken, + OpenIddictConstants.Permissions.ResponseTypes.CodeToken, + OpenIddictConstants.Permissions.ResponseTypes.IdToken, + OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken, + OpenIddictConstants.Permissions.ResponseTypes.None, + OpenIddictConstants.Permissions.ResponseTypes.Token, + + OpenIddictConstants.Permissions.Scopes.Roles, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Address, + OpenIddictConstants.Permissions.Scopes.Phone, + OpenIddictConstants.Permissions.Prefixes.Scope + scope + } + }); + + var internalServicePermissions = new string[2] + { + "AbpIdentity.UserLookup","AbpIdentity.Users" + }; + await _permissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, internalServiceClientId, internalServicePermissions); + } + } + } + + #endregion + + #region IdentityServer + + private async Task SeedIdentityServerAsync() + { + await _identityResourceDataSeeder.CreateStandardResourcesAsync(); + await _customIdentityResourceDataSeeder.CreateCustomResourcesAsync(); + await CreateApiResourcesAsync(); + await CreateApiScopesAsync(); + await CreateClientsAsync(); + } + + private async Task CreateApiScopesAsync() + { + await CreateApiScopeAsync("lingyun-abp-application"); + } + + private async Task CreateApiResourcesAsync() + { + var commonApiUserClaims = new[] + { + "email", + "email_verified", + "name", + "phone_number", + "phone_number_verified", + "role" + }; + + await CreateApiResourceAsync("lingyun-abp-application", commonApiUserClaims); + } + + private async Task CreateApiResourceAsync(string name, IEnumerable claims, IEnumerable secrets = null) + { + var apiResource = await _apiResourceRepository.FindByNameAsync(name); + if (apiResource == null) + { + apiResource = await _apiResourceRepository.InsertAsync( + new ApiResource( + _guidGenerator.Create(), + name, + name + " API" + ), + autoSave: true + ); + } + + foreach (var claim in claims) + { + if (apiResource.FindClaim(claim) == null) + { + apiResource.AddUserClaim(claim); + } + } + if (secrets != null) + { + foreach (var secret in secrets) + { + if (apiResource.FindSecret(secret) == null) + { + apiResource.AddSecret(secret); + } + } + } + + return await _apiResourceRepository.UpdateAsync(apiResource); + } + + private async Task CreateApiScopeAsync(string name) + { + var apiScope = await _apiScopeRepository.FindByNameAsync(name); + if (apiScope == null) + { + apiScope = await _apiScopeRepository.InsertAsync( + new ApiScope( + _guidGenerator.Create(), + name, + name + " API" + ), + autoSave: true + ); + } + + return apiScope; + } + + private async Task CreateClientsAsync() + { + + string commonSecret = IdentityServer4.Models.HashExtensions.Sha256("1q2w3e*"); + + var commonScopes = new[] + { + "email", + "openid", + "profile", + "role", + "phone", + "address", + "offline_access" // 加上刷新, + + }; + + var configurationSection = _configuration.GetSection("IdentityServer:Clients"); + + var vueClientId = configurationSection["VueAdmin:ClientId"]; + if (!vueClientId.IsNullOrWhiteSpace()) + { + var vueClientPermissions = new string[1] + { + "AbpIdentity.UserLookup" + }; + var vueClientRootUrl = configurationSection["VueAdmin:RootUrl"].EnsureEndsWith('/'); + await CreateClientAsync( + vueClientId, + commonScopes.Union(new[] { "lingyun-abp-application" }), + new[] { "password", "client_credentials", "implicit", "phone_verify", "wx-mp" }, + commonSecret, + redirectUri: $"{vueClientRootUrl}signin-oidc", + postLogoutRedirectUri: $"{vueClientRootUrl}signout-callback-oidc", + corsOrigins: configurationSection["CorsOrigins"], + permissions: vueClientPermissions + ); + } + + // InternalService 内部服务间通讯客户端,必要的话需要在前端指定它拥有所有权限,当前项目仅预置用户查询权限 + var internalServiceClientId = configurationSection["InternalService:ClientId"]; + if (!internalServiceClientId.IsNullOrWhiteSpace()) + { + var internalServicePermissions = new string[2] + { + "AbpIdentity.UserLookup","AbpIdentity.Users" + }; + await CreateClientAsync( + internalServiceClientId, + commonScopes.Union(new[] { "lingyun-abp-application" }), + new[] { "client_credentials" }, + commonSecret, + permissions: internalServicePermissions + ); + } + } + + private async Task CreateClientAsync( + string name, + IEnumerable scopes, + IEnumerable grantTypes, + string secret, + string redirectUri = null, + string postLogoutRedirectUri = null, + IEnumerable permissions = null, + string corsOrigins = null) + { + var client = await _clientRepository.FindByClientIdAsync(name); + if (client == null) + { + client = await _clientRepository.InsertAsync( + new Client( + _guidGenerator.Create(), + name + ) + { + ClientName = name, + ProtocolType = "oidc", + Description = name, + AlwaysIncludeUserClaimsInIdToken = true, + AllowOfflineAccess = true, + AbsoluteRefreshTokenLifetime = 10800, //3 hours + AccessTokenLifetime = 7200, //2 hours + AuthorizationCodeLifetime = 300, + IdentityTokenLifetime = 300, + RequireConsent = false + }, + autoSave: true + ); + } + + foreach (var scope in scopes) + { + if (client.FindScope(scope) == null) + { + client.AddScope(scope); + } + } + + foreach (var grantType in grantTypes) + { + if (client.FindGrantType(grantType) == null) + { + client.AddGrantType(grantType); + } + } + + if (client.FindSecret(secret) == null) + { + client.AddSecret(secret); + } + + if (redirectUri != null) + { + if (client.FindRedirectUri(redirectUri) == null) + { + client.AddRedirectUri(redirectUri); + } + } + + if (postLogoutRedirectUri != null) + { + if (client.FindPostLogoutRedirectUri(postLogoutRedirectUri) == null) + { + client.AddPostLogoutRedirectUri(postLogoutRedirectUri); + } + } + + if (corsOrigins != null) + { + var corsOriginsSplit = corsOrigins.Split(";"); + foreach (var corsOrigin in corsOriginsSplit) + { + if (client.FindCorsOrigin(corsOrigin) == null) + { + client.AddCorsOrigin(corsOrigin); + } + } + } + + if (permissions != null) + { + await _permissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, name, permissions); + } + + return await _clientRepository.UpdateAsync(client); + } + + #endregion +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.csproj b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.csproj new file mode 100644 index 000000000..fe8585fb9 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.csproj @@ -0,0 +1,47 @@ + + + + + + + net8.0 + LY.MicroService.Applications.Single.EntityFrameworkCore + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.EN.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.EN.md new file mode 100644 index 000000000..67e42e5c3 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.EN.md @@ -0,0 +1,97 @@ +# LY.MicroService.Applications.Single.EntityFrameworkCore + +Monolithic Application Database Migration Module, providing comprehensive application database migration functionality. + +[简体中文](./README.md) + +## Features + +* Integrated Audit Logging data migration +* Integrated Setting Management data migration +* Integrated Permission Management data migration +* Integrated Feature Management data migration +* Integrated Notification System data migration +* Integrated Message Service data migration +* Integrated Platform Management data migration +* Integrated Localization Management data migration +* Integrated Identity Authentication data migration +* Integrated IdentityServer data migration +* Integrated OpenIddict data migration +* Integrated Text Templating data migration +* Integrated Webhooks Management data migration +* Integrated Task Management data migration +* Integrated SaaS multi-tenancy data migration +* Support for database migration event handling +* Provides database migration service + +## Module Dependencies + +```csharp +[DependsOn( + typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpNotificationsEntityFrameworkCoreModule), + typeof(AbpMessageServiceEntityFrameworkCoreModule), + typeof(PlatformEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpIdentityServerEntityFrameworkCoreModule), + typeof(AbpOpenIddictEntityFrameworkCoreModule), + typeof(AbpTextTemplatingEntityFrameworkCoreModule), + typeof(WebhooksManagementEntityFrameworkCoreModule), + typeof(TaskManagementEntityFrameworkCoreModule), + typeof(AbpWeChatModule), + typeof(AbpDataDbMigratorModule) +)] +``` + +## Configuration + +```json +{ + "ConnectionStrings": { + "SingleDbMigrator": "Your database connection string" + } +} +``` + +## Basic Usage + +1. Configure Database Connection String + * Configure SingleDbMigrator connection string in appsettings.json + +2. Add Module Dependency + ```csharp + [DependsOn(typeof(SingleMigrationsEntityFrameworkCoreModule))] + public class YourModule : AbpModule + { + // ... + } + ``` + +## Database Tables Description + +* AbpAuditLogs - Audit logging data +* Identity Related Tables - User, role, claims and other authentication related data +* IdentityServer Related Tables - Client, API resources, identity resources and other OAuth/OpenID Connect related data +* OpenIddict Related Tables - OpenID Connect authentication related data +* AbpPermissionGrants - Permission authorization data +* AbpSettings - System settings data +* AbpFeatures - Feature data +* AbpNotifications - Notification system data +* AbpMessageService - Message service data +* Platform Related Tables - Platform management related data +* AbpLocalization - Localization management data +* AbpTextTemplates - Text template data +* AbpWebhooks - Webhooks management data +* AbpTasks - Task management data +* Saas Related Tables - Tenant, edition and other multi-tenancy related data + +## More Information + +* [ABP Documentation](https://docs.abp.io) +* [OpenIddict Documentation](https://documentation.openiddict.com) +* [IdentityServer4 Documentation](https://identityserver4.readthedocs.io) diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.md b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.md new file mode 100644 index 000000000..a96b753ab --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/README.md @@ -0,0 +1,97 @@ +# LY.MicroService.Applications.Single.EntityFrameworkCore + +单体应用数据迁移模块,提供完整的应用程序数据库迁移功能。 + +[English](./README.EN.md) + +## 功能特性 + +* 集成审计日志数据迁移 +* 集成设置管理数据迁移 +* 集成权限管理数据迁移 +* 集成特性管理数据迁移 +* 集成通知系统数据迁移 +* 集成消息服务数据迁移 +* 集成平台管理数据迁移 +* 集成本地化管理数据迁移 +* 集成身份认证数据迁移 +* 集成IdentityServer数据迁移 +* 集成OpenIddict数据迁移 +* 集成文本模板数据迁移 +* 集成Webhooks管理数据迁移 +* 集成任务管理数据迁移 +* 集成SaaS多租户数据迁移 +* 支持数据库迁移事件处理 +* 提供数据库迁移服务 + +## 模块依赖 + +```csharp +[DependsOn( + typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpNotificationsEntityFrameworkCoreModule), + typeof(AbpMessageServiceEntityFrameworkCoreModule), + typeof(PlatformEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpIdentityServerEntityFrameworkCoreModule), + typeof(AbpOpenIddictEntityFrameworkCoreModule), + typeof(AbpTextTemplatingEntityFrameworkCoreModule), + typeof(WebhooksManagementEntityFrameworkCoreModule), + typeof(TaskManagementEntityFrameworkCoreModule), + typeof(AbpWeChatModule), + typeof(AbpDataDbMigratorModule) +)] +``` + +## 配置项 + +```json +{ + "ConnectionStrings": { + "SingleDbMigrator": "你的数据库连接字符串" + } +} +``` + +## 基本用法 + +1. 配置数据库连接字符串 + * 在appsettings.json中配置SingleDbMigrator连接字符串 + +2. 添加模块依赖 + ```csharp + [DependsOn(typeof(SingleMigrationsEntityFrameworkCoreModule))] + public class YourModule : AbpModule + { + // ... + } + ``` + +## 数据库表说明 + +* AbpAuditLogs - 审计日志数据 +* Identity相关表 - 用户、角色、声明等身份认证相关数据 +* IdentityServer相关表 - 客户端、API资源、身份资源等OAuth/OpenID Connect相关数据 +* OpenIddict相关表 - OpenID Connect认证相关数据 +* AbpPermissionGrants - 权限授权数据 +* AbpSettings - 系统设置数据 +* AbpFeatures - 功能特性数据 +* AbpNotifications - 通知系统数据 +* AbpMessageService - 消息服务数据 +* Platform相关表 - 平台管理相关数据 +* AbpLocalization - 本地化管理数据 +* AbpTextTemplates - 文本模板数据 +* AbpWebhooks - Webhooks管理数据 +* AbpTasks - 任务管理数据 +* Saas相关表 - 租户、版本等多租户相关数据 + +## 更多信息 + +* [ABP文档](https://docs.abp.io) +* [OpenIddict文档](https://documentation.openiddict.com) +* [IdentityServer4文档](https://identityserver4.readthedocs.io) diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationEventHandler.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationEventHandler.cs new file mode 100644 index 000000000..75ae423ed --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationEventHandler.cs @@ -0,0 +1,242 @@ +using LINGYUN.Abp.BackgroundTasks; +using LINGYUN.Abp.BackgroundTasks.Internal; +using LINGYUN.Abp.Saas.Tenants; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EntityFrameworkCore.Migrations; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; +using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Uow; +using IdentityRole = Volo.Abp.Identity.IdentityRole; +using IdentityUser = Volo.Abp.Identity.IdentityUser; + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore; +public class SingleDbMigrationEventHandler : + EfCoreDatabaseMigrationEventHandlerBase, + IDistributedEventHandler> +{ + protected AbpBackgroundTasksOptions Options { get; } + protected IJobStore JobStore { get; } + protected IJobScheduler JobScheduler { get; } + protected IGuidGenerator GuidGenerator { get; } + protected IdentityUserManager IdentityUserManager { get; } + protected IdentityRoleManager IdentityRoleManager { get; } + protected IPermissionDataSeeder PermissionDataSeeder { get; } + + public SingleDbMigrationEventHandler( + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + ITenantStore tenantStore, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory, + IGuidGenerator guidGenerator, + IdentityUserManager identityUserManager, + IdentityRoleManager identityRoleManager, + IPermissionDataSeeder permissionDataSeeder, + IJobStore jobStore, + IJobScheduler jobScheduler, + IOptions options) + : base("SingleDbMigrator", currentTenant, unitOfWorkManager, tenantStore, abpDistributedLock, distributedEventBus, loggerFactory) + { + GuidGenerator = guidGenerator; + IdentityUserManager = identityUserManager; + IdentityRoleManager = identityRoleManager; + PermissionDataSeeder = permissionDataSeeder; + JobStore = jobStore; + JobScheduler = jobScheduler; + Options = options.Value; + } + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + // 租户删除时移除轮询作业 + var pollingJob = BuildPollingJobInfo(eventData.Entity.Id, eventData.Entity.Name); + await JobScheduler.RemoveAsync(pollingJob); + await JobStore.RemoveAsync(pollingJob.Id); + + var cleaningJob = BuildCleaningJobInfo(eventData.Entity.Id, eventData.Entity.Name); + await JobScheduler.RemoveAsync(cleaningJob); + await JobStore.RemoveAsync(cleaningJob.Id); + + var checkingJob = BuildCheckingJobInfo(eventData.Entity.Id, eventData.Entity.Name); + await JobScheduler.RemoveAsync(checkingJob); + await JobStore.RemoveAsync(checkingJob.Id); + } + + protected async override Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated) + { + if (!schemaMigrated) + { + return; + } + + using (CurrentTenant.Change(eventData.Id)) + { + await QueueBackgroundJobAsync(eventData); + + await SeedTenantDefaultRoleAsync(eventData); + await SeedTenantAdminAsync(eventData); + } + } + + protected async virtual Task QueueBackgroundJobAsync(TenantCreatedEto eventData) + { + var pollingJob = BuildPollingJobInfo(eventData.Id, eventData.Name); + await JobStore.StoreAsync(pollingJob); + await JobScheduler.QueueAsync(pollingJob); + + var cleaningJob = BuildCleaningJobInfo(eventData.Id, eventData.Name); + await JobStore.StoreAsync(cleaningJob); + await JobScheduler.QueueAsync(cleaningJob); + + var checkingJob = BuildCheckingJobInfo(eventData.Id, eventData.Name); + await JobStore.StoreAsync(checkingJob); + await JobScheduler.QueueAsync(checkingJob); + } + + protected virtual JobInfo BuildPollingJobInfo(Guid tenantId, string tenantName) + { + return new JobInfo + { + Id = tenantId.ToString() + "_Polling", + Name = nameof(BackgroundPollingJob), + Group = "Polling", + Description = "Polling tasks to be executed", + Args = new Dictionary() { { nameof(JobInfo.TenantId), tenantId } }, + Status = JobStatus.Running, + BeginTime = DateTime.Now, + CreationTime = DateTime.Now, + Cron = Options.JobFetchCronExpression, + JobType = JobType.Period, + Priority = JobPriority.High, + Source = JobSource.System, + LockTimeOut = Options.JobFetchLockTimeOut, + TenantId = tenantId, + Type = typeof(BackgroundPollingJob).AssemblyQualifiedName, + }; + } + + protected virtual JobInfo BuildCleaningJobInfo(Guid tenantId, string tenantName) + { + return new JobInfo + { + Id = tenantId.ToString() + "_Cleaning", + Name = nameof(BackgroundCleaningJob), + Group = "Cleaning", + Description = "Cleaning tasks to be executed", + Args = new Dictionary() { { nameof(JobInfo.TenantId), tenantId } }, + Status = JobStatus.Running, + BeginTime = DateTime.Now, + CreationTime = DateTime.Now, + Cron = Options.JobCleanCronExpression, + JobType = JobType.Period, + Priority = JobPriority.High, + Source = JobSource.System, + TenantId = tenantId, + Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName, + }; + } + + protected virtual JobInfo BuildCheckingJobInfo(Guid tenantId, string tenantName) + { + return new JobInfo + { + Id = tenantId.ToString() + "_Checking", + Name = nameof(BackgroundCheckingJob), + Group = "Checking", + Description = "Checking tasks to be executed", + Args = new Dictionary() { { nameof(JobInfo.TenantId), tenantId } }, + Status = JobStatus.Running, + BeginTime = DateTime.Now, + CreationTime = DateTime.Now, + Cron = Options.JobCheckCronExpression, + LockTimeOut = Options.JobCheckLockTimeOut, + JobType = JobType.Period, + Priority = JobPriority.High, + Source = JobSource.System, + TenantId = tenantId, + Type = typeof(BackgroundCheckingJob).AssemblyQualifiedName, + }; + } + + protected async virtual Task SeedTenantDefaultRoleAsync(TenantCreatedEto eventData) + { + // 默认用户 + var roleId = GuidGenerator.Create(); + var defaultRole = new IdentityRole(roleId, "Users", eventData.Id) + { + IsStatic = true, + IsPublic = true, + IsDefault = true, + }; + (await IdentityRoleManager.CreateAsync(defaultRole)).CheckErrors(); + + // 所有用户都应该具有查询用户权限, 用于IM场景 + await PermissionDataSeeder.SeedAsync( + RolePermissionValueProvider.ProviderName, + defaultRole.Name, + new string[] + { + IdentityPermissions.UserLookup.Default, + IdentityPermissions.Users.Default + }, + tenantId: eventData.Id); + } + + protected async virtual Task SeedTenantAdminAsync(TenantCreatedEto eventData) + { + const string tenantAdminUserName = "admin"; + const string tenantAdminRoleName = "admin"; + Guid tenantAdminRoleId; + if (!await IdentityRoleManager.RoleExistsAsync(tenantAdminRoleName)) + { + tenantAdminRoleId = GuidGenerator.Create(); + var tenantAdminRole = new IdentityRole(tenantAdminRoleId, tenantAdminRoleName, eventData.Id) + { + IsStatic = true, + IsPublic = true + }; + (await IdentityRoleManager.CreateAsync(tenantAdminRole)).CheckErrors(); + } + else + { + var tenantAdminRole = await IdentityRoleManager.FindByNameAsync(tenantAdminRoleName); + tenantAdminRoleId = tenantAdminRole.Id; + } + + var adminUserId = GuidGenerator.Create(); + if (eventData.Properties.TryGetValue("AdminUserId", out var userIdString) && + Guid.TryParse(userIdString, out var adminUserGuid)) + { + adminUserId = adminUserGuid; + } + var adminEmailAddress = eventData.Properties.GetOrDefault("AdminEmail") ?? "admin@abp.io"; + var adminPassword = eventData.Properties.GetOrDefault("AdminPassword") ?? "1q2w3E*"; + + var tenantAdminUser = await IdentityUserManager.FindByNameAsync(adminEmailAddress); + if (tenantAdminUser == null) + { + tenantAdminUser = new IdentityUser( + adminUserId, + tenantAdminUserName, + adminEmailAddress, + eventData.Id); + + tenantAdminUser.AddRole(tenantAdminRoleId); + + // 创建租户管理用户 + (await IdentityUserManager.CreateAsync(tenantAdminUser)).CheckErrors(); + (await IdentityUserManager.AddPasswordAsync(tenantAdminUser, adminPassword)).CheckErrors(); + } + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationService.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationService.cs new file mode 100644 index 000000000..1247b3499 --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleDbMigrationService.cs @@ -0,0 +1,101 @@ +using LINGYUN.Abp.Saas.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Migrations; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore; + +public class SingleDbMigrationService : EfCoreRuntimeDatabaseMigratorBase, ITransientDependency +{ + protected IDataSeeder DataSeeder { get; } + protected ITenantRepository TenantRepository { get; } + public SingleDbMigrationService( + IUnitOfWorkManager unitOfWorkManager, + IServiceProvider serviceProvider, + ICurrentTenant currentTenant, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory, + IDataSeeder dataSeeder, + ITenantRepository tenantRepository) + : base("SingleDbMigrator", unitOfWorkManager, serviceProvider, currentTenant, abpDistributedLock, distributedEventBus, loggerFactory) + { + DataSeeder = dataSeeder; + TenantRepository = tenantRepository; + } + protected async override Task LockAndApplyDatabaseMigrationsAsync() + { + await base.LockAndApplyDatabaseMigrationsAsync(); + + var tenants = await TenantRepository.GetListAsync(); + foreach (var tenant in tenants.Where(x => x.IsActive)) + { + Logger.LogInformation($"Trying to acquire the distributed lock for database migration: {DatabaseName} with tenant: {tenant.Name}."); + + var schemaMigrated = false; + + await using (var handle = await DistributedLock.TryAcquireAsync("DatabaseMigration_" + DatabaseName + "_Tenant" + tenant.Id.ToString())) + { + if (handle is null) + { + Logger.LogInformation($"Distributed lock could not be acquired for database migration: {DatabaseName} with tenant: {tenant.Name}. Operation cancelled."); + return; + } + + Logger.LogInformation($"Distributed lock is acquired for database migration: {DatabaseName} with tenant: {tenant.Name}..."); + + using (CurrentTenant.Change(tenant.Id)) + { + // Create database tables if needed + using var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false); + var dbContext = await ServiceProvider + .GetRequiredService>() + .GetDbContextAsync(); + + var pendingMigrations = await dbContext + .Database + .GetPendingMigrationsAsync(); + + if (pendingMigrations.Any()) + { + await dbContext.Database.MigrateAsync(); + schemaMigrated = true; + } + + await uow.CompleteAsync(); + + await SeedAsync(); + + if (schemaMigrated || AlwaysSeedTenantDatabases) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = tenant.Id + } + ); + } + } + } + + Logger.LogInformation($"Distributed lock has been released for database migration: {DatabaseName} with tenant: {tenant.Name}..."); + } + } + + protected async override Task SeedAsync() + { + await DataSeeder.SeedAsync(CurrentTenant.Id); + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsDbContext.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsDbContext.cs new file mode 100644 index 000000000..1a1312e6d --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsDbContext.cs @@ -0,0 +1,58 @@ +using LINGYUN.Abp.DataProtectionManagement.EntityFrameworkCore; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.MessageService.EntityFrameworkCore; +using LINGYUN.Abp.Notifications.EntityFrameworkCore; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.TextTemplating.EntityFrameworkCore; +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using LINGYUN.Platform.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using PackageName.CompanyName.ProjectName.EntityFrameworkCore; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.OpenIddict.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore; + +[ConnectionStringName("SingleDbMigrator")] +public class SingleMigrationsDbContext : AbpDbContext +{ + public SingleMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureAuditLogging(); + modelBuilder.ConfigureIdentity(); + modelBuilder.ConfigureIdentityServer(); + modelBuilder.ConfigureOpenIddict(); + modelBuilder.ConfigureSaas(); + modelBuilder.ConfigureFeatureManagement(); + modelBuilder.ConfigureSettingManagement(); + modelBuilder.ConfigurePermissionManagement(); + modelBuilder.ConfigureTextTemplating(); + modelBuilder.ConfigureTaskManagement(); + modelBuilder.ConfigureWebhooksManagement(); + modelBuilder.ConfigurePlatform(); + modelBuilder.ConfigureLocalization(); + modelBuilder.ConfigureNotifications(); + modelBuilder.ConfigureNotificationsDefinition(); + modelBuilder.ConfigureMessageService(); + modelBuilder.ConfigureDataProtectionManagement(); + modelBuilder.ConfigureWebhooksManagement(); + + modelBuilder.ConfigureProjectName(); + } +} diff --git a/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..623f5d40b --- /dev/null +++ b/aspnet-core/templates/aio/content/migrations/PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.AuditLogging.EntityFrameworkCore; +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.Identity.EntityFrameworkCore; +using LINGYUN.Abp.IdentityServer.EntityFrameworkCore; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.MessageService.EntityFrameworkCore; +using LINGYUN.Abp.Notifications.EntityFrameworkCore; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using LINGYUN.Abp.TextTemplating.EntityFrameworkCore; +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using LINGYUN.Abp.WeChat; +using LINGYUN.Platform.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.OpenIddict.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore; + +[DependsOn( + typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpNotificationsEntityFrameworkCoreModule), + typeof(AbpMessageServiceEntityFrameworkCoreModule), + typeof(PlatformEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpIdentityServerEntityFrameworkCoreModule), + typeof(AbpOpenIddictEntityFrameworkCoreModule), + typeof(AbpTextTemplatingEntityFrameworkCoreModule), + typeof(WebhooksManagementEntityFrameworkCoreModule), + typeof(TaskManagementEntityFrameworkCoreModule), + typeof(AbpWeChatModule), + typeof(AbpDataDbMigratorModule) + )] +public class SingleMigrationsEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName.CompanyName.ProjectName.Application.Contracts.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName.CompanyName.ProjectName.Application.Contracts.csproj new file mode 100644 index 000000000..0eaa24015 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName.CompanyName.ProjectName.Application.Contracts.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0 + PackageName.CompanyName.ProjectName.Application.Contracts + PackageName.CompanyName.ProjectName.Application.Contracts + false + false + false + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureDefinitionProvider.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureDefinitionProvider.cs new file mode 100644 index 000000000..5fed2b916 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureDefinitionProvider.cs @@ -0,0 +1,18 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; + +namespace PackageName.CompanyName.ProjectName.Features; + +public class ProjectNameFeatureDefinitionProvider : FeatureDefinitionProvider +{ + public override void Define(IFeatureDefinitionContext context) + { + var group = context.AddGroup(ProjectNameFeatureNames.GroupName, L("Features:ProjectName")); + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureNames.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureNames.cs new file mode 100644 index 000000000..6389fbcf6 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Features/ProjectNameFeatureNames.cs @@ -0,0 +1,6 @@ +namespace PackageName.CompanyName.ProjectName.Features; + +public static class ProjectNameFeatureNames +{ + public const string GroupName = "ProjectName"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/IProjectNameDynamicQueryableAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/IProjectNameDynamicQueryableAppService.cs new file mode 100644 index 000000000..6f132d9a3 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/IProjectNameDynamicQueryableAppService.cs @@ -0,0 +1,10 @@ +using LINGYUN.Abp.Dynamic.Queryable; + +namespace PackageName.CompanyName.ProjectName; +/// +/// 提供动态查询接口定义 +/// +/// 实体dto类型 +public interface IProjectNameDynamicQueryableAppService : IDynamicQueryableAppService +{ +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs new file mode 100644 index 000000000..4a170551b --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissionDefinitionProvider.cs @@ -0,0 +1,22 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace PackageName.CompanyName.ProjectName.Permissions; + +public class ProjectNamePermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var group = context.AddGroup(ProjectNamePermissions.GroupName, L("Permission:ProjectName")); + + group.AddPermission( + ProjectNamePermissions.ManageSettings, + L("Permission:ManageSettings")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs new file mode 100644 index 000000000..98a957ff9 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/Permissions/ProjectNamePermissions.cs @@ -0,0 +1,8 @@ +namespace PackageName.CompanyName.ProjectName.Permissions; + +public static class ProjectNamePermissions +{ + public const string GroupName = "ProjectName"; + + public const string ManageSettings = GroupName + ".ManageSettings"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameApplicationContractsModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameApplicationContractsModule.cs new file mode 100644 index 000000000..eb0b2fb15 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameApplicationContractsModule.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.Dynamic.Queryable; +using Volo.Abp.Application; +using Volo.Abp.Authorization; +using Volo.Abp.Features; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpFeaturesModule), + typeof(AbpAuthorizationModule), + typeof(AbpDddApplicationContractsModule), + typeof(AbpDynamicQueryableApplicationContractsModule), + typeof(ProjectNameDomainSharedModule))] +public class ProjectNameApplicationContractsModule : AbpModule +{ +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameRemoteServiceConsts.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameRemoteServiceConsts.cs new file mode 100644 index 000000000..4b5bb260d --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application.Contracts/PackageName/CompanyName/ProjectName/ProjectNameRemoteServiceConsts.cs @@ -0,0 +1,7 @@ +namespace PackageName.CompanyName.ProjectName; + +public static class ProjectNameRemoteServiceConsts +{ + public const string RemoteServiceName = "ProjectName"; + public const string ModuleName = "ProjectName"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj new file mode 100644 index 000000000..cf195e665 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName.CompanyName.ProjectName.Application.csproj @@ -0,0 +1,26 @@ + + + + + + + net8.0 + PackageName.CompanyName.ProjectName.Application + PackageName.CompanyName.ProjectName.Application + false + false + false + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameAppServiceBase.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameAppServiceBase.cs new file mode 100644 index 000000000..496140dfe --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameAppServiceBase.cs @@ -0,0 +1,13 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Volo.Abp.Application.Services; + +namespace PackageName.CompanyName.ProjectName; + +public abstract class ProjectNameAppServiceBase : ApplicationService +{ + protected ProjectNameAppServiceBase() + { + LocalizationResource = typeof(ProjectNameResource); + ObjectMapperContext = typeof(ProjectNameApplicationModule); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs new file mode 100644 index 000000000..0ace9b456 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationMapperProfile.cs @@ -0,0 +1,10 @@ +using AutoMapper; + +namespace PackageName.CompanyName.ProjectName; + +public class ProjectNameApplicationMapperProfile : Profile +{ + public ProjectNameApplicationMapperProfile() + { + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs new file mode 100644 index 000000000..6e1cf009f --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameApplicationModule.cs @@ -0,0 +1,27 @@ +using LINGYUN.Abp.Dynamic.Queryable; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Application; +using Volo.Abp.Authorization; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpAuthorizationModule), + typeof(AbpDddApplicationModule), + typeof(ProjectNameDomainModule), + typeof(ProjectNameApplicationContractsModule), + typeof(AbpDynamicQueryableApplicationModule))] +public class ProjectNameApplicationModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableAppServiceBase.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableAppServiceBase.cs new file mode 100644 index 000000000..e20348b53 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableAppServiceBase.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.Dynamic.Queryable; +using PackageName.CompanyName.ProjectName.Localization; + +namespace PackageName.CompanyName.ProjectName; +/// +/// 提供动态查询接口实现 +/// +/// 实体类型 +/// 实体dto类型 +public abstract class ProjectNameDynamicQueryableAppServiceBase : + DynamicQueryableAppService, + IProjectNameDynamicQueryableAppService +{ + protected ProjectNameDynamicQueryableAppServiceBase() + { + LocalizationResource = typeof(ProjectNameResource); + ObjectMapperContext = typeof(ProjectNameApplicationModule); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/System/Linq/Expressions/ExpressionFuncExtensions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/System/Linq/Expressions/ExpressionFuncExtensions.cs new file mode 100644 index 000000000..fd7a017ea --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/System/Linq/Expressions/ExpressionFuncExtensions.cs @@ -0,0 +1,32 @@ +using Volo.Abp.Specifications; + +namespace System.Linq.Expressions; + +internal static class ExpressionFuncExtensions +{ + public static Expression> AndIf( + this Expression> first, + bool condition, + Expression> second) + { + if (condition) + { + return ExpressionFuncExtender.And(first, second); + } + + return first; + } + + public static Expression> OrIf( + this Expression> first, + bool condition, + Expression> second) + { + if (condition) + { + return ExpressionFuncExtender.Or(first, second); + } + + return first; + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName.CompanyName.ProjectName.Dapr.Client.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName.CompanyName.ProjectName.Dapr.Client.csproj new file mode 100644 index 000000000..aa6f4772b --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName.CompanyName.ProjectName.Dapr.Client.csproj @@ -0,0 +1,19 @@ + + + + + + + net8.0 + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName/CompanyName/ProjectName/ProjectNameDaprClientModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName/CompanyName/ProjectName/ProjectNameDaprClientModule.cs new file mode 100644 index 000000000..ca16b0bf1 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Dapr.Client/PackageName/CompanyName/ProjectName/ProjectNameDaprClientModule.cs @@ -0,0 +1,18 @@ +using LINGYUN.Abp.Dapr.Client; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpDaprClientModule), + typeof(ProjectNameApplicationContractsModule))] +public class ProjectNameDaprClientModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddStaticDaprClientProxies( + typeof(ProjectNameApplicationContractsModule).Assembly, + ProjectNameRemoteServiceConsts.RemoteServiceName); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName.CompanyName.ProjectName.Domain.Shared.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName.CompanyName.ProjectName.Domain.Shared.csproj new file mode 100644 index 000000000..cd98bbcf9 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName.CompanyName.ProjectName.Domain.Shared.csproj @@ -0,0 +1,30 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0 + PackageName.CompanyName.ProjectName.Domain.Shared + PackageName.CompanyName.ProjectName.Domain.Shared + false + false + false + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/ProjectNameResource.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/ProjectNameResource.cs new file mode 100644 index 000000000..b0c280d5a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/ProjectNameResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace PackageName.CompanyName.ProjectName.Localization; + +[LocalizationResourceName("ProjectName")] +public class ProjectNameResource +{ +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/en.json b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/en.json new file mode 100644 index 000000000..d14e6f1dd --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/en.json @@ -0,0 +1,8 @@ +{ + "culture": "en", + "texts": { + "Features:ProjectName": "ProjectName", + "Permission:ProjectName": "ProjectName", + "Permission:ManageSettings": "Manage Settings" + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/zh-Hans.json b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..c85b3754a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/Localization/Resources/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "culture": "zh-Hans", + "texts": { + "Features:ProjectName": "ProjectName", + "Permission:ProjectName": "ProjectName", + "Permission:ManageSettings": "管理设置" + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfiguration.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfiguration.cs new file mode 100644 index 000000000..6634ceb15 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfiguration.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.ObjectExtending.Modularity; + +namespace PackageName.CompanyName.ProjectName.ObjectExtending; + +public class ProjectNameModuleExtensionConfiguration : ModuleExtensionConfiguration +{ + public ProjectNameModuleExtensionConfiguration ConfigureProjectName( + Action configureAction) + { + return this.ConfigureEntity( + ProjectNameModuleExtensionConsts.EntityNames.Entity, + configureAction + ); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfigurationDictionaryExtensions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfigurationDictionaryExtensions.cs new file mode 100644 index 000000000..fabfd40c9 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConfigurationDictionaryExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.ObjectExtending.Modularity; + +namespace PackageName.CompanyName.ProjectName.ObjectExtending; + +public static class ProjectNameModuleExtensionConfigurationDictionaryExtensions +{ + public static ModuleExtensionConfigurationDictionary ConfigureProjectName( + this ModuleExtensionConfigurationDictionary modules, + Action configureAction) + { + return modules.ConfigureModule( + ProjectNameModuleExtensionConsts.ModuleName, + configureAction + ); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConsts.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConsts.cs new file mode 100644 index 000000000..1973ef9b6 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ObjectExtending/ProjectNameModuleExtensionConsts.cs @@ -0,0 +1,11 @@ +namespace PackageName.CompanyName.ProjectName.ObjectExtending; + +public static class ProjectNameModuleExtensionConsts +{ + public const string ModuleName = "ProjectName"; + + public static class EntityNames + { + public const string Entity = "Entity"; + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameDomainSharedModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameDomainSharedModule.cs new file mode 100644 index 000000000..ec682b498 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameDomainSharedModule.cs @@ -0,0 +1,32 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpLocalizationModule))] +public class ProjectNameDomainSharedModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/PackageName/CompanyName/ProjectName/Localization/Resources"); + }); + + Configure(options => + { + options.MapCodeNamespace(ProjectNameErrorCodes.Namespace, typeof(ProjectNameResource)); + }); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameErrorCodes.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameErrorCodes.cs new file mode 100644 index 000000000..b2383ee40 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain.Shared/PackageName/CompanyName/ProjectName/ProjectNameErrorCodes.cs @@ -0,0 +1,6 @@ +namespace PackageName.CompanyName.ProjectName; + +public static class ProjectNameErrorCodes +{ + public const string Namespace = "ProjectName"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj new file mode 100644 index 000000000..2cccd0944 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName.CompanyName.ProjectName.Domain.csproj @@ -0,0 +1,27 @@ + + + + + + + net8.0 + PackageName.CompanyName.ProjectName.Domain + PackageName.CompanyName.ProjectName.Domain + false + false + false + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/IProjectNameBasicRepository.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/IProjectNameBasicRepository.cs new file mode 100644 index 000000000..290e09398 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/IProjectNameBasicRepository.cs @@ -0,0 +1,54 @@ +using LINGYUN.Abp.DataProtection; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Specifications; + +namespace PackageName.CompanyName.ProjectName; +/// +/// 基本仓储接口 +/// +/// 实体类型 +/// 实体主键类型 +public interface IProjectNameBasicRepository : IDataProtectionRepository + where TEntity : class, IEntity +{ + /// + /// 获取过滤后的实体数量 + /// + /// + /// + /// + Task GetCountAsync( + ISpecification specification, + CancellationToken cancellationToken = default); + /// + /// 获取过滤后的实体列表(分页) + /// + /// + /// + /// + /// + /// + /// + Task> GetListAsync( + ISpecification specification, + string sorting = nameof(IEntity.Id), + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default); + /// + /// 获取过滤后的实体列表 + /// + /// + /// + /// + /// + /// + Task> GetListAsync( + ISpecification specification, + string sorting = nameof(IEntity.Id), + int maxResultCount = 10, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDbProperties.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDbProperties.cs new file mode 100644 index 000000000..a18947c6c --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDbProperties.cs @@ -0,0 +1,11 @@ +namespace PackageName.CompanyName.ProjectName; + +public static class ProjectNameDbProperties +{ + public static string DbTablePrefix { get; set; } = "ProjectName_"; + + public static string DbSchema { get; set; } = null; + + + public const string ConnectionStringName = "ProjectName"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainMapperProfile.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainMapperProfile.cs new file mode 100644 index 000000000..79c7af553 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainMapperProfile.cs @@ -0,0 +1,11 @@ +using AutoMapper; + +namespace PackageName.CompanyName.ProjectName; + +public class ProjectNameDomainMapperProfile : Profile +{ + public ProjectNameDomainMapperProfile() + { + + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs new file mode 100644 index 000000000..401aa849e --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/ProjectNameDomainModule.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using PackageName.CompanyName.ProjectName.ObjectExtending; +using Volo.Abp.AutoMapper; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Threading; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpAutoMapperModule), + typeof(AbpDataProtectionModule), + typeof(ProjectNameDomainSharedModule))] +public class ProjectNameDomainModule : AbpModule +{ + private static readonly OneTimeRunner OneTimeRunner = new(); + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + + Configure(options => + { + }); + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + // 扩展实体配置 + //ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + // ProjectNameModuleExtensionConsts.ModuleName, + // ProjectNameModuleExtensionConsts.EntityNames.Entity, + // typeof(Entity) + //); + }); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettingDefinitionProvider.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettingDefinitionProvider.cs new file mode 100644 index 000000000..cdfdef118 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettingDefinitionProvider.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Settings; + +namespace PackageName.CompanyName.ProjectName.Settings; + +public class ProjectNameSettingDefinitionProvider : SettingDefinitionProvider +{ + public override void Define(ISettingDefinitionContext context) + { + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettings.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettings.cs new file mode 100644 index 000000000..deb0b7b71 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Settings/ProjectNameSettings.cs @@ -0,0 +1,6 @@ +namespace PackageName.CompanyName.ProjectName.Settings; + +public static class ProjectNameSettings +{ + public const string GroupName = "ProjectName"; +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/System/Linq/Expressions/ExpressionFuncExtensions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/System/Linq/Expressions/ExpressionFuncExtensions.cs new file mode 100644 index 000000000..fd7a017ea --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/System/Linq/Expressions/ExpressionFuncExtensions.cs @@ -0,0 +1,32 @@ +using Volo.Abp.Specifications; + +namespace System.Linq.Expressions; + +internal static class ExpressionFuncExtensions +{ + public static Expression> AndIf( + this Expression> first, + bool condition, + Expression> second) + { + if (condition) + { + return ExpressionFuncExtender.And(first, second); + } + + return first; + } + + public static Expression> OrIf( + this Expression> first, + bool condition, + Expression> second) + { + if (condition) + { + return ExpressionFuncExtender.Or(first, second); + } + + return first; + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj new file mode 100644 index 000000000..05ba83db3 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName.CompanyName.ProjectName.EntityFrameworkCore.csproj @@ -0,0 +1,37 @@ + + + + + + + net8.0 + PackageName.CompanyName.ProjectName.EntityFrameworkCore + PackageName.CompanyName.ProjectName.EntityFrameworkCore + false + false + false + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/EfCoreProjectNameRepository.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/EfCoreProjectNameRepository.cs new file mode 100644 index 000000000..2d68cf660 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/EfCoreProjectNameRepository.cs @@ -0,0 +1,77 @@ +using LINGYUN.Abp.DataProtection; +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +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.Entities; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Specifications; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; +/// +/// 实现基本接口 +/// +/// 实体类型 +/// 实体主键类型 +public abstract class EfCoreProjectNameRepository : + EfCoreDataProtectionRepository, + IProjectNameBasicRepository + where TEntity : class, IEntity +{ + protected EfCoreProjectNameRepository( + IDbContextProvider dbContextProvider, + IDataAuthorizationService dataAuthorizationService, + IEntityTypeFilterBuilder entityTypeFilterBuilder) + : base(dbContextProvider, dataAuthorizationService, entityTypeFilterBuilder) + { + } + + public async virtual Task GetCountAsync( + ISpecification specification, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(specification.ToExpression()) + .CountAsync(GetCancellationToken(cancellationToken)); + } + + public async virtual Task> GetListAsync( + ISpecification specification, + string sorting = nameof(IEntity.Id), + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(specification.ToExpression()) + .OrderBy(GetSortingOrDefault(sorting)) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async virtual Task> GetListAsync( + ISpecification specification, + string sorting = nameof(IEntity.Id), + int maxResultCount = 10, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .Where(specification.ToExpression()) + .OrderBy(GetSortingOrDefault(sorting)) + .Take(maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + protected virtual string GetSortingOrDefault(string sorting = nameof(IEntity.Id)) + { + if (sorting.IsNullOrWhiteSpace()) + { + return nameof(IEntity.Id); + } + return sorting; + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs new file mode 100644 index 000000000..c39c2923e --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/IProjectNameDbContext.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +[ConnectionStringName(ProjectNameDbProperties.ConnectionStringName)] +public interface IProjectNameDbContext : IEfCoreDbContext +{ +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs new file mode 100644 index 000000000..5cb2c015b --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContext.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +[ConnectionStringName(ProjectNameDbProperties.ConnectionStringName)] +public class ProjectNameDbContext : AbpDataProtectionDbContext, IProjectNameDbContext +{ + public ProjectNameDbContext( + DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureProjectName(); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextFactory.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextFactory.cs new file mode 100644 index 000000000..d9ffcb9ca --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextFactory.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; +public class ProjectNameDbContextFactory : IDesignTimeDbContextFactory +{ + public ProjectNameDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var connectionString = configuration.GetConnectionString("ProjectName"); + + DbContextOptionsBuilder builder = null; + +#if MySQL + builder = new DbContextOptionsBuilder() + .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); +#elif SqlServer + builder = new DbContextOptionsBuilder() + .UseSqlServer(connectionString); +#elif Sqlite + builder = new DbContextOptionsBuilder() + .UseSqlite(connectionString); +#elif Oracle + builder = new DbContextOptionsBuilder() + .UseOracle(connectionString); +#elif OracleDevart + builder = (DbContextOptionsBuilder) new DbContextOptionsBuilder() + .UseOracle(connectionString); +#elif PostgreSql + builder = new DbContextOptionsBuilder() + .UseNpgsql(connectionString); +#endif + + return new ProjectNameDbContext(builder!.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../PackageName.CompanyName.ProjectName.DbMigrator/")) + .AddJsonFile("appsettings.json", optional: false) + .AddJsonFile("appsettings.Development.json", optional: true); + + return builder.Build(); + } +} + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextModelCreatingExtensions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextModelCreatingExtensions.cs new file mode 100644 index 000000000..726b4ec9a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbContextModelCreatingExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using System; +using Volo.Abp; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public static class ProjectNameDbContextModelCreatingExtensions +{ + public static void ConfigureProjectName( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new ProjectNameModelBuilderConfigurationOptions( + ProjectNameDbProperties.DbTablePrefix, + ProjectNameDbProperties.DbSchema + ); + optionsAction?.Invoke(options); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationEventHandler.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationEventHandler.cs new file mode 100644 index 000000000..74a4a39bc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationEventHandler.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EntityFrameworkCore.Migrations; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public class ProjectNameDbMigrationEventHandler : EfCoreDatabaseMigrationEventHandlerBase +{ + protected IDataSeeder DataSeeder { get; } + + public ProjectNameDbMigrationEventHandler( + IDataSeeder dataSeeder, + ITenantStore tenantStore, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + : base( + ConnectionStringNameAttribute.GetConnStringName(), + currentTenant, unitOfWorkManager, tenantStore, abpDistributedLock, distributedEventBus, loggerFactory) + { + DataSeeder = dataSeeder; + } + + protected async override Task SeedAsync(Guid? tenantId) + { + await DataSeeder.SeedAsync(tenantId); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationService.cs new file mode 100644 index 000000000..70d5d69e8 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameDbMigrationService.cs @@ -0,0 +1,58 @@ +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.Saas.Tenants; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public class ProjectNameDbMigrationService : EfCoreRuntimeDbMigratorBase, ITransientDependency +{ + protected IDataSeeder DataSeeder { get; } + protected IDbSchemaMigrator DbSchemaMigrator { get; } + protected ITenantRepository TenantRepository { get; } + + public ProjectNameDbMigrationService( + IDataSeeder dataSeeder, + IDbSchemaMigrator dbSchemaMigrator, + ITenantRepository tenantRepository, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IServiceProvider serviceProvider, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + : base( + ConnectionStringNameAttribute.GetConnStringName(), + unitOfWorkManager, serviceProvider, currentTenant, abpDistributedLock, distributedEventBus, loggerFactory) + { + DataSeeder = dataSeeder; + DbSchemaMigrator = dbSchemaMigrator; + TenantRepository = tenantRepository; + } + + protected async override Task LockAndApplyDatabaseMigrationsAsync() + { + await base.LockAndApplyDatabaseMigrationsAsync(); + + var tenants = await TenantRepository.GetListAsync(); + foreach (var tenant in tenants.Where(x => x.IsActive)) + { + await LockAndApplyDatabaseWithTenantMigrationsAsync(tenant.Id); + } + } + + protected async override Task SeedAsync() + { + Logger.LogInformation($"Executing {(!CurrentTenant.IsAvailable ? "host" : CurrentTenant.Name ?? CurrentTenant.GetId().ToString())} database seed..."); + + await DataSeeder.SeedAsync(CurrentTenant.Id); + } +} \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEfCoreQueryableExtensions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEfCoreQueryableExtensions.cs new file mode 100644 index 000000000..90b369745 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEfCoreQueryableExtensions.cs @@ -0,0 +1,6 @@ +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public static class ProjectNameEfCoreQueryableExtensions +{ + // 在此聚合仓储服务的扩展方法 +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..b18f43183 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreModule.cs @@ -0,0 +1,76 @@ +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; +#if MySQL +using Volo.Abp.EntityFrameworkCore.MySQL; +#elif SqlServer +using Volo.Abp.EntityFrameworkCore.SqlServer; +using Microsoft.EntityFrameworkCore.Infrastructure; +#elif Sqlite +using Volo.Abp.EntityFrameworkCore.Sqlite; +#elif Oracle +using Volo.Abp.EntityFrameworkCore.Oracle; +#elif OracleDevart +using Volo.Abp.EntityFrameworkCore.Oracle.Devart; +#elif PostgreSql +using Volo.Abp.EntityFrameworkCore.PostgreSql; +#endif + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +[DependsOn( + typeof(ProjectNameDomainModule), + typeof(AbpDataDbMigratorModule), + typeof(AbpDataProtectionEntityFrameworkCoreModule), +#if MySQL + typeof(AbpEntityFrameworkCoreMySQLModule), +#elif SqlServer + typeof(AbpEntityFrameworkCoreSqlServerModule), +#elif Sqlite + typeof(AbpEntityFrameworkCoreSqliteModule), +#elif Oracle + typeof(AbpEntityFrameworkCoreOracleModule), +#elif OracleDevart + typeof(AbpEntityFrameworkCoreOracleDevartModule), +#elif PostgreSql + typeof(AbpEntityFrameworkCorePostgreSqlModule), +#endif + typeof(AbpSaasEntityFrameworkCoreModule))] +public class ProjectNameEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // 配置Ef + Configure(options => + { +#if MySQL + options.UseMySQL(); + options.UseMySQL(); +#elif SqlServer + options.UseSqlServer(); + options.UseSqlServer(builder => + { + // see https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-database-transact-sql-compatibility-level?view=sql-server-ver16 + // builder.UseCompatibilityLevel(150); + }); +#elif Sqlite + options.UseSqlite(); + options.UseSqlite(); +#elif Oracle || OracleDevart + options.UseOracle(); + options.UseOracle(); +#elif PostgreSql + options.UseNpgsql(); + options.UseNpgsql(); +#endif + }); + + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(); + }); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameModelBuilderConfigurationOptions.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameModelBuilderConfigurationOptions.cs new file mode 100644 index 000000000..5849eb902 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.EntityFrameworkCore/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameModelBuilderConfigurationOptions.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public class ProjectNameModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions +{ + public ProjectNameModelBuilderConfigurationOptions( + [NotNull] string tablePrefix = "", + [CanBeNull] string schema = null) + : base( + tablePrefix, + schema) + { + + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName.CompanyName.ProjectName.HttpApi.Client.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName.CompanyName.ProjectName.HttpApi.Client.csproj new file mode 100644 index 000000000..30cf00fd9 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName.CompanyName.ProjectName.HttpApi.Client.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0 + PackageName.CompanyName.ProjectName.HttpApi.Client + PackageName.CompanyName.ProjectName.HttpApi.Client + false + false + false + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName/CompanyName/ProjectName/ProjectNameHttpApiClientModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName/CompanyName/ProjectName/ProjectNameHttpApiClientModule.cs new file mode 100644 index 000000000..831ba468a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi.Client/PackageName/CompanyName/ProjectName/ProjectNameHttpApiClientModule.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpHttpClientModule), + typeof(ProjectNameApplicationContractsModule))] +public class ProjectNameHttpApiClientModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddStaticHttpClientProxies( + typeof(ProjectNameApplicationContractsModule).Assembly, + ProjectNameRemoteServiceConsts.RemoteServiceName); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName.CompanyName.ProjectName.HttpApi.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName.CompanyName.ProjectName.HttpApi.csproj new file mode 100644 index 000000000..0f2375f61 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName.CompanyName.ProjectName.HttpApi.csproj @@ -0,0 +1,25 @@ + + + + + + + net8.0 + PackageName.CompanyName.ProjectName.HttpApi + PackageName.CompanyName.ProjectName.HttpApi + false + false + false + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameControllerBase.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameControllerBase.cs new file mode 100644 index 000000000..7345f99ea --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameControllerBase.cs @@ -0,0 +1,12 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Volo.Abp.AspNetCore.Mvc; + +namespace PackageName.CompanyName.ProjectName; + +public abstract class ProjectNameControllerBase : AbpControllerBase +{ + protected ProjectNameControllerBase() + { + LocalizationResource = typeof(ProjectNameResource); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableControllerBase.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableControllerBase.cs new file mode 100644 index 000000000..41cfe834a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameDynamicQueryableControllerBase.cs @@ -0,0 +1,17 @@ +using LINGYUN.Abp.Dynamic.Queryable; +using PackageName.CompanyName.ProjectName.Localization; + +namespace PackageName.CompanyName.ProjectName; +/// +/// 提供动态查询控制器实现 +/// +/// 实体dto类型 +public abstract class ProjectNameDynamicQueryableControllerBase : DynamicQueryableControllerBase +{ + protected ProjectNameDynamicQueryableControllerBase( + IDynamicQueryableAppService service) + : base(service) + { + LocalizationResource = typeof(ProjectNameResource); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameHttpApiModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameHttpApiModule.cs new file mode 100644 index 000000000..3a68e60da --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.HttpApi/PackageName/CompanyName/ProjectName/ProjectNameHttpApiModule.cs @@ -0,0 +1,42 @@ +using PackageName.CompanyName.ProjectName.Localization; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Validation.Localization; +using LINGYUN.Abp.Dynamic.Queryable; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpAspNetCoreMvcModule), + typeof(ProjectNameApplicationContractsModule), + typeof(AbpDynamicQueryableHttpApiModule))] +public class ProjectNameHttpApiModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(ProjectNameHttpApiModule).Assembly); + }); + + PreConfigure(options => + { + options.AddAssemblyResource( + typeof(ProjectNameResource), + typeof(ProjectNameApplicationContractsModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpValidationResource)); + }); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xml b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xsd b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName.CompanyName.ProjectName.SettingManagement.csproj b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName.CompanyName.ProjectName.SettingManagement.csproj new file mode 100644 index 000000000..51af57d0b --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName.CompanyName.ProjectName.SettingManagement.csproj @@ -0,0 +1,27 @@ + + + + + + + net8.0 + PackageName.CompanyName.ProjectName.SettingManagement + PackageName.CompanyName.ProjectName.SettingManagement + false + false + false + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/IProjectNameSettingAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/IProjectNameSettingAppService.cs new file mode 100644 index 000000000..dff74dddb --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/IProjectNameSettingAppService.cs @@ -0,0 +1,7 @@ +using LINGYUN.Abp.SettingManagement; + +namespace PackageName.CompanyName.ProjectName.SettingManagement; + +public interface IProjectNameSettingAppService : ISettingAppService, IUserSettingAppService +{ +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingAppService.cs new file mode 100644 index 000000000..4fab9bdd6 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingAppService.cs @@ -0,0 +1,106 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Authorization; +using PackageName.CompanyName.ProjectName.Permissions; +using PackageName.CompanyName.ProjectName.Localization; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Features; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SettingManagement; +using Volo.Abp.Settings; +using Volo.Abp.Users; + +namespace PackageName.CompanyName.ProjectName.SettingManagement; + +public class ProjectNameSettingAppService : ApplicationService, IProjectNameSettingAppService +{ + protected ISettingManager SettingManager { get; } + protected ISettingDefinitionManager SettingDefinitionManager { get; } + + public ProjectNameSettingAppService( + ISettingManager settingManager, + ISettingDefinitionManager settingDefinitionManager) + { + SettingManager = settingManager; + SettingDefinitionManager = settingDefinitionManager; + LocalizationResource = typeof(ProjectNameResource); + } + + public virtual async Task GetAllForCurrentTenantAsync() + { + return await GetAllForProviderAsync(TenantSettingValueProvider.ProviderName, CurrentTenant.GetId().ToString()); + } + + [Authorize] + public virtual async Task GetAllForCurrentUserAsync() + { + return await GetAllForProviderAsync(UserSettingValueProvider.ProviderName, CurrentUser.GetId().ToString()); + } + + public virtual async Task GetAllForGlobalAsync() + { + return await GetAllForProviderAsync(GlobalSettingValueProvider.ProviderName, null); + } + + [Authorize(ProjectNamePermissions.ManageSettings)] + public virtual async Task SetCurrentTenantAsync(UpdateSettingsDto input) + { + // 增加特性检查 + await CheckFeatureAsync(); + + if (CurrentTenant.IsAvailable) + { + foreach (var setting in input.Settings) + { + await SettingManager.SetForTenantAsync(CurrentTenant.GetId(), setting.Name, setting.Value); + } + + await CurrentUnitOfWork.SaveChangesAsync(); + } + } + + [Authorize] + public virtual async Task SetCurrentUserAsync(UpdateSettingsDto input) + { + // 增加特性检查 + await CheckFeatureAsync(); + + foreach (var setting in input.Settings) + { + await SettingManager.SetForCurrentUserAsync(setting.Name, setting.Value); + } + + await CurrentUnitOfWork.SaveChangesAsync(); + } + + [Authorize(ProjectNamePermissions.ManageSettings)] + public virtual async Task SetGlobalAsync(UpdateSettingsDto input) + { + // 增加特性检查 + await CheckFeatureAsync(); + + foreach (var setting in input.Settings) + { + await SettingManager.SetGlobalAsync(setting.Name, setting.Value); + } + + await CurrentUnitOfWork.SaveChangesAsync(); + } + + + protected virtual async Task CheckFeatureAsync() + { + await FeatureChecker.CheckEnabledAsync(SettingManagementFeatures.Enable); + } + + protected virtual async Task GetAllForProviderAsync(string providerName, string providerKey) + { + var settingGroups = new SettingGroupResult(); + + //TODO: 当前项目所有配置项在此定义返回 + + await Task.CompletedTask; + + return settingGroups; + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingController.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingController.cs new file mode 100644 index 000000000..2c045ba59 --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingController.cs @@ -0,0 +1,69 @@ +using Asp.Versioning; +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PackageName.CompanyName.ProjectName.Permissions; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; + +namespace PackageName.CompanyName.ProjectName.SettingManagement; + +[RemoteService(Name = ProjectNameRemoteServiceConsts.RemoteServiceName)] +[ApiVersion("2.0")] +[Area(ProjectNameRemoteServiceConsts.ModuleName)] +[Route("api/ProjectName/settings")] +public class ProjectNameSettingController : AbpController, IProjectNameSettingAppService +{ + private readonly IProjectNameSettingAppService _settingAppService; + public ProjectNameSettingController(IProjectNameSettingAppService settingAppService) + { + _settingAppService = settingAppService; + } + + [Authorize(ProjectNamePermissions.ManageSettings)] + [HttpPut] + [Route("by-current-tenant")] + public virtual async Task SetCurrentTenantAsync(UpdateSettingsDto input) + { + await _settingAppService.SetCurrentTenantAsync(input); + } + + [HttpGet] + [Route("by-current-tenant")] + public virtual async Task GetAllForCurrentTenantAsync() + { + return await _settingAppService.GetAllForCurrentTenantAsync(); + } + + [Authorize] + [HttpPut] + [Route("by-current-user")] + public virtual async Task SetCurrentUserAsync(UpdateSettingsDto input) + { + await _settingAppService.SetCurrentTenantAsync(input); + } + + [Authorize] + [HttpGet] + [Route("by-current-user")] + public virtual async Task GetAllForCurrentUserAsync() + { + return await _settingAppService.GetAllForCurrentTenantAsync(); + } + + [Authorize(ProjectNamePermissions.ManageSettings)] + [HttpPut] + [Route("by-global")] + public virtual async Task SetGlobalAsync(UpdateSettingsDto input) + { + await _settingAppService.SetGlobalAsync(input); + } + + [HttpGet] + [Route("by-global")] + public virtual async Task GetAllForGlobalAsync() + { + return await _settingAppService.GetAllForGlobalAsync(); + } +} diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingManagementModule.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingManagementModule.cs new file mode 100644 index 000000000..914707f4a --- /dev/null +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.SettingManagement/PackageName/CompanyName/ProjectName/SettingManagement/ProjectNameSettingManagementModule.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Modularity; +using Volo.Abp.SettingManagement; + +namespace PackageName.CompanyName.ProjectName.SettingManagement; + +[DependsOn( + typeof(AbpSettingManagementApplicationContractsModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpSettingManagementDomainModule))] +public class ProjectNameSettingManagementModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(ProjectNameSettingManagementModule).Assembly); + }); + } +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName.CompanyName.ProjectName.Application.Tests.csproj b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName.CompanyName.ProjectName.Application.Tests.csproj new file mode 100644 index 000000000..7aefab82c --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName.CompanyName.ProjectName.Application.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + + false + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestBase.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestBase.cs new file mode 100644 index 000000000..f5ae45141 --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestBase.cs @@ -0,0 +1,5 @@ +namespace PackageName.CompanyName.ProjectName; + +public abstract class ProjectNameApplicationTestBase : ProjectNameTestBase +{ +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs new file mode 100644 index 000000000..7e9fa658a --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(ProjectNameDomainTestModule), + typeof(ProjectNameApplicationModule) + )] +public class ProjectNameApplicationTestModule : AbpModule +{ +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName.CompanyName.ProjectName.Domain.Tests.csproj b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName.CompanyName.ProjectName.Domain.Tests.csproj new file mode 100644 index 000000000..5ca01ede0 --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName.CompanyName.ProjectName.Domain.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + + false + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestBase.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestBase.cs new file mode 100644 index 000000000..471e0adf8 --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestBase.cs @@ -0,0 +1,5 @@ +namespace PackageName.CompanyName.ProjectName; + +public abstract class ProjectNameDomainTestBase : ProjectNameTestBase +{ +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestModule.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestModule.cs new file mode 100644 index 000000000..f4a8a9d16 --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Domain.Tests/PackageName/CompanyName/ProjectName/ProjectNameDomainTestModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(ProjectNameTestBaseModule), + typeof(ProjectNameDomainModule) + )] +public class ProjectNameDomainTestModule : AbpModule +{ +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests.csproj b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests.csproj new file mode 100644 index 000000000..cb9c91e1f --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + + false + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestBase.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestBase.cs new file mode 100644 index 000000000..738149b6f --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestBase.cs @@ -0,0 +1,5 @@ +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +public abstract class ProjectNameEntityFrameworkCoreTestBase : ProjectNameTestBase +{ +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestModule.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestModule.cs new file mode 100644 index 000000000..da8ce7c6c --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.EntityFrameworkCore.Tests/PackageName/CompanyName/ProjectName/EntityFrameworkCore/ProjectNameEntityFrameworkCoreTestModule.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName.EntityFrameworkCore; + +[DependsOn( + typeof(ProjectNameTestBaseModule), + typeof(ProjectNameEntityFrameworkCoreModule) + )] +public class ProjectNameEntityFrameworkCoreTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddEntityFrameworkInMemoryDatabase(); + + var databaseName = Guid.NewGuid().ToString(); + + Configure(options => + { + options.Configure(abpDbContextConfigurationContext => + { + abpDbContextConfigurationContext.DbContextOptions.EnableDetailedErrors(); + abpDbContextConfigurationContext.DbContextOptions.EnableSensitiveDataLogging(); + + abpDbContextConfigurationContext.DbContextOptions.UseInMemoryDatabase(databaseName); + }); + }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions + }); + } +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName.CompanyName.ProjectName.TestBase.csproj b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName.CompanyName.ProjectName.TestBase.csproj new file mode 100644 index 000000000..ae4b6d5bf --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName.CompanyName.ProjectName.TestBase.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + + false + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBase.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBase.cs new file mode 100644 index 000000000..08aeffccb --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBase.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.Testing; +using Volo.Abp.Uow; + +namespace PackageName.CompanyName.ProjectName; + +public abstract class ProjectNameTestBase : AbpIntegratedTest + where TStartupModule : IAbpModule +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected virtual Task WithUnitOfWorkAsync(Func func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + await action(); + + await uow.CompleteAsync(); + } + } + } + + protected virtual Task WithUnitOfWorkAsync(Func> func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + var result = await func(); + await uow.CompleteAsync(); + return result; + } + } + } +} diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBaseModule.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBaseModule.cs new file mode 100644 index 000000000..4564d29b6 --- /dev/null +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.TestBase/PackageName/CompanyName/ProjectName/ProjectNameTestBaseModule.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.Authorization; +using Volo.Abp.Autofac; +using Volo.Abp.Features; +using Volo.Abp.MemoryDb; +using Volo.Abp.Modularity; + +namespace PackageName.CompanyName.ProjectName; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpTestBaseModule), + typeof(AbpAuthorizationModule), + typeof(AbpFeaturesModule), + typeof(AbpMemoryDbModule) + )] +public class ProjectNameTestBaseModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAlwaysAllowAuthorization(); + } +}