Browse Source

Enhanced configuration of Hangfire

pull/274/head
cKey 5 years ago
parent
commit
93d86728ab
  1. 1
      .gitignore
  2. 39
      aspnet-core/LINGYUN.MicroService.Messages.sln
  3. 4
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN.Abp.BackgroundJobs.Hangfire.csproj
  4. 34
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs
  5. 4
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
  6. 6
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  7. 7
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/Volo/Abp/BackgroundJobs/IBackgroundJobManagerExtensions.cs
  8. 14
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN.Abp.BackgroundJobs.csproj
  9. 43
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN/Abp/BackgroundJobs/RetryAsyncBackgroundJobArgs.cs
  10. 80
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN/Abp/BackgroundJobs/RetryAsyncBackgroundJobBase.cs
  11. 15
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN.Abp.BackgroundWorkers.Hangfire.csproj
  12. 19
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs
  13. 25
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/Check.cs
  14. 171
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/CronGenerator.cs
  15. 47
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerAdapter.cs
  16. 63
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs
  17. 10
      aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/IHangfireBackgroundWorkerAdapter.cs
  18. 23
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN.Abp.Hangfire.Dashboard.csproj
  19. 36
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/AbpHangfireDashboardModule.cs
  20. 32
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/AbpHangfireDashboardOptionsProvider.cs
  21. 32
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/DashboardAuthorizationFilter.cs
  22. 33
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/HangfireDashboardPermissionDefinitionProvider.cs
  23. 15
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/HangfireDashboardPermissions.cs
  24. 9
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/HangfireDashboardResource.cs
  25. 7
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/Resources/en.json
  26. 7
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/Resources/zh-Hans.json
  27. 12
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/Microsoft/AspNetCore/Builder/ApplicationBuilderAbpHangfireAuthoricationMiddlewareExtension.cs
  28. 27
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/Microsoft/AspNetCore/Http/HangfireAuthoricationMiddleware.cs
  29. 4
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.MySqlStorage/LINGYUN/Abp/Hangfire/Storage/MySql/AbpHangfireMySqlStorageModule.cs
  30. 4
      aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Storage.SqlServer/LINGYUN/Abp/Hangfire/Storage/SqlServer/AbpHangfireSqlServerStorageModule.cs
  31. 8
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/Class1.cs
  32. 14
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/LINGYUN.Abp.Notifications.Workers.csproj
  33. 10
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/LINGYUN/Abp/Notifications/Workers/AbpNotificationsWorkersModule.cs
  34. 3
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN.Abp.Notifications.csproj
  35. 5
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/AbpNotificationModule.cs
  36. 55
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/AbpMessageServiceHttpApiHostModule.cs
  37. 73
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Authorization/HangfireDashboardAuthorizationFilter.cs
  38. 38
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/BackgroundJobs/NotificationCleanupExpritionJob.cs
  39. 23
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/BackgroundJobs/NotificationCleanupExpritionJobArgs.cs
  40. 74
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/DashboardOptionsExtensions.cs
  41. 47
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireApplicationBuilderExtensions.cs
  42. 74
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireDashboardRouteOptions.cs
  43. 31
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireJwtTokenMiddleware.cs
  44. 1
      aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj

1
.gitignore

@ -11,6 +11,7 @@ appsettings.json
appsettings.*.json
tempkey.jwk
.vs
Publish
/tests/e2e/videos/
/tests/e2e/screenshots/

39
aspnet-core/LINGYUN.MicroService.Messages.sln

