19 changed files with 368 additions and 0 deletions
@ -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,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Platform.Jobs</AssemblyName> |
|||
<PackageId>LINGYUN.Platform.Jobs</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Platform\Jobs\Localization\Resources\*.json" /> |
|||
<EmbeddedResource Include="LINGYUN\Platform\Jobs\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\task-management\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Platform.Domain\LINGYUN.Platform.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using LINGYUN.Platform.Localization; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Platform.Jobs; |
|||
|
|||
internal static class LocalizableStatic |
|||
{ |
|||
public static ILocalizableString Create(string name) |
|||
{ |
|||
return LocalizableString.Create<PlatformResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"SmsMessageRetrySendJob": "Re-sending of SMS message job", |
|||
"EmailMessageRetrySendJob": "Re-sending of Email message job", |
|||
"DisplayName:RecentSnedFailedHours": "The latest failure in sending occurred at", |
|||
"Description:RecentSnedFailedHours": "Recent failure sending time, unit: hours, default: 2 hours.", |
|||
"DisplayName:PropertyMaxOfFailedAttempts": "Maximum number of failure attempts", |
|||
"Description:PropertyMaxOfFailedAttempts": "Maximum number of failures, default: 3.", |
|||
"DisplayName:PropertyPollingBatchCount": "Polling batch count", |
|||
"Description:PropertyPollingBatchCount": "Polling batch count, default: 100." |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"SmsMessageRetrySendJob": "短信重新发送作业", |
|||
"EmailMessageRetrySendJob": "邮件重新发送作业", |
|||
"DisplayName:RecentSnedFailedHours": "最近发送失败时间", |
|||
"Description:RecentSnedFailedHours": "最近发送失败时间, 单位: 小时, 默认: 2.", |
|||
"DisplayName:PropertyMaxOfFailedAttempts": "失败次数上限", |
|||
"Description:PropertyMaxOfFailedAttempts": "失败次数上限, 默认: 3.", |
|||
"DisplayName:PropertyPollingBatchCount": "轮询批次数量", |
|||
"Description:PropertyPollingBatchCount": "轮询批次数量, 默认: 100." |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Platform.Messages; |
|||
using Microsoft.Extensions.Logging; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.Specifications; |
|||
using Volo.Abp.Timing; |
|||
|
|||
namespace LINGYUN.Platform.Jobs.Messages; |
|||
/// <summary>
|
|||
/// 邮件重新发送作业
|
|||
/// </summary>
|
|||
[DisableAuditing] |
|||
[DisableJobAction] |
|||
public class EmailMessageRetrySendJob : IJobRunnable |
|||
{ |
|||
public const string Name = "EmailMessageRetrySendJob"; |
|||
|
|||
#region Definition Paramters
|
|||
|
|||
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters = |
|||
new List<JobDefinitionParamter> |
|||
{ |
|||
new JobDefinitionParamter( |
|||
PropertyRecentSnedFailedHours, |
|||
LocalizableStatic.Create("DisplayName:RecentSnedFailedHours"), |
|||
LocalizableStatic.Create("Description:RecentSnedFailedHours")), |
|||
new JobDefinitionParamter( |
|||
PropertyMaxOfFailedAttempts, |
|||
LocalizableStatic.Create("DisplayName:PropertyMaxOfFailedAttempts"), |
|||
LocalizableStatic.Create("Description:PropertyMaxOfFailedAttempts")), |
|||
new JobDefinitionParamter( |
|||
PropertyPollingBatchCount, |
|||
LocalizableStatic.Create("DisplayName:PropertyPollingBatchCount"), |
|||
LocalizableStatic.Create("Description:PropertyPollingBatchCount")), |
|||
}; |
|||
|
|||
#endregion
|
|||
/// <summary>
|
|||
/// 最近发送失败时间, 单位: 小时, 默认: 2
|
|||
/// </summary>
|
|||
public const string PropertyRecentSnedFailedHours = "RecentSnedFailedHours"; |
|||
/// <summary>
|
|||
/// 失败次数上限, 默认: 3
|
|||
/// </summary>
|
|||
public const string PropertyMaxOfFailedAttempts = "MaxOfFailedAttempts"; |
|||
/// <summary>
|
|||
/// 轮询批次数量, 默认: 100
|
|||
/// </summary>
|
|||
public const string PropertyPollingBatchCount = "PollingBatchCount"; |
|||
|
|||
public async virtual Task ExecuteAsync(JobRunnableContext context) |
|||
{ |
|||
var logger = context.GetRequiredService<ILogger<EmailMessageRetrySendJob>>(); |
|||
|
|||
var distributedLock = context.GetRequiredService<IAbpDistributedLock>(); |
|||
var distributedLockKey = nameof(EmailMessageRetrySendJob); |
|||
await using var jobLockHandle = await distributedLock.TryAcquireAsync(distributedLockKey); |
|||
if (jobLockHandle == null) |
|||
{ |
|||
logger.LogWarning("Handle is null because of the locking for : {distributedLockKey}", distributedLockKey); |
|||
return; |
|||
} |
|||
|
|||
logger.LogDebug("Lock is acquired for {distributedLockKey}.", distributedLockKey); |
|||
|
|||
var clock = context.GetRequiredService<IClock>(); |
|||
var emailMessageManager = context.GetRequiredService<IEmailMessageManager>(); |
|||
var emailMessageRepo = context.GetRequiredService<IEmailMessageRepository>(); |
|||
|
|||
var recentSnedFailedHours = context.GetOrDefaultJobData(PropertyRecentSnedFailedHours, 2); |
|||
var maxOfFailedAttempts = context.GetOrDefaultJobData(PropertyMaxOfFailedAttempts, 3); |
|||
var pollingBatchCount = context.GetOrDefaultJobData(PropertyPollingBatchCount, 100); |
|||
var sendTime = clock.Now.AddHours(-recentSnedFailedHours); |
|||
|
|||
var recentSnedFailedMessages = await emailMessageRepo.GetListAsync( |
|||
new ExpressionSpecification<EmailMessage>(x => x.Status == MessageStatus.Failed && |
|||
x.SendCount < maxOfFailedAttempts && x.SendTime >= sendTime), |
|||
maxResultCount: pollingBatchCount); |
|||
|
|||
if (recentSnedFailedMessages.Count > 0) |
|||
{ |
|||
logger.LogDebug("In the last {Hour} hours, a total of {Count} Email messages need to be resent.", recentSnedFailedHours, recentSnedFailedMessages.Count); |
|||
|
|||
foreach (var message in recentSnedFailedMessages) |
|||
{ |
|||
await emailMessageManager.SendAsync(message); |
|||
} |
|||
|
|||
await emailMessageRepo.UpdateManyAsync(recentSnedFailedMessages); |
|||
} |
|||
else |
|||
{ |
|||
logger.LogDebug($"There are no Email messages that need to be resent."); |
|||
} |
|||
|
|||
logger.LogDebug($"The batch retry operation of the Email messages has been successfully completed."); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Platform.Messages; |
|||
using Microsoft.Extensions.Logging; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.DistributedLocking; |
|||
using Volo.Abp.Specifications; |
|||
using Volo.Abp.Timing; |
|||
|
|||
namespace LINGYUN.Platform.Jobs.Messages; |
|||
/// <summary>
|
|||
/// 短信重新发送作业
|
|||
/// </summary>
|
|||
[DisableAuditing] |
|||
[DisableJobAction] |
|||
public class SmsMessageRetrySendJob : IJobRunnable |
|||
{ |
|||
public const string Name = "SmsMessageRetrySendJob"; |
|||
|
|||
#region Definition Paramters
|
|||
|
|||
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters = |
|||
new List<JobDefinitionParamter> |
|||
{ |
|||
new JobDefinitionParamter( |
|||
PropertyRecentSnedFailedHours, |
|||
LocalizableStatic.Create("DisplayName:RecentSnedFailedHours"), |
|||
LocalizableStatic.Create("Description:RecentSnedFailedHours")), |
|||
new JobDefinitionParamter( |
|||
PropertyMaxOfFailedAttempts, |
|||
LocalizableStatic.Create("DisplayName:PropertyMaxOfFailedAttempts"), |
|||
LocalizableStatic.Create("Description:PropertyMaxOfFailedAttempts")), |
|||
new JobDefinitionParamter( |
|||
PropertyPollingBatchCount, |
|||
LocalizableStatic.Create("DisplayName:PropertyPollingBatchCount"), |
|||
LocalizableStatic.Create("Description:PropertyPollingBatchCount")), |
|||
}; |
|||
|
|||
#endregion
|
|||
/// <summary>
|
|||
/// 最近发送失败时间, 单位: 小时, 默认: 2
|
|||
/// </summary>
|
|||
public const string PropertyRecentSnedFailedHours = "RecentSnedFailedHours"; |
|||
/// <summary>
|
|||
/// 失败次数上限, 默认: 3
|
|||
/// </summary>
|
|||
public const string PropertyMaxOfFailedAttempts = "MaxOfFailedAttempts"; |
|||
/// <summary>
|
|||
/// 轮询批次数量, 默认: 100
|
|||
/// </summary>
|
|||
public const string PropertyPollingBatchCount = "PollingBatchCount"; |
|||
|
|||
public async virtual Task ExecuteAsync(JobRunnableContext context) |
|||
{ |
|||
var logger = context.GetRequiredService<ILogger<SmsMessageRetrySendJob>>(); |
|||
|
|||
var distributedLock = context.GetRequiredService<IAbpDistributedLock>(); |
|||
var distributedLockKey = nameof(SmsMessageRetrySendJob); |
|||
await using var jobLockHandle = await distributedLock.TryAcquireAsync(distributedLockKey); |
|||
if (jobLockHandle == null) |
|||
{ |
|||
logger.LogWarning("Handle is null because of the locking for : {distributedLockKey}", distributedLockKey); |
|||
return; |
|||
} |
|||
|
|||
logger.LogDebug("Lock is acquired for {distributedLockKey}.", distributedLockKey); |
|||
|
|||
var clock = context.GetRequiredService<IClock>(); |
|||
var smsMessageManager = context.GetRequiredService<ISmsMessageManager>(); |
|||
var smsMessageRepo = context.GetRequiredService<ISmsMessageRepository>(); |
|||
|
|||
var recentSnedFailedHours = context.GetOrDefaultJobData(PropertyRecentSnedFailedHours, 2); |
|||
var maxOfFailedAttempts = context.GetOrDefaultJobData(PropertyMaxOfFailedAttempts, 3); |
|||
var pollingBatchCount = context.GetOrDefaultJobData(PropertyPollingBatchCount, 100); |
|||
var sendTime = clock.Now.AddHours(-recentSnedFailedHours); |
|||
|
|||
var recentSnedFailedMessages = await smsMessageRepo.GetListAsync( |
|||
new ExpressionSpecification<SmsMessage>(x => x.Status == MessageStatus.Failed && |
|||
x.SendCount < maxOfFailedAttempts && x.SendTime >= sendTime), |
|||
maxResultCount: pollingBatchCount); |
|||
|
|||
if (recentSnedFailedMessages.Count > 0) |
|||
{ |
|||
logger.LogDebug("In the last {Hour} hours, a total of {Count} SMS messages need to be resent.", recentSnedFailedHours, recentSnedFailedMessages.Count); |
|||
|
|||
foreach (var message in recentSnedFailedMessages) |
|||
{ |
|||
await smsMessageManager.SendAsync(message); |
|||
} |
|||
|
|||
await smsMessageRepo.UpdateManyAsync(recentSnedFailedMessages); |
|||
} |
|||
else |
|||
{ |
|||
logger.LogDebug($"There are no SMS messages that need to be resent."); |
|||
} |
|||
|
|||
logger.LogDebug($"The batch retry operation of the SMS messages has been successfully completed."); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Platform.Jobs.Messages; |
|||
|
|||
namespace LINGYUN.Platform.Jobs; |
|||
public class IdentityJobDefinitionProvider : JobDefinitionProvider |
|||
{ |
|||
public override void Define(IJobDefinitionContext context) |
|||
{ |
|||
context.Add( |
|||
new JobDefinition( |
|||
EmailMessageRetrySendJob.Name, |
|||
typeof(EmailMessageRetrySendJob), |
|||
LocalizableStatic.Create("EmailMessageRetrySendJob"), |
|||
EmailMessageRetrySendJob.Paramters), |
|||
new JobDefinition( |
|||
SmsMessageRetrySendJob.Name, |
|||
typeof(SmsMessageRetrySendJob), |
|||
LocalizableStatic.Create("SmsMessageRetrySendJob"), |
|||
SmsMessageRetrySendJob.Paramters) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using LINGYUN.Platform.Localization; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Platform.Jobs; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpBackgroundJobsAbstractionsModule), |
|||
typeof(PlatformDomainModule))] |
|||
public class PlatformJobsModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<PlatformJobsModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<PlatformResource>() |
|||
.AddVirtualJson("/LINGYUN/Platform/Jobs/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue