From aae7c90320a23d98c4df5dc70f793ce20e9815b2 Mon Sep 17 00:00:00 2001 From: feijie Date: Tue, 17 Dec 2024 22:27:54 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(AIO):=20=E6=96=B0=E5=A2=9E=20L?= =?UTF-8?q?Y.AIO.Applications.Single=E9=A1=B9=E7=9B=AE=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E6=BA=90=E7=A0=81=E5=BC=95=E7=94=A8=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E5=BF=AB=E8=BD=BB=E9=87=8F=E5=8C=96=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=95=B4=E5=A5=97=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LINGYUN.MicroService.SingleProject.sln | 7 + aspnet-core/services/Directory.Packages.props | 263 +++++ .../.config/dotnet-tools.json | 12 + .../LY.AIO.Applications.Single/.gitignore | 2 + .../AbpCookieAuthenticationHandler.cs | 89 ++ .../BackgroundJobs/NotificationPublishJob.cs | 38 + .../NotificationPublishJobArgs.cs | 22 + .../Controllers/HomeController.cs | 11 + .../Controllers/SettingMergeController.cs | 70 ++ .../Controllers/UserSettingMergeController.cs | 45 + .../LY.AIO.Applications.Single/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 + .../LY.AIO.Applications.Single.csproj | 272 +++++ ...rviceApplicationsSingleModule.Configure.cs | 935 ++++++++++++++++++ .../MicroServiceApplicationsSingleModule.cs | 394 ++++++++ ...eSiteCookiesServiceCollectionExtensions.cs | 67 ++ .../MultiTenancy/ITenantConfigurationCache.cs | 10 + .../MultiTenancy/TenantConfigurationCache.cs | 59 ++ .../TenantConfigurationCacheItem.cs | 19 + .../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 + .../LY.AIO.Applications.Single/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.PostgreSql.json | 246 +++++ .../appsettings.json | 89 ++ .../LY.AIO.Applications.Single/gulpfile.js | 10 + common.props | 2 +- 57 files changed, 4840 insertions(+), 1 deletion(-) create mode 100644 aspnet-core/services/Directory.Packages.props create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/.config/dotnet-tools.json create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/.gitignore create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Authentication/AbpCookieAuthenticationHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJob.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Controllers/HomeController.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Controllers/SettingMergeController.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Controllers/UserSettingMergeController.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Dockerfile create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/ChatMessageEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/UserCreateEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/WebhooksEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateJoinIMEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateSendWelcomeEventHandler.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/IdentityResources/CustomIdentityResources.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/LY.AIO.Applications.Single.csproj create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCache.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Index.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/Index.cshtml.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Pages/_ViewImports.cshtml create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Program.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/Properties/launchSettings.json create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/WeChat/Official/Messages/TextMessageReplyContributor.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/WeChat/Official/Messages/UserSubscribeEventContributor.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/WeChat/Work/Messages/TextMessageReplyContributor.cs create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/appsettings.Development.json create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/appsettings.PostgreSql.json create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/appsettings.json create mode 100644 aspnet-core/services/LY.AIO.Applications.Single/gulpfile.js diff --git a/aspnet-core/LINGYUN.MicroService.SingleProject.sln b/aspnet-core/LINGYUN.MicroService.SingleProject.sln index 3dde071c9..a850d1539 100644 --- a/aspnet-core/LINGYUN.MicroService.SingleProject.sln +++ b/aspnet-core/LINGYUN.MicroService.SingleProject.sln @@ -617,6 +617,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LY.MicroService.Application EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer", "migrations\LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer\LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer.csproj", "{30EEF879-CFF7-4661-89CB-9CB68328D008}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LY.AIO.Applications.Single", "services\LY.AIO.Applications.Single\LY.AIO.Applications.Single.csproj", "{37740138-D088-46F5-83A8-8A8180FE65D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1619,6 +1621,10 @@ Global {30EEF879-CFF7-4661-89CB-9CB68328D008}.Debug|Any CPU.Build.0 = Debug|Any CPU {30EEF879-CFF7-4661-89CB-9CB68328D008}.Release|Any CPU.ActiveCfg = Release|Any CPU {30EEF879-CFF7-4661-89CB-9CB68328D008}.Release|Any CPU.Build.0 = Release|Any CPU + {37740138-D088-46F5-83A8-8A8180FE65D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37740138-D088-46F5-83A8-8A8180FE65D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37740138-D088-46F5-83A8-8A8180FE65D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37740138-D088-46F5-83A8-8A8180FE65D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1916,6 +1922,7 @@ Global {5A07FFDF-F979-44F9-BE24-81D6A25BEADB} = {0D69B63D-F082-4D57-9FF0-355642C56993} {2B167D92-2327-4679-9096-49F274FABE0C} = {0D69B63D-F082-4D57-9FF0-355642C56993} {30EEF879-CFF7-4661-89CB-9CB68328D008} = {0D69B63D-F082-4D57-9FF0-355642C56993} + {37740138-D088-46F5-83A8-8A8180FE65D8} = {B4247B78-34BC-4A3F-91A4-661F7DCD6E10} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1} diff --git a/aspnet-core/services/Directory.Packages.props b/aspnet-core/services/Directory.Packages.props new file mode 100644 index 000000000..84f4c0737 --- /dev/null +++ b/aspnet-core/services/Directory.Packages.props @@ -0,0 +1,263 @@ + + + + 8.3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspnet-core/services/LY.AIO.Applications.Single/.config/dotnet-tools.json b/aspnet-core/services/LY.AIO.Applications.Single/.config/dotnet-tools.json new file mode 100644 index 000000000..6b93cca86 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/.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/services/LY.AIO.Applications.Single/.gitignore b/aspnet-core/services/LY.AIO.Applications.Single/.gitignore new file mode 100644 index 000000000..7b6f60857 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/.gitignore @@ -0,0 +1,2 @@ +wwwroot +package*.json \ No newline at end of file diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Authentication/AbpCookieAuthenticationHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/Authentication/AbpCookieAuthenticationHandler.cs new file mode 100644 index 000000000..2cf43e1f2 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJob.cs b/aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJob.cs new file mode 100644 index 000000000..e1bbef7c2 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJob.cs @@ -0,0 +1,38 @@ +using LINGYUN.Abp.Notifications; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs b/aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs new file mode 100644 index 000000000..8d721981c --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/BackgroundJobs/NotificationPublishJobArgs.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.Notifications; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Controllers/HomeController.cs b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/HomeController.cs new file mode 100644 index 000000000..c154b5098 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/HomeController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace LY.AIO.Applications.Single.Controllers; + +public class HomeController : Controller +{ + public IActionResult Index() + { + return Redirect("/swagger"); + } +} diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Controllers/SettingMergeController.cs b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/SettingMergeController.cs new file mode 100644 index 000000000..dd64e4201 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/SettingMergeController.cs @@ -0,0 +1,70 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Controllers/UserSettingMergeController.cs b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/UserSettingMergeController.cs new file mode 100644 index 000000000..ab8428491 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Controllers/UserSettingMergeController.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.SettingManagement; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Dockerfile b/aspnet-core/services/LY.AIO.Applications.Single/Dockerfile new file mode 100644 index 000000000..aee09fd66 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/EventBus/Distributed/ChatMessageEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/ChatMessageEventHandler.cs new file mode 100644 index 000000000..52cf0d875 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs new file mode 100644 index 000000000..275ba0a4d --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/NotificationEventHandler.cs @@ -0,0 +1,470 @@ +using LINGYUN.Abp.Notifications; +using LY.AIO.Applications.Single.BackgroundJobs; +using LY.AIO.Applications.Single.MultiTenancy; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs new file mode 100644 index 000000000..7a33a9cae --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/TenantSynchronizer.cs @@ -0,0 +1,53 @@ +using LINGYUN.Abp.Saas.Tenants; +using LY.AIO.Applications.Single.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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Distributed/UserCreateEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/UserCreateEventHandler.cs new file mode 100644 index 000000000..11684352e --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Distributed/WebhooksEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Distributed/WebhooksEventHandler.cs new file mode 100644 index 000000000..3681560cb --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateJoinIMEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateJoinIMEventHandler.cs new file mode 100644 index 000000000..822e1220d --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateSendWelcomeEventHandler.cs b/aspnet-core/services/LY.AIO.Applications.Single/EventBus/Local/UserCreateSendWelcomeEventHandler.cs new file mode 100644 index 000000000..c430f94c0 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/IdentityResources/CustomIdentityResources.cs b/aspnet-core/services/LY.AIO.Applications.Single/IdentityResources/CustomIdentityResources.cs new file mode 100644 index 000000000..feefc80df --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/IdentityResources/CustomIdentityResources.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.Identity; +using IdentityServer4.Models; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/LY.AIO.Applications.Single.csproj b/aspnet-core/services/LY.AIO.Applications.Single/LY.AIO.Applications.Single.csproj new file mode 100644 index 000000000..d135a63ea --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/LY.AIO.Applications.Single.csproj @@ -0,0 +1,272 @@ + + + + + + net8.0 + enable + LY.AIO.Applications.Single + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs b/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs new file mode 100644 index 000000000..f2f66239a --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.Authentication; +using LY.AIO.Applications.Single.IdentityResources; +using LY.AIO.Applications.Single.Microsoft.Extensions.DependencyInjection; +using LY.AIO.Applications.Single.WeChat.Official.Messages; +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 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 LY.AIO.Applications.Single; + +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/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.cs b/aspnet-core/services/LY.AIO.Applications.Single/MicroServiceApplicationsSingleModule.cs new file mode 100644 index 000000000..a0c2ce2d6 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single; + +[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/services/LY.AIO.Applications.Single/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs b/aspnet-core/services/LY.AIO.Applications.Single/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs new file mode 100644 index 000000000..9413fb658 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Microsoft/Extensions/DependencyInjection/SameSiteCookiesServiceCollectionExtensions.cs @@ -0,0 +1,67 @@ +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs b/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs new file mode 100644 index 000000000..475466d04 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/ITenantConfigurationCache.cs @@ -0,0 +1,10 @@ +using Volo.Abp.MultiTenancy; + +namespace LY.AIO.Applications.Single.MultiTenancy; + +public interface ITenantConfigurationCache +{ + Task RefreshAsync(); + + Task> GetTenantsAsync(); +} diff --git a/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCache.cs b/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCache.cs new file mode 100644 index 000000000..e6d81bdcc --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs b/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs new file mode 100644 index 000000000..023235e3e --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/MultiTenancy/TenantConfigurationCacheItem.cs @@ -0,0 +1,19 @@ +using Volo.Abp.MultiTenancy; + +namespace LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml new file mode 100644 index 000000000..aed9202eb --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js new file mode 100644 index 000000000..55a88e52e --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml new file mode 100644 index 000000000..63e834b2b --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml @@ -0,0 +1,17 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model LY.AIO.Applications.Single.Pages.Account.EmailConfirmModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +
+
+

@L["EmailConfirm"]

+
+ + + @L["Cancel"] + + +
+
diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs new file mode 100644 index 000000000..ea7b4671c --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml new file mode 100644 index 000000000..56c00aeaf --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml @@ -0,0 +1,13 @@ +@page +@model LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs new file mode 100644 index 000000000..a66f51c1b --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml new file mode 100644 index 000000000..1598132ba --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model LY.AIO.Applications.Single.Pages.Account.SendCodeModel +@inject IHtmlLocalizer L + +
+
+

@L["TwoFactor"]

+
+ + + +
+ +
+
+ @L["SendVerifyCode"] +
+ + @L["Login"] + + +
+
+ diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendCode.cshtml.cs new file mode 100644 index 000000000..34b2f4c86 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml new file mode 100644 index 000000000..68bc7890d --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml @@ -0,0 +1,16 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model LY.AIO.Applications.Single.Pages.Account.SendEmailConfirmModel +@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout +
+
+

@L["EmailConfirm"]

+
+ + @L["Cancel"] + + +
+
diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs new file mode 100644 index 000000000..c2ef4852f --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs new file mode 100644 index 000000000..322cca6be --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml new file mode 100644 index 000000000..079925ee4 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml @@ -0,0 +1,4 @@ +@page +@model LY.AIO.Applications.Single.Pages.Account.UseRecoveryCodeModel +@{ +} diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs new file mode 100644 index 000000000..016bf3771 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace LY.AIO.Applications.Single.Pages.Account +{ + public class UseRecoveryCodeModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml new file mode 100644 index 000000000..bc3e49acf --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model LY.AIO.Applications.Single.Pages.Account.VerifyAuthenticatorCodeModel +@inject IHtmlLocalizer L +
+
+
+ + +
+ + + +
+
+ + +
+ @L["VerifyAuthenticatorCode"] + + @L["Login"] + +
+
+
\ No newline at end of file diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml.cs new file mode 100644 index 000000000..9a5d3c6ec --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml new file mode 100644 index 000000000..94589fb71 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml @@ -0,0 +1,29 @@ +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@model LY.AIO.Applications.Single.Pages.Account.VerifyCodeModel +
+
+
+ + + + +
+ +
+ + + + + +
+ @L["VerifyAuthenticatorCode"] +
+ + @L["ReSendVerifyCode"] + +
+
+
diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Account/VerifyCode.cshtml.cs new file mode 100644 index 000000000..17ebff5bb --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Index.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Index.cshtml new file mode 100644 index 000000000..f220a0b10 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/Pages/Index.cshtml.cs b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Index.cshtml.cs new file mode 100644 index 000000000..5164786e9 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Pages/Index.cshtml.cs @@ -0,0 +1,11 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace LY.AIO.Applications.Single.Pages +{ + public class IndexModel : AbpPageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/aspnet-core/services/LY.AIO.Applications.Single/Pages/_ViewImports.cshtml b/aspnet-core/services/LY.AIO.Applications.Single/Pages/_ViewImports.cshtml new file mode 100644 index 000000000..c1da1f5f1 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/Program.cs b/aspnet-core/services/LY.AIO.Applications.Single/Program.cs new file mode 100644 index 000000000..7d97a1b43 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/Program.cs @@ -0,0 +1,82 @@ +using LINGYUN.Abp.Identity.Session.AspNetCore; +using LY.AIO.Applications.Single; +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/services/LY.AIO.Applications.Single/Properties/launchSettings.json b/aspnet-core/services/LY.AIO.Applications.Single/Properties/launchSettings.json new file mode 100644 index 000000000..337677308 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs b/aspnet-core/services/LY.AIO.Applications.Single/TenantHeaderParamter.cs new file mode 100644 index 000000000..9d80a45fd --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single; + +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/services/LY.AIO.Applications.Single/WeChat/Official/Messages/TextMessageReplyContributor.cs b/aspnet-core/services/LY.AIO.Applications.Single/WeChat/Official/Messages/TextMessageReplyContributor.cs new file mode 100644 index 000000000..593116275 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/WeChat/Official/Messages/UserSubscribeEventContributor.cs b/aspnet-core/services/LY.AIO.Applications.Single/WeChat/Official/Messages/UserSubscribeEventContributor.cs new file mode 100644 index 000000000..249105de0 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/WeChat/Work/Messages/TextMessageReplyContributor.cs b/aspnet-core/services/LY.AIO.Applications.Single/WeChat/Work/Messages/TextMessageReplyContributor.cs new file mode 100644 index 000000000..f0c6a2b1f --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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 LY.AIO.Applications.Single.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/services/LY.AIO.Applications.Single/appsettings.Development.json b/aspnet-core/services/LY.AIO.Applications.Single/appsettings.Development.json new file mode 100644 index 000000000..a9d10fdff --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/appsettings.PostgreSql.json b/aspnet-core/services/LY.AIO.Applications.Single/appsettings.PostgreSql.json new file mode 100644 index 000000000..cf15e11f7 --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/appsettings.PostgreSql.json @@ -0,0 +1,246 @@ +{ + "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": false, + "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": false, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "PostgreSql": { + "Enabled": true + } + } + }, + "WorkflowSettings": { + "Enabled": false, + "ConnectionStringIdentifier": "Default", + "EntityFrameworkCore": { + "PostgreSql": { + "Enabled": true + } + } + } + }, + "Server": { + "BaseUrl": "http://127.0.0.1:30000" + } + }, + "Quartz": { + "UsePersistentStore": false, + "Properties": { + "quartz.jobStore.dataSource": "tkm", + "quartz.jobStore.useProperties": "true", + "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;SslMode=Prefer", + "quartz.dataSource.tkm.provider": "Npgsql", + "quartz.dataSource.tkm.connectionStringName": "Default", + "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/services/LY.AIO.Applications.Single/appsettings.json b/aspnet-core/services/LY.AIO.Applications.Single/appsettings.json new file mode 100644 index 000000000..ff9beea3e --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/services/LY.AIO.Applications.Single/gulpfile.js b/aspnet-core/services/LY.AIO.Applications.Single/gulpfile.js new file mode 100644 index 000000000..bec4d578f --- /dev/null +++ b/aspnet-core/services/LY.AIO.Applications.Single/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/common.props b/common.props index cd370d567..9af819310 100644 --- a/common.props +++ b/common.props @@ -11,7 +11,7 @@ git https://github.com/colinin/abp-next-admin true - Debug;Release;PostgreSQL + Debug;Release