@ -67,9 +67,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.EntityFrameworkCore.Tests\LINGYUN.Abp.EntityFrameworkCore.Tests.csproj", "{25267137-08BE-44A6-9F7E-7783CC8C62E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests\LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests.csproj", "{CB6D56DA-539B-4D4F-81CD-D683ADEBFB5F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests\LINGYUN.Abp.MessageService.EntityFrameworkCore.Tests.csproj", "{CB6D56DA-539B-4D4F-81CD-D683ADEBFB5F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.MessageService.Domain.Tests", "tests\LINGYUN.Abp.MessageService.Domain.Tests\LINGYUN.Abp.MessageService.Domain.Tests.csproj", "{097319B9-D062-4A54-8F8C-001C180E2CB3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MessageService.Domain.Tests", "tests\LINGYUN.Abp.MessageService.Domain.Tests\LINGYUN.Abp.MessageService.Domain.Tests.csproj", "{097319B9-D062-4A54-8F8C-001C180E2CB3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundJobs.Hangfire", "modules\common\LINGYUN.Abp.BackgroundJobs.Hangfire\LINGYUN.Abp.BackgroundJobs.Hangfire.csproj", "{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Hangfire.Storage.MySql", "modules\common\LINGYUN.Abp.Hangfire.MySqlStorage\LINGYUN.Abp.Hangfire.Storage.MySql.csproj", "{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Hangfire.Dashboard", "modules\common\LINGYUN.Abp.Hangfire.Dashboard\LINGYUN.Abp.Hangfire.Dashboard.csproj", "{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundWorkers.Hangfire", "modules\common\LINGYUN.Abp.BackgroundWorkers.Hangfire\LINGYUN.Abp.BackgroundWorkers.Hangfire.csproj", "{32D4DB5D-74D1-4166-85EA-B2D8F14B8058}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EventBus.CAP", "modules\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj", "{C49B50D4-5D63-47E6-82F7-E742181CF9DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -189,6 +199,26 @@ Global
{097319B9-D062-4A54-8F8C-001C180E2CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{097319B9-D062-4A54-8F8C-001C180E2CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{097319B9-D062-4A54-8F8C-001C180E2CB3}.Release|Any CPU.Build.0 = Release|Any CPU
{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97}.Release|Any CPU.Build.0 = Release|Any CPU
{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4}.Release|Any CPU.Build.0 = Release|Any CPU
{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48}.Release|Any CPU.Build.0 = Release|Any CPU
{32D4DB5D-74D1-4166-85EA-B2D8F14B8058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32D4DB5D-74D1-4166-85EA-B2D8F14B8058}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32D4DB5D-74D1-4166-85EA-B2D8F14B8058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32D4DB5D-74D1-4166-85EA-B2D8F14B8058}.Release|Any CPU.Build.0 = Release|Any CPU
{C49B50D4-5D63-47E6-82F7-E742181CF9DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C49B50D4-5D63-47E6-82F7-E742181CF9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C49B50D4-5D63-47E6-82F7-E742181CF9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C49B50D4-5D63-47E6-82F7-E742181CF9DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -225,6 +255,11 @@ Global
{25267137-08BE-44A6-9F7E-7783CC8C62E8} = {42800C56-9473-4B96-BF29-1B0F25C867F4}
{CB6D56DA-539B-4D4F-81CD-D683ADEBFB5F} = {42800C56-9473-4B96-BF29-1B0F25C867F4}
{097319B9-D062-4A54-8F8C-001C180E2CB3} = {42800C56-9473-4B96-BF29-1B0F25C867F4}
{737EF17D-477F-4C73-A4CC-0DA6CBD3BB97} = {C00828FB-E7D5-4086-BA50-02022594AB73}
{0B6AAACE-A1DE-4063-8DA8-E7EBA30186E4} = {C00828FB-E7D5-4086-BA50-02022594AB73}
{05DCBA6B-480A-4D2F-9855-F66F5B7F3A48} = {C00828FB-E7D5-4086-BA50-02022594AB73}
{32D4DB5D-74D1-4166-85EA-B2D8F14B8058} = {C00828FB-E7D5-4086-BA50-02022594AB73}
{C49B50D4-5D63-47E6-82F7-E742181CF9DE} = {C00828FB-E7D5-4086-BA50-02022594AB73}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6238659A-7267-49B9-A499-8746BDEED6B8}

4
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN.Abp.BackgroundJobs.Hangfire.csproj

@ -11,5 +11,9 @@
<PackageReference Include="Volo.Abp.HangFire" Version="4.3.0" />
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Hangfire.Dashboard\LINGYUN.Abp.Hangfire.Dashboard.csproj" />
</ItemGroup>
</Project>

34
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs

@ -1,7 +1,10 @@
using Hangfire;
using Hangfire.Dashboard;
using LINGYUN.Abp.Hangfire.Dashboard;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using Volo.Abp;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Hangfire;
@ -11,14 +14,39 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
{
[DependsOn(
typeof(AbpBackgroundJobsAbstractionsModule),
typeof(AbpHangfireModule)
typeof(AbpHangfireModule),
typeof(AbpHangfireDashboardModule)
)]
public class AbpBackgroundJobsHangfireModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<DashboardOptions>(options =>
{
options.DisplayNameFunc = (dashboardContext, job) =>
{
if (job.Args.Count == 0)
{
return job.Type.FullName;
//if (job.Type.GenericTypeArguments.Length == 0)
//{
// return job.Type.FullName;
//}
//// TODO: 把特性作为任务名称?
//return BackgroundJobNameAttribute.GetName(job.Type.GenericTypeArguments[0]);
}
var context = dashboardContext.GetHttpContext();
var options = context.RequestServices.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
return options.GetJob(job.Args.First().GetType()).JobName;
};
});
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
if (!options.IsJobExecutionEnabled)
var jobOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
if (!jobOptions.IsJobExecutionEnabled)
{
var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value;
hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer;

4
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs

@ -16,7 +16,7 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
{
return Task.FromResult(
BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args)
adapter => adapter.ExecuteAsync(args)
)
);
}
@ -24,7 +24,7 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
{
return Task.FromResult(
BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args),
adapter => adapter.ExecuteAsync(args),
delay.Value
)
);

6
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs

@ -1,8 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.BackgroundJobs.Hangfire
{
@ -22,7 +22,7 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
Options = options.Value;
}
public void Execute(TArgs args)
public async Task ExecuteAsync(TArgs args)
{
if (!Options.IsJobExecutionEnabled)
{
@ -39,7 +39,7 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
{
var jobType = Options.GetJob(typeof(TArgs)).JobType;
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args);
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context));
await JobExecuter.ExecuteAsync(context);
}
}
}

7
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/LINGYUN/Abp/BackgroundJobs/Hangfire/IBackgroundJobManagerExtensions.cs → aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs.Hangfire/Volo/Abp/BackgroundJobs/IBackgroundJobManagerExtensions.cs

@ -1,10 +1,9 @@
using Hangfire;
using JetBrains.Annotations;
using LINGYUN.Abp.BackgroundJobs.Hangfire;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.BackgroundJobs;
namespace LINGYUN.Abp.BackgroundJobs.Hangfire
namespace Volo.Abp.BackgroundJobs
{
public static class IBackgroundJobManagerExtensions
{
@ -27,7 +26,7 @@ namespace LINGYUN.Abp.BackgroundJobs.Hangfire
var jobName = BackgroundJobNameAttribute.GetName<TArgs>();
RecurringJob.AddOrUpdate<HangfireJobExecutionAdapter<TArgs>>(jobName, adapter => adapter.Execute(args), cron);
RecurringJob.AddOrUpdate<HangfireJobExecutionAdapter<TArgs>>(jobName, adapter => adapter.ExecuteAsync(args), cron);
return Task.CompletedTask;
}

14
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN.Abp.BackgroundJobs.csproj

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="4.3.0" />
</ItemGroup>
</Project>

43
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN/Abp/BackgroundJobs/RetryAsyncBackgroundJobArgs.cs

@ -1,43 +0,0 @@
namespace LINGYUN.Abp.BackgroundJobs
{
public class RetryAsyncBackgroundJobArgs<TArgs>
{
/// <summary>
/// 重试次数
/// </summary>
public int RetryCount { get; set; } = 0;
/// <summary>
/// 重试间隔(毫秒)
/// 默认 300000ms = 5min
/// </summary>
public double RetryIntervalMillisecond { get; set; } = 300000d;
/// <summary>
/// 最大重试次数
/// 默认 20
/// </summary>
public int MaxRetryCount { get; set; } = 20;
/// <summary>
/// 作业参数
/// </summary>
public TArgs JobArgs { get; set; }
public RetryAsyncBackgroundJobArgs()
{
}
public RetryAsyncBackgroundJobArgs(TArgs jobArgs)
{
JobArgs = jobArgs;
}
public RetryAsyncBackgroundJobArgs(TArgs jobArgs, int retryCount = 0, double interval = 300000d, int maxRetryCount = 20)
{
JobArgs = jobArgs;
RetryCount = retryCount;
RetryIntervalMillisecond = interval;
MaxRetryCount = maxRetryCount;
}
}
}

80
aspnet-core/modules/common/LINGYUN.Abp.BackgroundJobs/LINGYUN/Abp/BackgroundJobs/RetryAsyncBackgroundJobBase.cs

@ -1,80 +0,0 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
namespace LINGYUN.Abp.BackgroundJobs
{
public abstract class RetryAsyncBackgroundJobBase<TArgs> : IAsyncBackgroundJob<RetryAsyncBackgroundJobArgs<TArgs>>
{
public ILogger<RetryAsyncBackgroundJobBase<TArgs>> Logger { get; set; }
protected IBackgroundJobManager BackgroundJobManager { get; }
protected RetryAsyncBackgroundJobBase(
IBackgroundJobManager backgroundJobManager)
{
BackgroundJobManager = backgroundJobManager;
Logger = NullLogger<RetryAsyncBackgroundJobBase<TArgs>>.Instance;
}
public async Task ExecuteAsync(RetryAsyncBackgroundJobArgs<TArgs> args)
{
if (args.RetryCount > args.MaxRetryCount)
{
Logger.LogWarning("Job has failed and the maximum number of retries has been reached. The failure callback is about to enter");
// 任务执行失败次数已达上限,调用用户定义回调,并不再执行
await OnJobExecuteFailedAsync(args.JobArgs);
return;
}
try
{
// 执行任务
await ExecuteAsync(args.JobArgs, args.RetryCount);
// 执行完成后回调
await OnJobExecuteCompletedAsync(args.JobArgs);
}
catch(Exception ex)
{
Logger.LogWarning("Job execution has failed and a retry is imminent");
Logger.LogWarning("Job running error:{0}", ex.Message);
// 每次重试 间隔时间增加1.1倍
var retryInterval = args.RetryIntervalMillisecond * 1.1;
var retryJobArgs = new RetryAsyncBackgroundJobArgs<TArgs>(args.JobArgs,
args.RetryCount + 1, retryInterval, args.MaxRetryCount);
Logger.LogDebug("Job task is queued for the next execution");
// 计算优先级
BackgroundJobPriority priority = BackgroundJobPriority.Normal;
if (args.RetryCount <= (args.MaxRetryCount / 2) &&
args.RetryCount > (args.MaxRetryCount / 3))
{
priority = BackgroundJobPriority.BelowNormal;
}
else if (args.RetryCount > (args.MaxRetryCount / 1.5))
{
priority = BackgroundJobPriority.Low;
}
// 延迟入队,等待下一次运行
await BackgroundJobManager.EnqueueAsync(retryJobArgs, priority, delay: TimeSpan.FromMilliseconds(retryInterval));
}
}
protected abstract Task ExecuteAsync(TArgs args, int retryCount);
protected virtual Task OnJobExecuteFailedAsync(TArgs args)
{
return Task.CompletedTask;
}
protected virtual Task OnJobExecuteCompletedAsync(TArgs args)
{
return Task.CompletedTask;
}
}
}

15
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN.Abp.BackgroundWorkers.Hangfire.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.HangFire" Version="4.3.0" />
<PackageReference Include="Volo.Abp.BackgroundWorkers" Version="4.3.0" />
</ItemGroup>
</Project>

19
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/AbpBackgroundWorkersHangfireModule.cs

@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Hangfire;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
[DependsOn(
typeof(AbpBackgroundWorkersModule),
typeof(AbpHangfireModule)
)]
public class AbpBackgroundWorkersHangfireModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton(typeof(HangfireBackgroundWorkerAdapter<>));
}
}
}

25
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/Check.cs

@ -0,0 +1,25 @@
using JetBrains.Annotations;
using System;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
internal static class Check
{
public static int Range(
int value,
[InvokerParameterName][NotNull] string parameterName,
int minimum = int.MinValue,
int maximum = int.MaxValue)
{
if (value < minimum)
{
throw new ArgumentException($"{parameterName} must be equal to or lower than {minimum}!", parameterName);
}
if (value > maximum)
{
throw new ArgumentException($"{parameterName} must be equal to or greater than {maximum}!", parameterName);
}
return value;
}
}
}

171
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/CronGenerator.cs

@ -0,0 +1,171 @@
using System;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
public class CronGenerator
{
public const long MilliSecondsOfYear = 315_3600_0000L;
public const long MilliSecondsOfMonth = 26_7840_0000L;
public const int MilliSecondsOfWeek = 6_0480_0000;
public const int MilliSecondsOfDays = 8640_0000;
public const int MilliSecondsOfHours = 360_0000;
public const int MilliSecondsOfMinutes = 60000;
/// <summary>
/// 从毫秒计算Cron表达式
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
public static string FormMilliseconds(long milliseconds = 1000)
{
if (milliseconds <= 1000)
{
return Seconds(0, 1);
}
if (milliseconds >= MilliSecondsOfYear)
{
return Year(1, 1, 0, 0, 2001, (int)(milliseconds / MilliSecondsOfYear));
}
if (milliseconds >= MilliSecondsOfMonth)
{
return Month(1, 0, 0, 1, (int)(milliseconds / MilliSecondsOfMonth));
}
// TODO: 以周为单位的任务Cron
if (milliseconds >= MilliSecondsOfWeek)
{
return Day(0, 0, 1, (int)(milliseconds / MilliSecondsOfWeek));
}
if (milliseconds >= MilliSecondsOfDays)
{
return Day(0, 0, 1, (int)(milliseconds / MilliSecondsOfDays));
}
if (milliseconds >= MilliSecondsOfHours)
{
return Hour(0, 0, (int)(milliseconds / MilliSecondsOfHours));
}
if (milliseconds >= MilliSecondsOfMinutes)
{
return Minute(0, 0, (int)(milliseconds / MilliSecondsOfMinutes));
}
return Seconds(0, (int)(milliseconds / 1000));
}
/// <summary>
/// 周期性为秒钟的任务
/// </summary>
/// <param name="second">第几秒开始,默认为第0秒</param>
/// <param name="interval">执行周期的间隔,默认为每5秒一次</param>
/// <returns></returns>
public static string Seconds(int second = 0, int interval = 5)
{
Check.Range(second, nameof(second), 0, 59);
return $"{second}/{interval} * * * * ? ";
}
/// <summary>
/// 周期性为分钟的任务
/// </summary>
/// <param name="second">第几秒开始,默认为第0秒</param>
/// <param name="minute">第几分钟开始,默认为第0分钟</param>
/// <param name="interval">执行周期的间隔,默认为每分钟一次</param>
/// <returns></returns>
public static string Minute(int second = 0, int minute = 0, int interval = 1)
{
Check.Range(second, nameof(second), 0, 59);
Check.Range(minute, nameof(minute), 0, 59);
return $"{second} {minute}/{interval} * * * ? ";
}
/// <summary>
/// 周期性为小时的任务
/// </summary>
/// <param name="minute">第几分钟开始,默认为第0分钟</param>
/// <param name="hour">第几小时开始,默认为0点</param>
/// <param name="interval">执行周期的间隔,默认为每小时一次</param>
/// <returns></returns>
public static string Hour(int minute = 0, int hour = 0, int interval = 1)
{
Check.Range(hour, nameof(hour), 0, 23);
Check.Range(minute, nameof(minute), 0, 59);
return $"0 {minute} {hour}/{interval} * * ? ";
}
/// <summary>
/// 周期性为天的任务
/// </summary>
/// <param name="hour">第几小时开始,默认从0点开始</param>
/// <param name="minute">第几分钟开始,默认从第0分钟开始</param>
/// <param name="day">第几天开始,默认从第1天开始</param>
/// <param name="interval">执行周期的间隔,默认为每天一次</param>
/// <returns></returns>
public static string Day(int hour = 0, int minute = 0, int day = 1, int interval = 1)
{
Check.Range(hour, nameof(hour), 0, 23);
Check.Range(minute, nameof(minute), 0, 59);
Check.Range(day, nameof(day), 1, 31);
return $"0 {minute} {hour} {day}/{interval} * ? ";
}
/// <summary>
/// 周期性为周的任务
/// </summary>
/// <param name="dayOfWeek">星期几开始,默认从星期一点开始</param>
/// <param name="hour">第几小时开始,默认从0点开始</param>
/// <param name="minute">第几分钟开始,默认从第0分钟开始</param>
/// <returns></returns>
public static string Week(DayOfWeek dayOfWeek = DayOfWeek.Monday, int hour = 0, int minute = 0)
{
Check.Range(hour, nameof(hour), 0, 23);
Check.Range(minute, nameof(minute), 0, 59);
return $"{minute} {hour} * * {dayOfWeek.ToString().Substring(0, 3)}";
}
/// <summary>
/// 周期性为月的任务
/// </summary>
/// <param name="day">几号开始,默认从一号开始</param>
/// <param name="hour">第几小时开始,默认从0点开始</param>
/// <param name="minute">第几分钟开始,默认从第0分钟开始</param>
/// <param name="month">第几月开始,默认从第1月开始</param>
/// <param name="interval">月份间隔</param>
/// <returns></returns>
public static string Month(int day = 1, int hour = 0, int minute = 0, int month = 1, int interval = 1)
{
Check.Range(hour, nameof(hour), 0, 23);
Check.Range(minute, nameof(minute), 0, 59);
Check.Range(day, nameof(day), 1, 31);
return $"0 {minute} {hour} {day} {month}/{interval} ?";
}
/// <summary>
/// 周期性为年的任务
/// </summary>
/// <param name="month">几月开始,默认从一月开始</param>
/// <param name="day">几号开始,默认从一号开始</param>
/// <param name="hour">第几小时开始,默认从0点开始</param>
/// <param name="year">第几年开始,默认从2001年开始</param>
/// <param name="minute">第几分钟开始,默认从第0分钟开始</param>
/// <returns></returns>
public static string Year(int month = 1, int day = 1, int hour = 0, int minute = 0, int year = 2001, int interval = 1)
{
Check.Range(hour, nameof(hour), 0, 23);
Check.Range(minute, nameof(minute), 0, 59);
Check.Range(day, nameof(day), 1, 31);
Check.Range(month, nameof(month), 1, 12);
return $"0 {minute} {hour} {day} {month} ? {year}/{interval}";
}
}
}

47
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerAdapter.cs

@ -0,0 +1,47 @@
using System.Reflection;
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
public class HangfireBackgroundWorkerAdapter<TWorker> : BackgroundWorkerBase, IHangfireBackgroundWorkerAdapter
where TWorker : IBackgroundWorker
{
private readonly MethodInfo _doWorkAsyncMethod;
private readonly MethodInfo _doWorkMethod;
public HangfireBackgroundWorkerAdapter()
{
_doWorkAsyncMethod = typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic);
_doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic);
}
public virtual async Task ExecuteAsync()
{
var worker = (IBackgroundWorker)ServiceProvider.GetService(typeof(TWorker));
var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider);
switch (worker)
{
case AsyncPeriodicBackgroundWorkerBase asyncWorker:
{
if (_doWorkAsyncMethod != null)
{
await (Task)_doWorkAsyncMethod.Invoke(asyncWorker, new object[] { workerContext });
}
break;
}
case PeriodicBackgroundWorkerBase syncWorker:
{
if (_doWorkMethod != null)
{
_doWorkMethod.Invoke(syncWorker, new object[] { workerContext });
}
break;
}
}
}
}
}

