committed by
GitHub
16 changed files with 269 additions and 8 deletions
@ -0,0 +1,9 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public interface IJobExceptionNotifier |
||||
|
{ |
||||
|
Task NotifyAsync([NotNull] JobExceptionNotificationContext context); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobExceptionNotificationContext |
||||
|
{ |
||||
|
public JobInfo JobInfo { get; } |
||||
|
public Exception Exception { get; } |
||||
|
public JobExceptionNotificationContext( |
||||
|
JobInfo jobInfo, |
||||
|
Exception exception) |
||||
|
{ |
||||
|
JobInfo = jobInfo; |
||||
|
Exception = exception; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
[Dependency(TryRegister = true)] |
||||
|
public class NullJobExceptionNotifier : IJobExceptionNotifier, ISingletonDependency |
||||
|
{ |
||||
|
public Task NotifyAsync([NotNull] JobExceptionNotificationContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Timing" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\common\LINGYUN.Abp.ExceptionHandling\LINGYUN.Abp.ExceptionHandling.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using LINGYUN.Abp.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; |
||||
|
|
||||
|
[DependsOn(typeof(AbpExceptionHandlingModule))] |
||||
|
public class AbpBackgroundTasksExceptionHandlingModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.ExceptionHandling; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class JobExceptionNotifier : IJobExceptionNotifier, ITransientDependency |
||||
|
{ |
||||
|
public const string Prefix = "exception."; |
||||
|
|
||||
|
protected IClock Clock { get; } |
||||
|
protected IJobStore JobStore { get; } |
||||
|
protected IJobScheduler JobScheduler { get; } |
||||
|
|
||||
|
public JobExceptionNotifier( |
||||
|
IClock clock, |
||||
|
IJobStore jobStore, |
||||
|
IJobScheduler jobScheduler) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
JobStore = jobStore; |
||||
|
JobScheduler = jobScheduler; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task NotifyAsync([NotNull] JobExceptionNotificationContext context) |
||||
|
{ |
||||
|
var notifyKey = Prefix + SendEmailJob.PropertyTo; |
||||
|
if (context.JobInfo.Args.TryGetValue(notifyKey, out var exceptionTo)) |
||||
|
{ |
||||
|
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 jobId = Guid.NewGuid(); |
||||
|
var jobArgs = new Dictionary<string, object> |
||||
|
{ |
||||
|
{ 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 |
||||
|
{ |
||||
|
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, |
||||
|
}; |
||||
|
|
||||
|
await JobStore.StoreAsync(jobInfo); |
||||
|
|
||||
|
await JobScheduler.TriggerAsync(jobInfo); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,3 +1,3 @@ |
|||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
<ConfigureAwait /> |
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
</Weavers> |
</Weavers> |
||||
@ -1,26 +1,76 @@ |
|||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Volo.Abp.Emailing; |
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.TextTemplating; |
||||
|
using Volo.Abp.Json; |
||||
|
using System.Collections.Generic; |
||||
|
using System; |
||||
|
|
||||
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
public class SendEmailJob : IJobRunnable |
public class SendEmailJob : IJobRunnable |
||||
{ |
{ |
||||
public const string PropertyFrom = "from"; |
public const string PropertyFrom = "from"; |
||||
|
/// <summary>
|
||||
|
/// 接收者
|
||||
|
/// </summary>
|
||||
public const string PropertyTo = "to"; |
public const string PropertyTo = "to"; |
||||
|
/// <summary>
|
||||
|
/// 必须,邮件主体
|
||||
|
/// </summary>
|
||||
public const string PropertySubject = "subject"; |
public const string PropertySubject = "subject"; |
||||
|
/// <summary>
|
||||
|
/// 消息内容, 文本消息时必须
|
||||
|
/// </summary>
|
||||
public const string PropertyBody = "body"; |
public const string PropertyBody = "body"; |
||||
public const string PropertyIsBodyHtml = "isBodyHtml"; |
|
||||
|
/// <summary>
|
||||
|
/// 发送模板消息
|
||||
|
/// </summary>
|
||||
|
public const string PropertyTemplate = "template"; |
||||
|
/// <summary>
|
||||
|
/// 可选, 模板消息中的上下文参数
|
||||
|
/// </summary>
|
||||
|
public const string PropertyContext = "context"; |
||||
|
/// <summary>
|
||||
|
/// 可选, 模板消息中的区域性
|
||||
|
/// </summary>
|
||||
|
public const string PropertyCulture = "culture"; |
||||
|
|
||||
public virtual async Task ExecuteAsync(JobRunnableContext context) |
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
{ |
{ |
||||
context.TryGetString(PropertyFrom, out var from); |
context.TryGetString(PropertyFrom, out var from); |
||||
var to = context.GetString(PropertyTo); |
var to = context.GetString(PropertyTo); |
||||
var subject = context.GetString(PropertySubject); |
var subject = context.GetString(PropertySubject); |
||||
var body = context.GetString(PropertyBody); |
|
||||
context.TryGetJobData<bool>(PropertyIsBodyHtml, out var isBodyHtml); |
|
||||
|
|
||||
var emailSender = context.GetRequiredService<IEmailSender>(); |
var emailSender = context.GetRequiredService<IEmailSender>(); |
||||
|
|
||||
await emailSender.QueueAsync(from, to, subject, body, isBodyHtml); |
if (context.TryGetString(PropertyTemplate, out var template) && !template.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
var globalContext = new Dictionary<string, object>(); |
||||
|
context.TryGetString(PropertyCulture, out var culture); |
||||
|
if (context.TryGetString(PropertyContext, out var globalCtx)) |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
try |
||||
|
{ |
||||
|
globalContext = jsonSerializer.Deserialize<Dictionary<string, object>>(globalCtx); |
||||
|
} |
||||
|
catch { } |
||||
|
} |
||||
|
|
||||
|
var templateRenderer = context.GetRequiredService<ITemplateRenderer>(); |
||||
|
|
||||
|
var content = await templateRenderer.RenderAsync( |
||||
|
templateName: template, |
||||
|
cultureName: culture, |
||||
|
globalContext: globalContext); |
||||
|
|
||||
|
await emailSender.QueueAsync(from, to, subject, content, true); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var body = context.GetString(PropertyBody); |
||||
|
|
||||
|
await emailSender.QueueAsync(from, to, subject, body, false); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue