From 27b5c69d127baf86414bfce52f7812d90d7adf8c Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Tue, 11 Jan 2022 19:40:58 +0800 Subject: [PATCH 1/2] fix(tasks): the lock task does not block the listener from running --- .../Abp/BackgroundTasks/IJobLockProvider.cs | 19 ++++++ .../Quartz/AbpBackgroundTasksQuartzModule.cs | 2 + .../Quartz/QuartzJobExecutorProvider.cs | 10 +-- .../Quartz/QuartzJobListener.cs | 22 +++++-- .../Quartz/QuartzTriggerListener.cs | 56 +++++++++++++++++ .../LINGYUN.Abp.BackgroundTasks.csproj | 1 + .../Abp/BackgroundTasks/JobLockProvider.cs | 61 +++++++++++++++++++ .../BackgroundTasks/JobRunnableExecuter.cs | 30 +-------- .../BackgroundJobInfoAppService.cs | 6 +- ...Service.TaskManagement.HttpApi.Host.csproj | 1 + .../TaskManagementHttpApiHostModule.cs | 2 + 11 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobLockProvider.cs create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzTriggerListener.cs create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobLockProvider.cs diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobLockProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobLockProvider.cs new file mode 100644 index 000000000..0d5979633 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobLockProvider.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BackgroundTasks; + +/// +/// 作业锁定提供者 +/// +public interface IJobLockProvider +{ + Task TryLockAsync( + string jobKey, + int lockSeconds, + CancellationToken cancellationToken = default); + + Task TryReleaseAsync( + string jobKey, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs index 983823e05..788825614 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs @@ -15,5 +15,7 @@ public class AbpBackgroundTasksQuartzModule : AbpModule var _scheduler = context.ServiceProvider.GetRequiredService(); _scheduler.ListenerManager.AddJobListener(context.ServiceProvider.GetRequiredService()); + _scheduler.ListenerManager.AddTriggerListener(context.ServiceProvider.GetRequiredService()); + } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs index a1070ddb3..c20d3015d 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs @@ -36,10 +36,12 @@ public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonD } var adapterType = typeof(QuartzJobSimpleAdapter<>); - if (job.LockTimeOut > 0) - { - adapterType = typeof(QuartzJobConcurrentAdapter<>); - } + + // 注释, 通过触发器监听锁定 + //if (job.LockTimeOut > 0) + //{ + // adapterType = typeof(QuartzJobConcurrentAdapter<>); + //} if (!typeof(IJob).IsAssignableFrom(jobType)) { diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs index b90cb54b6..6ed7e81c8 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs @@ -4,11 +4,12 @@ using Microsoft.Extensions.Logging.Abstractions; using Quartz; using Quartz.Listener; using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; -using Volo.Abp.Uow; +using Volo.Abp.Timing; namespace LINGYUN.Abp.BackgroundTasks.Quartz; @@ -18,13 +19,16 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency public override string Name => "QuartzJobListener"; + protected IClock Clock { get; } protected IJobEventProvider EventProvider { get; } protected IServiceProvider ServiceProvider { get; } public QuartzJobListener( + IClock clock, IServiceProvider serviceProvider, IJobEventProvider eventProvider) { + Clock = clock; ServiceProvider = serviceProvider; EventProvider = eventProvider; @@ -50,6 +54,12 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency { try { + var jobEventList = EventProvider.GetAll(); + if (!jobEventList.Any()) + { + return; + } + using var scope = ServiceProvider.CreateScope(); var jobEventData = new JobEventData( jobUUId, @@ -60,7 +70,6 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency Result = context.Result?.ToString() }; - var jobEventList = EventProvider.GetAll(); var eventContext = new JobEventContext( scope.ServiceProvider, jobEventData); @@ -86,6 +95,12 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency { try { + var jobEventList = EventProvider.GetAll(); + if (!jobEventList.Any()) + { + return; + } + using var scope = ServiceProvider.CreateScope(); var jobId = context.GetString(nameof(JobInfo.Id)); if (Guid.TryParse(jobId, out var jobUUId)) @@ -112,7 +127,7 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency jobEventData.RepeatCount = simpleTrigger.RepeatCount; } jobEventData.Description = context.JobDetail.Description; - jobEventData.RunTime = context.FireTimeUtc.LocalDateTime; + jobEventData.RunTime = Clock.Now; jobEventData.LastRunTime = context.PreviousFireTimeUtc?.LocalDateTime; jobEventData.NextRunTime = context.NextFireTimeUtc?.LocalDateTime; if (context.Result != null) @@ -125,7 +140,6 @@ public class QuartzJobListener : JobListenerSupport, ISingletonDependency jobEventData.TenantId = tenantId; } - var jobEventList = EventProvider.GetAll(); var eventContext = new JobEventContext( scope.ServiceProvider, jobEventData); diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzTriggerListener.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzTriggerListener.cs new file mode 100644 index 000000000..6b4182979 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzTriggerListener.cs @@ -0,0 +1,56 @@ +using Quartz; +using Quartz.Listener; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.BackgroundTasks.Quartz; + +public class QuartzTriggerListener : TriggerListenerSupport, ISingletonDependency +{ + protected const string LockKeyFormat = "p:abp-background-tasks,job:{0},key:{1}"; + + public override string Name => "QuartzTriggerListener"; + + protected IJobLockProvider JobLockProvider { get; } + + public QuartzTriggerListener( + IJobLockProvider jobLockProvider) + { + JobLockProvider = jobLockProvider; + } + + public override async Task VetoJobExecution( + ITrigger trigger, + IJobExecutionContext context, + CancellationToken cancellationToken = default) + { + context.MergedJobDataMap.TryGetValue(nameof(JobInfo.Id), out var jobId); + context.MergedJobDataMap.TryGetValue(nameof(JobInfo.LockTimeOut), out var lockTime); + if (jobId != null && lockTime != null && lockTime is int time && time > 0) + { + + return !await JobLockProvider.TryLockAsync(NormalizeKey(context, jobId), time, cancellationToken); + } + + return false; + } + + public override async Task TriggerComplete( + ITrigger trigger, + IJobExecutionContext context, + SchedulerInstruction triggerInstructionCode, + CancellationToken cancellationToken = default) + { + if (context.MergedJobDataMap.TryGetValue(nameof(JobInfo.Id), out var jobId) && + context.MergedJobDataMap.ContainsKey(nameof(JobInfo.LockTimeOut))) + { + await JobLockProvider.TryReleaseAsync(NormalizeKey(context, jobId), cancellationToken); + } + } + + protected virtual string NormalizeKey(IJobExecutionContext context, object jobId) + { + return string.Format(LockKeyFormat, context.JobDetail.JobType.Name, jobId.ToString()); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj index 14206d3f2..b49f1e324 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj @@ -11,6 +11,7 @@ + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobLockProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobLockProvider.cs new file mode 100644 index 000000000..85f286de8 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobLockProvider.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; + +namespace LINGYUN.Abp.BackgroundTasks; + +[Dependency(ReplaceServices = true)] +public class JobLockProvider : IJobLockProvider, ISingletonDependency +{ + protected IMemoryCache LockCache { get; } + protected IAbpDistributedLock DistributedLock { get; } + + public JobLockProvider( + IMemoryCache lockCache, + IAbpDistributedLock distributedLock) + { + LockCache = lockCache; + DistributedLock = distributedLock; + } + + public virtual async Task TryLockAsync(string jobKey, int lockSeconds, CancellationToken cancellationToken = default) + { + var handle = await DistributedLock.TryAcquireAsync(jobKey, cancellationToken: cancellationToken); + if (handle != null) + { + await LockCache.GetOrCreateAsync(jobKey, (entry) => + { + entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(lockSeconds)); + entry.RegisterPostEvictionCallback(async (key, value, reason, state) => + { + if (reason == EvictionReason.Expired && value is IAbpDistributedLockHandle handleValue) + { + await handleValue.DisposeAsync(); + } + }); + entry.SetValue(handle); + + return Task.FromResult(handle); + }); + + return true; + } + return false; + } + + public virtual async Task TryReleaseAsync(string jobKey, CancellationToken cancellationToken = default) + { + if (LockCache.TryGetValue(jobKey, out var handle)) + { + await handle.DisposeAsync(); + + LockCache.Remove(jobKey); + + return true; + } + return false; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs index 042f681fa..9e92ded39 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs @@ -1,17 +1,13 @@ using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; using Volo.Abp.MultiTenancy; namespace LINGYUN.Abp.BackgroundTasks; public class JobRunnableExecuter : IJobRunnableExecuter, ISingletonDependency { - protected const string LockKeyFormat = "p:{0},job:{1},key:{2}"; - public async virtual Task ExecuteAsync(JobRunnableContext context) { Guid? tenantId = null; @@ -24,31 +20,7 @@ public class JobRunnableExecuter : IJobRunnableExecuter, ISingletonDependency var currentTenant = context.ServiceProvider.GetRequiredService(); using (currentTenant.Change(tenantId)) { - context.JobData.TryGetValue(nameof(JobInfo.LockTimeOut), out var lockTime); - - // 某些提供者如果无法保证锁一致性, 那么需要用分布式锁 - if (lockTime != null && (lockTime is int time && time > 0)) - { - var jobId = context.JobData.GetOrDefault(nameof(JobInfo.Id)); - var jobLockKey = string.Format(LockKeyFormat, tenantId?.ToString() ?? "Default", context.JobType.Name, jobId); - var distributedLock = context.ServiceProvider.GetRequiredService(); - - var handle = await distributedLock.TryAcquireAsync(jobLockKey, TimeSpan.FromSeconds(time)); - if (handle == null) - { - // 抛出异常 通过监听器使其重试 - throw new AbpBackgroundTaskConcurrentException(context.JobType); - } - - await using (handle) - { - await InternalExecuteAsync(context); - } - } - else - { - await InternalExecuteAsync(context); - } + await InternalExecuteAsync(context); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs index 4ded0bf2a..c9ee39774 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs @@ -156,8 +156,10 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa backgroundJobInfo.MaxCount = input.MaxCount; backgroundJobInfo.MaxTryCount = input.MaxTryCount; - backgroundJobInfo.Args.RemoveAll(x => !input.Args.ContainsKey(x.Key)); - backgroundJobInfo.Args.AddIfNotContains(input.Args); + foreach (var arg in input.Args) + { + backgroundJobInfo.Args[arg.Key] = arg.Value; + } backgroundJobInfo.SetPriority(input.Priority); switch (input.JobType) diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj index e64223cd1..3ce7471c7 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj @@ -38,6 +38,7 @@ + diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs index 2ea812a44..44bd38542 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs @@ -22,6 +22,7 @@ using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.DistributedLocking; using Volo.Abp.EntityFrameworkCore.MySQL; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Http.Client.IdentityModel.Web; @@ -38,6 +39,7 @@ namespace LY.MicroService.TaskManagement; typeof(AbpSerilogEnrichersUniqueIdModule), typeof(AbpAuditLoggingElasticsearchModule), typeof(AbpAspNetCoreSerilogModule), + typeof(AbpDistributedLockingModule), typeof(AbpEntityFrameworkCoreMySQLModule), typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpEmailingExceptionHandlingModule), From 6017f03a5ebf4ad2fe42043c67e6f96344c45ca3 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Wed, 12 Jan 2022 11:54:29 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(tasks):=20=E5=AE=8C=E5=96=84=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E5=BC=82=E5=B8=B8=E6=8E=A8=E9=80=81.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JobRunnableContextExtensions.cs | 2 +- ...p.BackgroundTasks.ExceptionHandling.csproj | 10 ++ ...pBackgroundTasksExceptionHandlingModule.cs | 16 +++ .../ExceptionHandling/JobExceptionNotifier.cs | 109 +++++++++++------- .../Localization/Resources/en.json | 15 +++ .../Localization/Resources/zh-Hans.json | 15 +++ .../Abp/BackgroundTasks/Jobs/ConsoleJob.cs | 2 +- .../Abp/BackgroundTasks/Jobs/SendEmailJob.cs | 30 ++++- .../Quartz/QuartzJobScheduler.cs | 7 +- .../AbpBackgroundTasksModule.cs | 16 +++ .../Internal/JobExecutedEvent.cs | 2 +- .../Localization/BackgroundTasksResource.cs | 8 ++ .../Localization/Resources/en.json | 5 + .../Localization/Resources/zh-Hans.json | 5 + .../TaskManagement/BackgroundJobManager.cs | 9 +- 15 files changed, 198 insertions(+), 53 deletions(-) create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/en.json create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/BackgroundTasksResource.cs create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/en.json create mode 100644 aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/zh-Hans.json diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs index c87d46bc5..bb0139e9d 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs @@ -71,7 +71,7 @@ public static class JobRunnableContextExtensions { return value; } - throw new ArgumentException($"Job required data {key} not specified."); + throw new ArgumentException($"Job required data [{key}] not specified."); } public static bool TryGetJobData(this JobRunnableContext context, string key, out object value) diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN.Abp.BackgroundTasks.ExceptionHandling.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN.Abp.BackgroundTasks.ExceptionHandling.csproj index 74c4f48de..a5dd97fab 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN.Abp.BackgroundTasks.ExceptionHandling.csproj +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN.Abp.BackgroundTasks.ExceptionHandling.csproj @@ -8,6 +8,16 @@ + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/AbpBackgroundTasksExceptionHandlingModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/AbpBackgroundTasksExceptionHandlingModule.cs index 0f11234c3..705a36a01 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/AbpBackgroundTasksExceptionHandlingModule.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/AbpBackgroundTasksExceptionHandlingModule.cs @@ -1,5 +1,8 @@ using LINGYUN.Abp.BackgroundTasks.Jobs; +using LINGYUN.Abp.BackgroundTasks.Localization; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; @@ -7,5 +10,18 @@ namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; [DependsOn(typeof(AbpBackgroundTasksJobsModule))] public class AbpBackgroundTasksExceptionHandlingModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources"); + }); + } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/JobExceptionNotifier.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/JobExceptionNotifier.cs index 1dda90249..e8f4927fc 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/JobExceptionNotifier.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/JobExceptionNotifier.cs @@ -1,10 +1,16 @@ using JetBrains.Annotations; using LINGYUN.Abp.BackgroundTasks.Jobs; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Emailing; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TextTemplating; using Volo.Abp.Timing; namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; @@ -13,64 +19,89 @@ namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; public class JobExceptionNotifier : IJobExceptionNotifier, ITransientDependency { public const string Prefix = "exception."; + public const string JobGroup = "ExceptionNotifier"; + + public ILogger Logger { protected get; set; } protected IClock Clock { get; } - protected IJobStore JobStore { get; } - protected IJobScheduler JobScheduler { get; } + protected IEmailSender EmailSender { get; } + protected ITemplateRenderer TemplateRenderer { get; } public JobExceptionNotifier( IClock clock, - IJobStore jobStore, - IJobScheduler jobScheduler) + IEmailSender emailSender, + ITemplateRenderer templateRenderer) { Clock = clock; - JobStore = jobStore; - JobScheduler = jobScheduler; + EmailSender = emailSender; + TemplateRenderer = templateRenderer; + + Logger = NullLogger.Instance; } public virtual async Task NotifyAsync([NotNull] JobExceptionNotificationContext context) { + // 异常所属分组不处理, 防止死循环 + if (string.Equals(context.JobInfo.Group, JobGroup)) + { + Logger.LogWarning($"There is a problem executing the job, reason: {context.Exception.Message}"); + return; + } var notifyKey = Prefix + SendEmailJob.PropertyTo; - if (context.JobInfo.Args.TryGetValue(notifyKey, out var exceptionTo)) + if (context.JobInfo.Args.TryGetValue(notifyKey, out var exceptionTo) && + exceptionTo is string to) { - var template = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyTemplate) ?? ""; - var subject = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertySubject) ?? "From job execute exception"; - var globalContext = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyContext) ?? "{}"; - var from = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyFrom) ?? ""; - var culture = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyCulture) ?? CultureInfo.CurrentCulture.Name; + var template = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyTemplate)?.ToString() ?? ""; + var content = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyBody)?.ToString() ?? ""; + var subject = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertySubject)?.ToString() ?? "From job execute exception"; + var from = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyFrom)?.ToString() ?? ""; + var errorMessage = context.Exception.GetBaseException().Message; - var jobId = Guid.NewGuid(); - var jobArgs = new Dictionary + if (template.IsNullOrWhiteSpace()) { - { SendEmailJob.PropertyTo, exceptionTo.ToString() }, - { SendEmailJob.PropertySubject, subject }, - { SendEmailJob.PropertyBody, context.Exception.GetBaseException().Message }, - { SendEmailJob.PropertyTemplate, template }, - { SendEmailJob.PropertyContext, globalContext }, - { SendEmailJob.PropertyFrom, from }, - { SendEmailJob.PropertyCulture, culture } - }; - var jobInfo = new JobInfo + await EmailSender.SendAsync(from, to, subject, content, false); + return; + } + + var footer = context.JobInfo.Args.GetOrDefault("footer")?.ToString() ?? $"Copyright to LY Colin © {Clock.Now.Year}"; + var model = new { - Id = jobId, - Name = jobId.ToString(), - Group = "ExceptionHandling", - Priority = JobPriority.Normal, - BeginTime = Clock.Now, - Args = jobArgs, - Description = subject.ToString(), - JobType = JobType.Once, - Interval = 5, - MaxCount = 1, - MaxTryCount = 1, - CreationTime = Clock.Now, - Status = JobStatus.None, - Type = DefaultJobNames.SendEmailJob, + Title = subject, + Group = context.JobInfo.Group, + Name = context.JobInfo.Name, + Id = context.JobInfo.Id, + Type = context.JobInfo.Type, + Triggertime = Clock.Now.ToString("yyyy-MM-dd HH:mm:ss"), + Message = errorMessage, + Tenantname = context.JobInfo.Args.GetOrDefault(nameof(IMultiTenant.TenantId)), + Footer = footer, }; - await JobStore.StoreAsync(jobInfo); + var globalContext = new Dictionary(); + if (context.JobInfo.Args.TryGetValue(Prefix + SendEmailJob.PropertyContext, out var ctx) && + ctx is string ctxStr && !ctxStr.IsNullOrWhiteSpace()) + { + try + { + globalContext = JsonConvert.DeserializeObject>(ctxStr); + } + catch { } + } + + var culture = context.JobInfo.Args.GetOrDefault(Prefix + SendEmailJob.PropertyCulture)?.ToString() ?? CultureInfo.CurrentCulture.Name; - await JobScheduler.TriggerAsync(jobInfo); + content = await TemplateRenderer.RenderAsync( + templateName: template, + model: model, + cultureName: culture, + globalContext: globalContext); + + if (from.IsNullOrWhiteSpace()) + { + await EmailSender.SendAsync(to, subject, content, true); + return; + } + await EmailSender.SendAsync(from, to, subject, content, true); } } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/en.json b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/en.json new file mode 100644 index 000000000..15dff07ad --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/en.json @@ -0,0 +1,15 @@ +{ + "culture": "en", + "texts": { + "TextTemplate:JobExceptionNotifier": "Background job exception pushes template", + "JobExceptionNotifier": "Background job exception push", + "TenantName": "TenantName", + "JobGroup": "Job Group", + "JobName": "Job Name", + "JobId": "Job Id", + "JobType": "Job Type", + "TriggerTime": "Trigger Time", + "ErrorMessage": "Error Message", + "JobExecuteError": "Background job exception" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/zh-Hans.json b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..4975448b6 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.ExceptionHandling/LINGYUN/Abp/BackgroundTasks/ExceptionHandling/Localization/Resources/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "culture": "zh-Hans", + "texts": { + "TextTemplate:JobExceptionNotifier": "后台作业异常推送模板", + "JobExceptionNotifier": "后台作业异常推送", + "TenantName": "租户名称", + "JobGroup": "作业分组", + "JobName": "作业名称", + "JobId": "作业标识", + "JobType": "作业类型", + "TriggerTime": "触发时间", + "ErrorMessage": "错误消息", + "JobExecuteError": "后台作业异常" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs index 88320073d..abc1d747f 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs @@ -9,7 +9,7 @@ public class ConsoleJob : IJobRunnable public Task ExecuteAsync(JobRunnableContext context) { context.TryGetString(PropertyMessage, out var message); - Console.WriteLine($"This message: {message ?? "None"} comes from the job: {GetType()}"); + Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}] - This message: {message ?? "None"} comes from the job: {GetType()}"); return Task.CompletedTask; } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs index 0864919bc..022429be5 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs @@ -4,6 +4,7 @@ using Volo.Abp.TextTemplating; using Volo.Abp.Json; using System.Collections.Generic; using System; +using Newtonsoft.Json; namespace LINGYUN.Abp.BackgroundTasks.Jobs; @@ -28,6 +29,10 @@ public class SendEmailJob : IJobRunnable /// public const string PropertyTemplate = "template"; /// + /// 可选, 模板消息中的模型参数 + /// + public const string PropertyModel = "model"; + /// /// 可选, 模板消息中的上下文参数 /// public const string PropertyContext = "context"; @@ -58,19 +63,40 @@ public class SendEmailJob : IJobRunnable catch { } } + object model = null; + if (context.TryGetString(PropertyModel, out var modelString) && !modelString.IsNullOrWhiteSpace()) + { + try + { + model = JsonConvert.DeserializeObject(modelString); + } + catch { } + } + var templateRenderer = context.GetRequiredService(); var content = await templateRenderer.RenderAsync( templateName: template, + model: model, cultureName: culture, globalContext: globalContext); - await emailSender.QueueAsync(from, to, subject, content, true); + await QueueEmail(emailSender, from, to, subject, content, true); return; } var body = context.GetString(PropertyBody); - await emailSender.QueueAsync(from, to, subject, body, false); + await QueueEmail(emailSender, from, to, subject, body, false); + } + + private async Task QueueEmail(IEmailSender emailSender, string from, string to, string subject, string body, bool isBodyHtml = true) + { + if (from.IsNullOrWhiteSpace()) + { + await emailSender.SendAsync(to, subject, body, isBodyHtml); + return; + } + await emailSender.SendAsync(from, to, subject, body, isBodyHtml); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs index af3be1c45..d6d5dc5d0 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs @@ -154,10 +154,11 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency var jobKey = new JobKey(job.Name, job.Group); if (!await Scheduler.CheckExists(jobKey)) { - job.JobType = JobType.Once; - await QueueAsync(job); } - await Scheduler.TriggerJob(jobKey); + else + { + await Scheduler.TriggerJob(jobKey); + } } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs index 94c50a110..644cf3c30 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs @@ -1,14 +1,18 @@ using LINGYUN.Abp.BackgroundTasks.Internal; +using LINGYUN.Abp.BackgroundTasks.Localization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Auditing; using Volo.Abp.BackgroundJobs; using Volo.Abp.DistributedLocking; using Volo.Abp.Guids; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace LINGYUN.Abp.BackgroundTasks; [DependsOn(typeof(AbpAuditingModule))] +[DependsOn(typeof(AbpLocalizationModule))] [DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] [DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))] [DependsOn(typeof(AbpDistributedLockingAbstractionsModule))] @@ -19,5 +23,17 @@ public class AbpBackgroundTasksModule : AbpModule { context.Services.AddTransient(typeof(BackgroundJobAdapter<>)); context.Services.AddHostedService(); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/LINGYUN/Abp/BackgroundTasks/Localization/Resources"); + }); } } diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs index 922eb911c..8fd996c10 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs @@ -47,7 +47,7 @@ public class JobExecutedEvent : JobEventBase, ITransientDepend job.Priority = JobPriority.Low; } - if (job.TryCount > job.MaxTryCount) + if (job.TryCount >= job.MaxTryCount) { job.Status = JobStatus.Stopped; job.IsAbandoned = true; diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/BackgroundTasksResource.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/BackgroundTasksResource.cs new file mode 100644 index 000000000..f75030a1e --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/BackgroundTasksResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.BackgroundTasks.Localization; + +[LocalizationResourceName("BackgroundTasks")] +public class BackgroundTasksResource +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/en.json b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/en.json new file mode 100644 index 000000000..a9c8dcc3f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/en.json @@ -0,0 +1,5 @@ +{ + "culture": "en", + "texts": { + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/zh-Hans.json b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..c5ad81326 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Localization/Resources/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "culture": "zh-Hans", + "texts": { + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs index fbf5b541f..fcede54a9 100644 --- a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs @@ -92,12 +92,9 @@ public class BackgroundJobManager : DomainService public virtual async Task TriggerAsync(BackgroundJobInfo jobInfo) { var job = ObjectMapper.Map(jobInfo); - //if (!await JobScheduler.ExistsAsync(job)) - //{ - // throw new BusinessException(TaskManagementErrorCodes.JobNotFoundInQueue) - // .WithData("Group", job.Group) - // .WithData("Name", job.Name); - //} + job.JobType = JobType.Once; + // 延迟两秒触发 + job.Interval = 2; await JobScheduler.TriggerAsync(job); }