63
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs

@ -0,0 +1,63 @@
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
[Dependency(ReplaceServices = true)]
public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingletonDependency
{
protected IServiceProvider ServiceProvider { get; }
public HangfireBackgroundWorkerManager(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public void Add(IBackgroundWorker worker)
{
var timer = worker.GetType()
.GetProperty("Timer", BindingFlags.NonPublic | BindingFlags.Instance)?
.GetValue(worker);
if (timer == null)
{
return;
}
var period = timer.GetType()
.GetProperty("Period", BindingFlags.Public | BindingFlags.Instance)?
.GetValue(timer)?
.To<int>();
if (!period.HasValue)
{
return;
}
var adapterType = typeof(HangfireBackgroundWorkerAdapter<>).MakeGenericType(worker.GetType());
var workerAdapter = ServiceProvider.GetRequiredService(adapterType) as IHangfireBackgroundWorkerAdapter;
RecurringJob.AddOrUpdate(
recurringJobId: worker.GetType().FullName,
methodCall: () => workerAdapter.ExecuteAsync(),
cronExpression: CronGenerator.FormMilliseconds(period.Value));
}
public Task StartAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
}

10
aspnet-core/modules/common/LINGYUN.Abp.BackgroundWorkers.Hangfire/LINGYUN/Abp/BackgroundWorkers/Hangfire/IHangfireBackgroundWorkerAdapter.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers;
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire
{
public interface IHangfireBackgroundWorkerAdapter : IBackgroundWorker
{
Task ExecuteAsync();
}
}

23
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN.Abp.Hangfire.Dashboard.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\Hangfire\Dashboard\Localization\Resources\en.json" />
<None Remove="LINGYUN\Abp\Hangfire\Dashboard\Localization\Resources\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LINGYUN\Abp\Hangfire\Dashboard\Localization\Resources\en.json" />
<EmbeddedResource Include="LINGYUN\Abp\Hangfire\Dashboard\Localization\Resources\zh-Hans.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Authorization" Version="4.3.0" />
<PackageReference Include="Volo.Abp.Hangfire" Version="4.3.0" />
</ItemGroup>
</Project>

36
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/AbpHangfireDashboardModule.cs

@ -0,0 +1,36 @@
using LINGYUN.Abp.Hangfire.Dashboard.Localization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization;
using Volo.Abp.Hangfire;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Hangfire.Dashboard
{
[DependsOn(
typeof(AbpLocalizationModule),
typeof(AbpAuthorizationModule),
typeof(AbpHangfireModule))]
public class AbpHangfireDashboardModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpHangfireDashboardModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources.Add<HangfireDashboardResource>();
});
context.Services.AddTransient(serviceProvider =>
{
var options = serviceProvider.GetRequiredService<AbpHangfireDashboardOptionsProvider>().Get();
return context.Services.ExecutePreConfiguredActions(options);
});
}
}
}

32
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/AbpHangfireDashboardOptionsProvider.cs

@ -0,0 +1,32 @@
using Hangfire;
using Hangfire.Dashboard;
using LINGYUN.Abp.Hangfire.Dashboard.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.Hangfire.Dashboard
{
public class AbpHangfireDashboardOptionsProvider : ITransientDependency
{
public virtual DashboardOptions Get()
{
return new DashboardOptions
{
Authorization = new IDashboardAuthorizationFilter[]
{
new DashboardAuthorizationFilter()
},
IsReadOnlyFunc = (context) =>
{
var httpContext = context.GetHttpContext();
var permissionChecker = httpContext.RequestServices.GetRequiredService<IPermissionChecker>();
return !AsyncHelper.RunSync(async () =>
await permissionChecker.IsGrantedAsync(HangfireDashboardPermissions.Dashboard.ManageJobs));
}
};
}
}
}

32
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/DashboardAuthorizationFilter.cs

@ -0,0 +1,32 @@
using Hangfire.Annotations;
using Hangfire.Dashboard;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.Hangfire.Dashboard.Authorization
{
public class DashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
internal readonly static string[] AllowRoutePrefixs = new string[]
{
"/stats",
"/js",
"/css",
"/fonts"
};
public bool Authorize([NotNull] DashboardContext context)
{
if (AllowRoutePrefixs.Any(url => context.Request.Path.StartsWith(url)))
{
return true;
}
var httpContext = context.GetHttpContext();
var permissionChecker = httpContext.RequestServices.GetRequiredService<IPermissionChecker>();
return AsyncHelper.RunSync(async () =>
await permissionChecker.IsGrantedAsync(httpContext.User, HangfireDashboardPermissions.Dashboard.Default));
}
}
}

33
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/HangfireDashboardPermissionDefinitionProvider.cs

@ -0,0 +1,33 @@
using LINGYUN.Abp.Hangfire.Dashboard.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Hangfire.Dashboard.Authorization
{
public class HangfireDashboardPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var group = context.AddGroup(
HangfireDashboardPermissions.GroupName,
L("Permission:Hangfire"),
MultiTenancySides.Host); // 除非对Hangfire Api进行改造,否则不能区分租户
var dashboard = group.AddPermission(
HangfireDashboardPermissions.Dashboard.Default,
L("Permission:Dashboard"),
MultiTenancySides.Host);
dashboard.AddChild(
HangfireDashboardPermissions.Dashboard.ManageJobs,
L("Permission:ManageJobs"),
MultiTenancySides.Host);
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<HangfireDashboardResource>(name);
}
}
}

15
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Authorization/HangfireDashboardPermissions.cs

@ -0,0 +1,15 @@
namespace LINGYUN.Abp.Hangfire.Dashboard.Authorization
{
public static class HangfireDashboardPermissions
{
public const string GroupName = "Hangfire";
public static class Dashboard
{
public const string Default = GroupName + ".Dashboard";
public const string ManageJobs = Default + ".ManageJobs";
// TODO: other pages...
}
}
}

9
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/HangfireDashboardResource.cs

@ -0,0 +1,9 @@
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Hangfire.Dashboard.Localization
{
[LocalizationResourceName("HangfireDashboard")]
public class HangfireDashboardResource
{
}
}

7
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/Resources/en.json

@ -0,0 +1,7 @@
{
"culture": "en",
"texts": {
"Permission:Hangfire": "Hangfire",
"Permission:Dashboard": "Dashboard"
}
}

7
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/LINGYUN/Abp/Hangfire/Dashboard/Localization/Resources/zh-Hans.json

@ -0,0 +1,7 @@
{
"culture": "zh-Hans",
"texts": {
"Permission:Hangfire": "Hangfire",
"Permission:Dashboard": "仪表板"
}
}

12
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/Microsoft/AspNetCore/Builder/ApplicationBuilderAbpHangfireAuthoricationMiddlewareExtension.cs

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder
{
public static class ApplicationBuilderAbpHangfireAuthoricationMiddlewareExtension
{
public static IApplicationBuilder UseHangfireAuthorication(this IApplicationBuilder app)
{
return app.UseMiddleware<HangfireAuthoricationMiddleware>();
}
}
}

27
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Dashboard/Microsoft/AspNetCore/Http/HangfireAuthoricationMiddleware.cs

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Microsoft.AspNetCore.Http
{
public class HangfireAuthoricationMiddleware : IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// 通过 iframe 加载页面的话,需要手动传递 access_token 到参数列表
if (context.Request.Path.StartsWithSegments("/hangfire") &&
context.User.Identity?.IsAuthenticated != true)
{
if (context.Request.Query.TryGetValue("access_token", out var accessTokens))
{
context.Request.Headers.Add("Authorization", accessTokens);
context.Response.Cookies.Append("access_token", accessTokens);
}
else if (context.Request.Cookies.TryGetValue("access_token", out string tokens))
{
context.Request.Headers.Add("Authorization", tokens);
}
}
await next(context);
}
}
}

4
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.MySqlStorage/LINGYUN/Abp/Hangfire/Storage/MySql/AbpHangfireMySqlStorageModule.cs

@ -13,7 +13,7 @@ namespace LINGYUN.Abp.Hangfire.Storage.MySql
{
private MySqlStorage _jobStorage;
public override void ConfigureServices(ServiceConfigurationContext context)
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
@ -30,7 +30,7 @@ namespace LINGYUN.Abp.Hangfire.Storage.MySql
return _jobStorage;
});
context.Services.AddHangfire(config =>
PreConfigure<IGlobalConfiguration>(config =>
{
config.UseStorage(_jobStorage);
});

4
aspnet-core/modules/common/LINGYUN.Abp.Hangfire.Storage.SqlServer/LINGYUN/Abp/Hangfire/Storage/SqlServer/AbpHangfireSqlServerStorageModule.cs

@ -12,7 +12,7 @@ namespace LINGYUN.Abp.Hangfire.Storage.SqlServer
{
private SqlServerStorage _jobStorage;
public override void ConfigureServices(ServiceConfigurationContext context)
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
@ -29,7 +29,7 @@ namespace LINGYUN.Abp.Hangfire.Storage.SqlServer
return _jobStorage;
});
context.Services.AddHangfire(config =>
PreConfigure<IGlobalConfiguration>(config =>
{
config.UseStorage(_jobStorage);
});

8
aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/Class1.cs

@ -0,0 +1,8 @@
using System;
namespace LINGYUN.Abp.Notifications.Workers
{
public class Class1
{
}
}

14
aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/LINGYUN.Abp.Notifications.Workers.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" />
</ItemGroup>
</Project>

10
aspnet-core/modules/common/LINGYUN.Abp.Notifications.Workers/LINGYUN/Abp/Notifications/Workers/AbpNotificationsWorkersModule.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LINGYUN.Abp.Notifications.Workers.LINGYUN.Abp.Notifications.Workers
{
class AbpNotificationsWorkersModule
{
}
}

3
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN.Abp.Notifications.csproj

@ -10,7 +10,8 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.EventBus" Version="4.3.0" />
<PackageReference Include="Volo.Abp.Json" Version="4.3.0" />
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="4.3.0" />
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="4.3.0" />
<PackageReference Include="Volo.Abp.BackgroundWorkers" Version="4.3.0" />
</ItemGroup>
</Project>

5
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/AbpNotificationModule.cs

@ -13,7 +13,8 @@ namespace LINGYUN.Abp.Notifications
{
// TODO: 需要重命名 AbpNotificationsModule
[DependsOn(
typeof(AbpBackgroundJobsModule),
typeof(AbpBackgroundWorkersModule),
typeof(AbpBackgroundJobsAbstractionsModule),
typeof(AbpJsonModule))]
public class AbpNotificationModule : AbpModule
{
@ -23,7 +24,7 @@ namespace LINGYUN.Abp.Notifications
AutoAddDefinitionProviders(context.Services);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpNotificationCleanupOptions>>().Value;
if (options.IsEnabled)

55
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/AbpMessageServiceHttpApiHostModule.cs

@ -8,7 +8,6 @@ using LINGYUN.Abp.ExceptionHandling;
using LINGYUN.Abp.ExceptionHandling.Notifications;
using LINGYUN.Abp.Hangfire.Storage.MySql;
using LINGYUN.Abp.IM.SignalR;
using LINGYUN.Abp.MessageService.Authorization;
using LINGYUN.Abp.MessageService.EntityFrameworkCore;
using LINGYUN.Abp.MessageService.Localization;
using LINGYUN.Abp.MultiTenancy.DbFinder;
@ -27,13 +26,17 @@ using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Volo.Abp;
using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.Autofac;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Caching;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.EntityFrameworkCore;
@ -59,6 +62,9 @@ namespace LINGYUN.Abp.MessageService
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
typeof(AbpHangfireMySqlStorageModule),
typeof(AbpBackgroundJobsHangfireModule),
typeof(AbpBackgroundWorkersModule),
typeof(AbpIMSignalRModule),
typeof(AbpNotificationsSmsModule),
typeof(AbpNotificationsSignalRModule),
@ -66,8 +72,6 @@ namespace LINGYUN.Abp.MessageService
typeof(AbpNotificationsExceptionHandlingModule),
typeof(AbpAspNetCoreSignalRProtocolJsonModule),
typeof(AbpCAPEventBusModule),
typeof(AbpBackgroundJobsHangfireModule),
typeof(AbpHangfireMySqlStorageModule),
typeof(AbpDbFinderMultiTenancyModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpAspNetCoreHttpOverridesModule),
@ -104,6 +108,11 @@ namespace LINGYUN.Abp.MessageService
options.UseMySQL();
});
Configure<AbpAspNetCoreAuditingOptions>(options =>
{
options.IgnoredUrls.AddIfNotContains("/hangfire");
});
// 解决某些不支持类型的序列化
Configure<AbpJsonOptions>(options =>
{
@ -233,25 +242,6 @@ namespace LINGYUN.Abp.MessageService
});
});
Configure<HangfireDashboardRouteOptions>(options =>
{
if (configuration.GetSection("Hangfire:Dashboard:WhiteList").Exists())
{
options.WithWhite(
configuration["Hangfire:Dashboard:WhiteList"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray());
}
options.WithOrigins(
configuration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
);
});
// 支持本地化语言类型
Configure<AbpLocalizationOptions>(options =>
{
@ -263,11 +253,6 @@ namespace LINGYUN.Abp.MessageService
.AddVirtualJson("/Localization/HttpApiHost");
});
Configure<AbpClaimsMapOptions>(options =>
{
options.Maps.TryAdd("name", () => AbpClaimTypes.UserName);
});
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
@ -285,14 +270,6 @@ namespace LINGYUN.Abp.MessageService
}
}
//public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
//{
// var backgroundJobManager = context.ServiceProvider.GetRequiredService<IBackgroundJobManager>();
// // 五分钟执行一次的定时任务
// AsyncHelper.RunSync(async () => await
// backgroundJobManager.EnqueueAsync(CronGenerator.Minute(5), new NotificationCleanupExpritionJobArgs(200)));
//}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
@ -309,7 +286,7 @@ namespace LINGYUN.Abp.MessageService
// 加入自定义中间件
app.UseSignalRJwtToken();
// TODO: 还有没有其他方法在iframe中传递身份令牌?
app.UseHangfireJwtToken();
app.UseHangfireAuthorication();
// 认证
app.UseAuthentication();
// jwt
@ -328,11 +305,7 @@ namespace LINGYUN.Abp.MessageService
// 审计日志
app.UseAuditing();
app.UseHangfireServer();
app.UseHangfireDashboard(options =>
{
options.IgnoreAntiforgeryToken = true;
options.UseAuthorization(new HangfireDashboardAuthorizationFilter());
});
app.UseHangfireDashboard();
// 路由
app.UseConfiguredEndpoints();
}

73
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Authorization/HangfireDashboardAuthorizationFilter.cs

@ -1,73 +0,0 @@
using Hangfire;
using Hangfire.Annotations;
using Hangfire.Dashboard;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUglify.Helpers;
using System.Linq;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.MessageService.Authorization
{
public class HangfireDashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
protected string[] AllowGrantPath { get; }
public HangfireDashboardAuthorizationFilter()
{
AllowGrantPath = new string[] { "/css", "/js", "/fonts", "/stats" };
}
public bool Authorize([NotNull] DashboardContext context)
{
// 放行路径
if (AllowGrantPath.Contains(context.Request.Path))
{
return true;
}
var httpContext = context.GetHttpContext();
var options = httpContext.RequestServices.GetService<IOptions<HangfireDashboardRouteOptions>>()?.Value;
if (options != null)
{
// 白名单检查
if (!context.Request.RemoteIpAddress.IsNullOrWhiteSpace()
&& options.IpAllow(context.Request.RemoteIpAddress))
{
return true;
}
// 请求路径对应的权限检查
// TODO: 怎么来传递用户身份令牌?
var permission = options.GetPermission(context.Request.Path);
if (!permission.IsNullOrWhiteSpace())
{
var permissionChecker = httpContext.RequestServices.GetRequiredService<IPermissionChecker>();
return AsyncHelper.RunSync(async () => await permissionChecker.IsGrantedAsync(permission));
}
}
return false;
}
public override int GetHashCode()
{
// 类型相同就行了
return GetType().FullName.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
// 类型相同就行了
if (GetType().Equals(obj.GetType()))
{
return true;
}
return base.Equals(obj);
}
}
}

38
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/BackgroundJobs/NotificationCleanupExpritionJob.cs

@ -1,38 +0,0 @@
using LINGYUN.Abp.Notifications;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.MessageService.BackgroundJobs
{
internal class NotificationCleanupExpritionJob : AsyncBackgroundJob<NotificationCleanupExpritionJobArgs>, ITransientDependency
{
protected INotificationStore Store { get; }
protected IServiceProvider ServiceProvider { get; }
public NotificationCleanupExpritionJob(
INotificationStore store,
IServiceProvider serviceProvider)
{
Store = store;
ServiceProvider = serviceProvider;
}
public override async Task ExecuteAsync(NotificationCleanupExpritionJobArgs args)
{
try
{
Logger.LogDebug("Before cleanup exprition jobs...");
await Store.DeleteNotificationAsync(args.Count);
Logger.LogDebug("Exprition jobs cleanup job was successful...");
}
catch (Exception ex)
{
Logger.LogWarning("Exprition jobs cleanup job was failed...");
Logger.LogWarning("Error:{0}", ex.Message);
}
}
}
}

23
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/BackgroundJobs/NotificationCleanupExpritionJobArgs.cs

@ -1,23 +0,0 @@
using Volo.Abp.BackgroundJobs;
namespace LINGYUN.Abp.MessageService.BackgroundJobs
{
[BackgroundJobName("定时清理过期通知消息任务")]
internal class NotificationCleanupExpritionJobArgs
{
/// <summary>
/// 清理大小
/// </summary>
public int Count { get; set; }
public NotificationCleanupExpritionJobArgs()
{
}
public NotificationCleanupExpritionJobArgs(int count = 200)
{
Count = count;
}
}
}

74
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/DashboardOptionsExtensions.cs

@ -1,74 +0,0 @@
using Hangfire.Dashboard;
using JetBrains.Annotations;
using System.Collections.Generic;
using Volo.Abp;
namespace Hangfire
{
public static class DashboardOptionsExtensions
{
public static DashboardOptions AddAuthorization(
[NotNull] this DashboardOptions options,
[NotNull] IDashboardAuthorizationFilter authorizationFilter)
{
Check.NotNull(options, nameof(options));
Check.NotNull(authorizationFilter, nameof(authorizationFilter));
List<IDashboardAuthorizationFilter> filters = new List<IDashboardAuthorizationFilter>();
filters.AddRange(options.Authorization);
filters.AddIfNotContains(authorizationFilter);
options.Authorization = filters;
return options;
}
public static DashboardOptions AddAuthorizations(
[NotNull] this DashboardOptions options,
[NotNull] IEnumerable<IDashboardAuthorizationFilter> authorizationFilters)
{
Check.NotNull(options, nameof(options));
Check.NotNull(authorizationFilters, nameof(authorizationFilters));
List<IDashboardAuthorizationFilter> filters = new List<IDashboardAuthorizationFilter>();
filters.AddRange(options.Authorization);
filters.AddIfNotContains(authorizationFilters);
options.Authorization = filters;
return options;
}
public static DashboardOptions UseAuthorization(
[NotNull] this DashboardOptions options,
[NotNull] IDashboardAuthorizationFilter authorizationFilter)
{
Check.NotNull(options, nameof(options));
Check.NotNull(authorizationFilter, nameof(authorizationFilter));
List<IDashboardAuthorizationFilter> filters = new List<IDashboardAuthorizationFilter>
{
authorizationFilter
};
options.Authorization = filters;
return options;
}
public static DashboardOptions UseAuthorizations(
[NotNull] this DashboardOptions options,
[NotNull] IEnumerable<IDashboardAuthorizationFilter> authorizationFilters)
{
Check.NotNull(options, nameof(options));
Check.NotNull(authorizationFilters, nameof(authorizationFilters));
List<IDashboardAuthorizationFilter> filters = new List<IDashboardAuthorizationFilter>();
filters.AddRange(authorizationFilters);
options.Authorization = filters;
return options;
}
}
}

47
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireApplicationBuilderExtensions.cs

@ -1,47 +0,0 @@
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using System;
using Volo.Abp;
namespace Hangfire
{
public static class HangfireApplicationBuilderExtensions
{
public static IApplicationBuilder UseHangfireJwtToken(
[NotNull] this IApplicationBuilder app)
{
return app.UseMiddleware<HangfireJwtTokenMiddleware>();
}
public static IApplicationBuilder UseHangfireDashboard(
[NotNull] this IApplicationBuilder app,
[CanBeNull] Action<DashboardOptions> setup = null)
{
Check.NotNull(app, nameof(app));
return app.UseHangfireDashboard("/hangfire", setup, null);
}
public static IApplicationBuilder UseHangfireDashboard(
[NotNull] this IApplicationBuilder app,
[CanBeNull] string pathMatch = "/hangfire",
[CanBeNull] Action<DashboardOptions> setup = null)
{
Check.NotNull(app, nameof(app));
return app.UseHangfireDashboard(pathMatch, setup, null);
}
public static IApplicationBuilder UseHangfireDashboard(
[NotNull] this IApplicationBuilder app,
[CanBeNull] string pathMatch = "/hangfire",
[CanBeNull] Action<DashboardOptions> setup = null,
[CanBeNull] JobStorage storage = null)
{
Check.NotNull(app, nameof(app));
var options = new DashboardOptions();
setup?.Invoke(options);
return app.UseHangfireDashboard(pathMatch: pathMatch, options: options, storage: storage);
}
}
}

74
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireDashboardRouteOptions.cs

@ -1,74 +0,0 @@
using LINGYUN.Abp.MessageService.Permissions;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Hangfire
{
public class HangfireDashboardRouteOptions
{
public IList<string> AllowFrameOrigins { get; }
/// <summary>
/// 白名单
/// 添加网关地址
/// </summary>
public IList<string> WhiteList { get; }
public IDictionary<string, string> RoutePermissions { get; }
public HangfireDashboardRouteOptions()
{
WhiteList = new List<string>();
AllowFrameOrigins = new List<string>();
RoutePermissions = new Dictionary<string, string>();
InitDefaultRoutes();
WithWhite("127.0.0.1");
WithWhite("::1");
}
public bool IpAllow(string ipaddress)
{
return WhiteList.Any(ip => ip == ipaddress);
}
public void WithWhite(params string[] wgites)
{
WhiteList.AddIfNotContains(wgites);
}
public void WithOrigins(params string[] origins)
{
AllowFrameOrigins.AddIfNotContains(origins);
}
public void WithPermission(string route, string permission)
{
RoutePermissions.Add(route, permission);
}
public string GetPermission(string route)
{
var permission = RoutePermissions
.Where(x => x.Key.StartsWith(route))
.Select(x => x.Value)
.FirstOrDefault();
return permission;
}
private void InitDefaultRoutes()
{
WithPermission("/hangfire", MessageServicePermissions.Hangfire.Default);
WithPermission("/stats", MessageServicePermissions.Hangfire.Default);
WithPermission("/servers", MessageServicePermissions.Hangfire.Default);
WithPermission("/retries", MessageServicePermissions.Hangfire.Default);
WithPermission("/recurring", MessageServicePermissions.Hangfire.Default);
WithPermission("/jobs/enqueued", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/processing", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/scheduled", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/failed", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/deleted", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/awaiting", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/actions", MessageServicePermissions.Hangfire.ManageQueue);
WithPermission("/jobs/details", MessageServicePermissions.Hangfire.ManageQueue);
}
}
}

31
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/Hangfire/HangfireJwtTokenMiddleware.cs

@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Hangfire
{
public class HangfireJwtTokenMiddleware : IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// 通过 iframe 加载页面的话,需要手动传递 access_token 到参数列表
if (context.Request.Path.StartsWithSegments("/hangfire") && context.User.Identity?.IsAuthenticated != true)
{
if (context.Request.Query.TryGetValue("access_token", out var accessTokens))
{
context.Request.Headers.Add("Authorization", accessTokens);
}
var options = context.RequestServices.GetService<IOptions<HangfireDashboardRouteOptions>>()?.Value;
if (options != null && options.AllowFrameOrigins.Count > 0)
{
// 跨域 iframe
context.Response.Headers.TryAdd("X-Frame-Options", $"\"ALLOW-FROM {options.AllowFrameOrigins.JoinAsString(",")}\"");
}
}
await next(context);
}
}
}

1
aspnet-core/services/messages/LINGYUN.Abp.MessageService.HttpApi.Host/LINGYUN.Abp.MessageService.HttpApi.Host.csproj

@ -45,6 +45,7 @@
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.AspNetCore.SignalR.Protocol.Json\LINGYUN.Abp.AspNetCore.SignalR.Protocol.Json.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.ExceptionHandling.Notifications\LINGYUN.Abp.ExceptionHandling.Notifications.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.Hangfire.Dashboard\LINGYUN.Abp.Hangfire.Dashboard.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.Hangfire.MySqlStorage\LINGYUN.Abp.Hangfire.Storage.MySql.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.BackgroundJobs.Hangfire\LINGYUN.Abp.BackgroundJobs.Hangfire.csproj" />
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />

Loading…
Cancel
Save