127 changed files with 4928 additions and 4 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,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Core" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpBackgroundTasksAbstractionsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 任务类需要实现此接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobRunnable |
||||
|
{ |
||||
|
Task ExecuteAsync(JobRunnableContext context); |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 定义任务执行者接口
|
||||
|
/// 可以通过它实现一些限制(例如分布式锁)
|
||||
|
/// </summary>
|
||||
|
public interface IJobRunnableExecuter |
||||
|
{ |
||||
|
Task ExecuteAsync(JobRunnableContext context); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobEventContext |
||||
|
{ |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
public JobEventData EventData { get; } |
||||
|
|
||||
|
public JobEventContext( |
||||
|
IServiceProvider serviceProvider, |
||||
|
JobEventData jobEventData) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
EventData = jobEventData; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobEventData |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public Type Type { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务组别
|
||||
|
/// </summary>
|
||||
|
public string Group { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务标识
|
||||
|
/// </summary>
|
||||
|
public Guid Key { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 执行者租户
|
||||
|
/// </summary>
|
||||
|
public Guid? TenantId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误明细
|
||||
|
/// </summary>
|
||||
|
public Exception Exception { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int Triggered { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大可执行次数
|
||||
|
/// </summary>
|
||||
|
public int RepeatCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime RunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
public JobEventData( |
||||
|
Guid key, |
||||
|
Type type, |
||||
|
string group, |
||||
|
string name, |
||||
|
Exception exception = null) |
||||
|
{ |
||||
|
Key = key; |
||||
|
Type = type; |
||||
|
Group = group; |
||||
|
Name = name; |
||||
|
Exception = exception; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务标识
|
||||
|
/// </summary>
|
||||
|
public Guid Id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public IDictionary<string, object> Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } = JobStatus.None; |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime CreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下一次执行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } = JobType.Once; |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int TriggerCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试次数
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } = 50; |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } = JobPriority.Normal; |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 与框架保持一致
|
||||
|
/// </remarks>
|
||||
|
public enum JobPriority |
||||
|
{ |
||||
|
Low = 5, |
||||
|
BelowNormal = 10, |
||||
|
Normal = 0xF, |
||||
|
AboveNormal = 20, |
||||
|
High = 25 |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobRunnableContext |
||||
|
{ |
||||
|
public Type JobType { get; } |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
public IReadOnlyDictionary<string, object> JobData { get; } |
||||
|
public JobRunnableContext( |
||||
|
Type jobType, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IReadOnlyDictionary<string, object> jobData) |
||||
|
{ |
||||
|
JobType = jobType; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
JobData = jobData; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public enum JobStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未知
|
||||
|
/// </summary>
|
||||
|
None = -1, |
||||
|
/// <summary>
|
||||
|
/// 已完成
|
||||
|
/// </summary>
|
||||
|
Completed = 0, |
||||
|
/// <summary>
|
||||
|
/// 运行中
|
||||
|
/// </summary>
|
||||
|
Running = 10, |
||||
|
/// <summary>
|
||||
|
/// 已暂停
|
||||
|
/// </summary>
|
||||
|
Paused = 20, |
||||
|
/// <summary>
|
||||
|
/// 已停止
|
||||
|
/// </summary>
|
||||
|
Stopped = 30 |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public enum JobType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 一次性
|
||||
|
/// </summary>
|
||||
|
Once, |
||||
|
/// <summary>
|
||||
|
/// 周期性
|
||||
|
/// </summary>
|
||||
|
Period, |
||||
|
/// <summary>
|
||||
|
/// 持续性
|
||||
|
/// </summary>
|
||||
|
Persistent |
||||
|
} |
||||
@ -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,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Quartz" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Quartz; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Quartz; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
[DependsOn(typeof(AbpBackgroundTasksModule))] |
||||
|
[DependsOn(typeof(AbpQuartzModule))] |
||||
|
public class AbpBackgroundTasksQuartzModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var _scheduler = context.ServiceProvider.GetRequiredService<IScheduler>(); |
||||
|
|
||||
|
_scheduler.ListenerManager.AddJobListener(context.ServiceProvider.GetRequiredService<QuartzJobListener>()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Quartz; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public interface IQuartzJobExecutorProvider |
||||
|
{ |
||||
|
#nullable enable |
||||
|
IJobDetail? CreateJob(JobInfo job); |
||||
|
|
||||
|
ITrigger CreateTrigger(JobInfo job); |
||||
|
#nullable disable |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Quartz; |
||||
|
using System; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonDependency |
||||
|
{ |
||||
|
public ILogger<QuartzJobExecutorProvider> Logger { protected get; set; } |
||||
|
|
||||
|
protected IClock Clock { get; } |
||||
|
protected AbpBackgroundTasksOptions Options { get; } |
||||
|
|
||||
|
public QuartzJobExecutorProvider( |
||||
|
IClock clock, |
||||
|
IOptions<AbpBackgroundTasksOptions> options) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
Logger = NullLogger<QuartzJobExecutorProvider>.Instance; |
||||
|
} |
||||
|
|
||||
|
public IJobDetail CreateJob(JobInfo job) |
||||
|
{ |
||||
|
var jobType = Type.GetType(job.Type); |
||||
|
if (jobType == null) |
||||
|
{ |
||||
|
Logger.LogWarning($"The task: {job.Group} - {job.Name}: {job.Type} is not registered and cannot create an instance of the performer type."); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var adapterType = typeof(QuartzJobSimpleAdapter<>); |
||||
|
|
||||
|
if (!typeof(IJob).IsAssignableFrom(jobType)) |
||||
|
{ |
||||
|
jobType = adapterType.MakeGenericType(jobType); |
||||
|
} |
||||
|
|
||||
|
var jobBuilder = JobBuilder.Create(jobType) |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description); |
||||
|
|
||||
|
jobBuilder.UsingJobData(nameof(JobInfo.Id), job.Id); |
||||
|
jobBuilder.UsingJobData(nameof(JobInfo.LockTimeOut), job.LockTimeOut); |
||||
|
jobBuilder.UsingJobData(new JobDataMap(job.Args)); |
||||
|
|
||||
|
return jobBuilder.Build(); |
||||
|
} |
||||
|
|
||||
|
public ITrigger CreateTrigger(JobInfo job) |
||||
|
{ |
||||
|
var triggerBuilder = TriggerBuilder.Create(); |
||||
|
|
||||
|
switch (job.JobType) |
||||
|
{ |
||||
|
case JobType.Period: |
||||
|
if (!CronExpression.IsValidExpression(job.Cron)) |
||||
|
{ |
||||
|
Logger.LogWarning($"The task: {job.Group} - {job.Name} periodic task Cron expression was invalid and the task trigger could not be created."); |
||||
|
return null; |
||||
|
} |
||||
|
triggerBuilder |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description) |
||||
|
.EndAt(job.EndTime) |
||||
|
.ForJob(job.Name, job.Group) |
||||
|
.WithPriority((int)job.Priority) |
||||
|
.WithCronSchedule(job.Cron); |
||||
|
if (job.BeginTime > Clock.Now) |
||||
|
{ |
||||
|
triggerBuilder = triggerBuilder.StartAt(job.BeginTime); |
||||
|
} |
||||
|
break; |
||||
|
case JobType.Once: |
||||
|
case JobType.Persistent: |
||||
|
default: |
||||
|
// Quartz 需要减一位
|
||||
|
var maxCount = job.MaxCount <= 0 ? -1 : job.MaxCount - 1; |
||||
|
if (job.JobType == JobType.Once) |
||||
|
{ |
||||
|
maxCount = 0; |
||||
|
} |
||||
|
triggerBuilder |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description) |
||||
|
.EndAt(job.EndTime) |
||||
|
.ForJob(job.Name, job.Group) |
||||
|
.WithPriority((int)job.Priority) |
||||
|
.WithSimpleSchedule(x => |
||||
|
x.WithIntervalInSeconds(job.Interval) |
||||
|
.WithRepeatCount(maxCount)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return triggerBuilder.Build(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,144 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Quartz; |
||||
|
using Quartz.Listener; |
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobListener : JobListenerSupport, ISingletonDependency |
||||
|
{ |
||||
|
public ILogger<QuartzJobListener> Logger { protected get; set; } |
||||
|
|
||||
|
public override string Name => "QuartzJobListener"; |
||||
|
|
||||
|
protected IJobEventProvider EventProvider { get; } |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public QuartzJobListener( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IJobEventProvider eventProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
EventProvider = eventProvider; |
||||
|
|
||||
|
Logger = NullLogger<QuartzJobListener>.Instance; |
||||
|
} |
||||
|
|
||||
|
public override Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobType = context.JobDetail.JobType; |
||||
|
if (jobType.IsGenericType) |
||||
|
{ |
||||
|
jobType = jobType.GetGenericTypeDefinition(); |
||||
|
} |
||||
|
Logger.LogInformation($"The task {jobType.Name} could not be performed..."); |
||||
|
|
||||
|
return Task.FromResult(-1); |
||||
|
} |
||||
|
|
||||
|
public override async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobId = context.GetString(nameof(JobInfo.Id)); |
||||
|
if (Guid.TryParse(jobId, out var jobUUId)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var scope = ServiceProvider.CreateScope(); |
||||
|
var jobEventData = new JobEventData( |
||||
|
jobUUId, |
||||
|
context.JobDetail.JobType, |
||||
|
context.JobDetail.Key.Group, |
||||
|
context.JobDetail.Key.Name) |
||||
|
{ |
||||
|
Result = context.Result?.ToString() |
||||
|
}; |
||||
|
|
||||
|
var jobEventList = EventProvider.GetAll(); |
||||
|
var eventContext = new JobEventContext( |
||||
|
scope.ServiceProvider, |
||||
|
jobEventData); |
||||
|
|
||||
|
var index = 0; |
||||
|
var taskList = new Task[jobEventList.Count]; |
||||
|
foreach (var jobEvent in jobEventList) |
||||
|
{ |
||||
|
taskList[index] = jobEvent.OnJobBeforeExecuted(eventContext); |
||||
|
index++; |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(taskList); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError($"The event before the task execution is abnormal:{ex}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public override async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var scope = ServiceProvider.CreateScope(); |
||||
|
var jobId = context.GetString(nameof(JobInfo.Id)); |
||||
|
if (Guid.TryParse(jobId, out var jobUUId)) |
||||
|
{ |
||||
|
var jobEventData = new JobEventData( |
||||
|
jobUUId, |
||||
|
context.JobDetail.JobType, |
||||
|
context.JobDetail.Key.Group, |
||||
|
context.JobDetail.Key.Name, |
||||
|
jobException) |
||||
|
{ |
||||
|
Status = JobStatus.Running |
||||
|
}; |
||||
|
|
||||
|
if (context.Trigger is ISimpleTrigger simpleTrigger) |
||||
|
{ |
||||
|
jobEventData.Triggered = simpleTrigger.TimesTriggered; |
||||
|
jobEventData.RepeatCount = simpleTrigger.RepeatCount; |
||||
|
} |
||||
|
jobEventData.Description = context.JobDetail.Description; |
||||
|
jobEventData.RunTime = context.FireTimeUtc.LocalDateTime; |
||||
|
jobEventData.LastRunTime = context.PreviousFireTimeUtc?.LocalDateTime; |
||||
|
jobEventData.NextRunTime = context.NextFireTimeUtc?.LocalDateTime; |
||||
|
if (context.Result != null) |
||||
|
{ |
||||
|
jobEventData.Result = context.Result.ToString(); |
||||
|
} |
||||
|
var tenantIdString = context.GetString(nameof(IMultiTenant.TenantId)); |
||||
|
if (Guid.TryParse(tenantIdString, out var tenantId)) |
||||
|
{ |
||||
|
jobEventData.TenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
var jobEventList = EventProvider.GetAll(); |
||||
|
var eventContext = new JobEventContext( |
||||
|
scope.ServiceProvider, |
||||
|
jobEventData); |
||||
|
|
||||
|
var index = 0; |
||||
|
var taskList = new Task[jobEventList.Count]; |
||||
|
foreach (var jobEvent in jobEventList) |
||||
|
{ |
||||
|
taskList[index] = jobEvent.OnJobAfterExecuted(eventContext); |
||||
|
index++; |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(taskList); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError($"The event is abnormal after the task is executed:{ex}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,164 @@ |
|||||
|
using Quartz; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class QuartzJobScheduler : IJobScheduler, ISingletonDependency |
||||
|
{ |
||||
|
protected IJobStore JobStore { get; } |
||||
|
protected IScheduler Scheduler { get; } |
||||
|
protected IQuartzJobExecutorProvider QuartzJobExecutor { get; } |
||||
|
|
||||
|
public QuartzJobScheduler( |
||||
|
IJobStore jobStore, |
||||
|
IScheduler scheduler, |
||||
|
IQuartzJobExecutorProvider quartzJobExecutor) |
||||
|
{ |
||||
|
JobStore = jobStore; |
||||
|
Scheduler = scheduler; |
||||
|
QuartzJobExecutor = quartzJobExecutor; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> ExistsAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
return await Scheduler.CheckExists(jobKey); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task PauseAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.PauseTrigger(trigger.Key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> QueueAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var jobDetail = QuartzJobExecutor.CreateJob(job); |
||||
|
if (jobDetail == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var jobTrigger = QuartzJobExecutor.CreateTrigger(job); |
||||
|
if (jobTrigger == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
await Scheduler.ScheduleJob(jobDetail, jobTrigger); |
||||
|
|
||||
|
return await Scheduler.CheckExists(jobTrigger.Key); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task QueuesAsync(IEnumerable<JobInfo> jobs) |
||||
|
{ |
||||
|
var jobDictionary = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>(); |
||||
|
foreach (var job in jobs) |
||||
|
{ |
||||
|
var jobDetail = QuartzJobExecutor.CreateJob(job); |
||||
|
if (jobDetail == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var jobTrigger = QuartzJobExecutor.CreateTrigger(job); |
||||
|
if (jobTrigger == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
jobDictionary.Add(jobDetail, new ITrigger[] { jobTrigger }); |
||||
|
} |
||||
|
|
||||
|
await Scheduler.ScheduleJobs(jobDictionary, false); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> RemoveAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (!await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.PauseTrigger(trigger.Key); |
||||
|
} |
||||
|
await Scheduler.DeleteJob(jobKey); |
||||
|
|
||||
|
return !await Scheduler.CheckExists(jobKey); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ResumeAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.ResumeTrigger(trigger.Key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> ShutdownAsync() |
||||
|
{ |
||||
|
await StopAsync(); |
||||
|
|
||||
|
await Scheduler.Shutdown(true); |
||||
|
|
||||
|
return Scheduler.IsShutdown; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> StartAsync() |
||||
|
{ |
||||
|
if (Scheduler.InStandbyMode) |
||||
|
{ |
||||
|
await Scheduler.Start(); |
||||
|
} |
||||
|
return Scheduler.InStandbyMode; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> StopAsync() |
||||
|
{ |
||||
|
if (!Scheduler.InStandbyMode) |
||||
|
{ |
||||
|
//等待任务运行完成
|
||||
|
await Scheduler.Standby(); |
||||
|
} |
||||
|
return !Scheduler.InStandbyMode; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task TriggerAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
await Scheduler.TriggerJob(jobKey); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new AbpException("This task could not be found in task scheduler, please confirm that it is enabled?"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Quartz; |
||||
|
using System; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobSimpleAdapter<TJobRunnable> : IJob |
||||
|
where TJobRunnable : IJobRunnable |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public QuartzJobSimpleAdapter( |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task Execute(IJobExecutionContext context) |
||||
|
{ |
||||
|
// 任务已经在一个作用域中
|
||||
|
// using var scope = ServiceProvider.CreateScope();
|
||||
|
var jobExecuter = ServiceProvider.GetRequiredService<IJobRunnableExecuter>(); |
||||
|
|
||||
|
var jobContext = new JobRunnableContext( |
||||
|
typeof(TJobRunnable), |
||||
|
ServiceProvider, |
||||
|
context.MergedJobDataMap.ToImmutableDictionary()); |
||||
|
|
||||
|
await jobExecuter.ExecuteAsync(jobContext); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Quartz; |
||||
|
|
||||
|
public static class IJobExecutionContextExtensions |
||||
|
{ |
||||
|
public static TValue GetData<TValue>(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.GetString(key); |
||||
|
|
||||
|
return (TValue)Convert.ChangeType(value, typeof(TValue)); |
||||
|
} |
||||
|
|
||||
|
public static string GetString(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.Get(key); |
||||
|
|
||||
|
return value != null ? value.ToString() : ""; |
||||
|
} |
||||
|
|
||||
|
public static int GetInt(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.GetInt(key); |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
# LINGYUN.Abp.BackgroundTasks.Quartz |
||||
|
|
||||
|
后台任务(队列)模块的Quartz实现, 使用任务适配器来做到任务的幂等性控制. |
||||
|
并添加一个监听器用于通知管理者任务状态 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用,具体配置参考Volo.Abp.Quartz模块 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpBackgroundTasksQuartzModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
@ -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,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Auditing" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Guids" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,49 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
[DependsOn(typeof(AbpAuditingModule))] |
||||
|
[DependsOn(typeof(AbpDistributedLockingModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpGuidsModule))] |
||||
|
public class AbpBackgroundTasksModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
AutoAddJobMonitors(context.Services); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTransient(typeof(BackgroundJobAdapter<>)); |
||||
|
context.Services.AddHostedService<DefaultBackgroundWorker>(); |
||||
|
} |
||||
|
|
||||
|
private static void AutoAddJobMonitors(IServiceCollection services) |
||||
|
{ |
||||
|
var jobMonitors = new List<Type>(); |
||||
|
|
||||
|
services.OnRegistred(context => |
||||
|
{ |
||||
|
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(JobEventBase<>))) |
||||
|
{ |
||||
|
jobMonitors.Add(context.ImplementationType); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
services.Configure<AbpBackgroundTasksOptions>(options => |
||||
|
{ |
||||
|
options.JobMonitors.AddIfNotContains(jobMonitors); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpBackgroundTasksOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务监听类型列表
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 用户可以实现事件监听实现自定义逻辑
|
||||
|
/// </remarks>
|
||||
|
public ITypeList<IJobEvent> JobMonitors { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务过期时间
|
||||
|
/// 默认: 15 days
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 任务过期时间,超出时间段清理
|
||||
|
/// </remarks>
|
||||
|
public TimeSpan JobExpiratime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 每次清理任务批次大小
|
||||
|
/// 默认: 1000
|
||||
|
/// </summary>
|
||||
|
public int MaxJobCleanCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 清理过期任务批次Cron表达式
|
||||
|
/// 默认: 600秒(0 0/10 * * * ? * )
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Cron表达式
|
||||
|
/// </remarks>
|
||||
|
public string JobCleanCronExpression { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 每次轮询任务批次大小
|
||||
|
/// 默认: 1000
|
||||
|
/// </summary>
|
||||
|
public int MaxJobFetchCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 轮询任务批次Cron表达式
|
||||
|
/// 默认: 30秒(0/30 * * * * ? )
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Cron表达式
|
||||
|
/// </remarks>
|
||||
|
public string JobFetchCronExpression { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 轮询任务批次时锁定任务超时时长(秒)
|
||||
|
/// 默认:120
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 轮询任务也属于一个后台任务, 需要对每一次轮询加锁,防止重复任务入库
|
||||
|
/// </remarks>
|
||||
|
public int JobFetchLockTimeOut { get; set; } |
||||
|
public AbpBackgroundTasksOptions() |
||||
|
{ |
||||
|
MaxJobFetchCount = 1000; |
||||
|
JobFetchLockTimeOut = 120; |
||||
|
JobFetchCronExpression = "0/30 * * * * ? "; |
||||
|
|
||||
|
MaxJobCleanCount = 1000; |
||||
|
JobExpiratime = TimeSpan.FromDays(15d); |
||||
|
JobCleanCronExpression = "0 0/10 * * * ? *"; |
||||
|
|
||||
|
JobMonitors = new TypeList<IJobEvent>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class BackgroundJobAdapter<TArgs> : IJobRunnable |
||||
|
{ |
||||
|
public ILogger<BackgroundJobAdapter<TArgs>> Logger { protected get; set; } |
||||
|
|
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
protected IServiceScopeFactory ServiceScopeFactory { get; } |
||||
|
protected IBackgroundJobExecuter JobExecuter { get; } |
||||
|
|
||||
|
public BackgroundJobAdapter( |
||||
|
IOptions<AbpBackgroundJobOptions> options, |
||||
|
IBackgroundJobExecuter jobExecuter, |
||||
|
IServiceScopeFactory serviceScopeFactory) |
||||
|
{ |
||||
|
JobExecuter = jobExecuter; |
||||
|
ServiceScopeFactory = serviceScopeFactory; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
Logger = NullLogger<BackgroundJobAdapter<TArgs>>.Instance; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
using var scope = ServiceScopeFactory.CreateScope(); |
||||
|
var args = context.JobData.GetOrDefault(nameof(TArgs)); |
||||
|
var jobType = Options.GetJob(typeof(TArgs)).JobType; |
||||
|
var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args); |
||||
|
await JobExecuter.ExecuteAsync(jobContext); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency |
||||
|
{ |
||||
|
protected IClock Clock { get; } |
||||
|
protected IJobStore JobStore { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
public BackgroundJobManager( |
||||
|
IClock clock, |
||||
|
IJobStore jobStore, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IOptions<AbpBackgroundJobOptions> options) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
JobStore = jobStore; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> EnqueueAsync<TArgs>( |
||||
|
TArgs args, |
||||
|
BackgroundJobPriority priority = BackgroundJobPriority.Normal, |
||||
|
TimeSpan? delay = null) |
||||
|
{ |
||||
|
var jobConfiguration = Options.GetJob<TArgs>(); |
||||
|
var interval = 60; |
||||
|
if (delay.HasValue) |
||||
|
{ |
||||
|
interval = delay.Value.Seconds; |
||||
|
} |
||||
|
var jobId = GuidGenerator.Create(); |
||||
|
var jobArgs = new Dictionary<string, object> |
||||
|
{ |
||||
|
{ nameof(TArgs), args }, |
||||
|
{ "ArgsType", jobConfiguration.ArgsType.AssemblyQualifiedName }, |
||||
|
{ "JobType", typeof(BackgroundJobAdapter<TArgs>).AssemblyQualifiedName }, |
||||
|
}; |
||||
|
var jobInfo = new JobInfo |
||||
|
{ |
||||
|
Id = jobId, |
||||
|
Name = jobConfiguration.JobName, |
||||
|
Group = "BackgroundJobs", |
||||
|
Priority = ConverForm(priority), |
||||
|
BeginTime = DateTime.Now, |
||||
|
Args = jobArgs, |
||||
|
Description = "From the framework background jobs", |
||||
|
JobType = JobType.Once, |
||||
|
Interval = interval, |
||||
|
CreationTime = Clock.Now, |
||||
|
Status = JobStatus.Running, |
||||
|
Type = typeof(BackgroundJobAdapter<TArgs>).AssemblyQualifiedName, |
||||
|
}; |
||||
|
|
||||
|
// 作为一次性任务持久化
|
||||
|
await JobStore.StoreAsync(jobInfo); |
||||
|
|
||||
|
return jobId.ToString(); |
||||
|
} |
||||
|
|
||||
|
private JobPriority ConverForm(BackgroundJobPriority priority) |
||||
|
{ |
||||
|
return priority switch |
||||
|
{ |
||||
|
BackgroundJobPriority.Low => JobPriority.Low, |
||||
|
BackgroundJobPriority.High => JobPriority.High, |
||||
|
BackgroundJobPriority.BelowNormal => JobPriority.BelowNormal, |
||||
|
BackgroundJobPriority.AboveNormal => JobPriority.AboveNormal, |
||||
|
_ => JobPriority.Normal, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 挂载任务事件接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobEvent |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务启动前事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobEventData"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task OnJobBeforeExecuted(JobEventContext context); |
||||
|
/// <summary>
|
||||
|
/// 任务完成后事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobEventData"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task OnJobAfterExecuted(JobEventContext context); |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 任务事件提供者
|
||||
|
/// </summary>
|
||||
|
public interface IJobEventProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 返回所有任务事件注册接口
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
IReadOnlyCollection<IJobEvent> GetAll(); |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 作业调度接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobScheduler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务入队
|
||||
|
/// </summary>
|
||||
|
/// <param name="job"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> QueueAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 任务入队
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobs"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task QueuesAsync(IEnumerable<JobInfo> jobs); |
||||
|
/// <summary>
|
||||
|
/// 任务是否存在
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> ExistsAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 触发任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task TriggerAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 暂停任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task PauseAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 恢复暂停的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task ResumeAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 移除任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> RemoveAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 启动任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> StartAsync(); |
||||
|
/// <summary>
|
||||
|
/// 停止任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> StopAsync(); |
||||
|
/// <summary>
|
||||
|
/// 释放任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> ShutdownAsync(); |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public interface IJobStore |
||||
|
{ |
||||
|
Task<List<JobInfo>> GetWaitingListAsync( |
||||
|
int maxResultCount, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<JobInfo>> GetAllPeriodTasksAsync( |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<JobInfo> FindAsync(Guid jobId); |
||||
|
|
||||
|
Task StoreAsync(JobInfo jobInfo); |
||||
|
|
||||
|
Task StoreLogAsync(JobEventData eventData); |
||||
|
|
||||
|
Task CleanupAsync( |
||||
|
int maxResultCount, |
||||
|
TimeSpan jobExpiratime, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class BackgroundCleaningJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value; |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
await store.CleanupAsync( |
||||
|
options.MaxJobCleanCount, |
||||
|
options.JobExpiratime); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
internal class BackgroundKeepAliveJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
var periodJobs = await store.GetAllPeriodTasksAsync(); |
||||
|
|
||||
|
if (!periodJobs.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
await jobScheduler.QueuesAsync(periodJobs); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
internal class BackgroundPollingJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value; |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
var waitingJobs = await store.GetWaitingListAsync(options.MaxJobFetchCount); |
||||
|
|
||||
|
if (!waitingJobs.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
foreach (var job in waitingJobs) |
||||
|
{ |
||||
|
await jobScheduler.QueueAsync(job); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class DefaultBackgroundWorker : BackgroundService |
||||
|
{ |
||||
|
private readonly IJobStore _jobStore; |
||||
|
private readonly IJobScheduler _jobScheduler; |
||||
|
private readonly AbpBackgroundTasksOptions _options; |
||||
|
|
||||
|
public DefaultBackgroundWorker( |
||||
|
IJobStore jobStore, |
||||
|
IJobScheduler jobScheduler, |
||||
|
IOptions<AbpBackgroundTasksOptions> options) |
||||
|
{ |
||||
|
_jobStore = jobStore; |
||||
|
_jobScheduler = jobScheduler; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
protected async override Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
await QueuePollingJob(); |
||||
|
await QueueKeepAliveJob(); |
||||
|
await QueueCleaningJob(); |
||||
|
} |
||||
|
|
||||
|
private async Task QueueKeepAliveJob() |
||||
|
{ |
||||
|
var keepAliveJob = BuildKeepAliveJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(keepAliveJob); |
||||
|
} |
||||
|
|
||||
|
private async Task QueuePollingJob() |
||||
|
{ |
||||
|
var pollingJob = BuildPollingJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(pollingJob); |
||||
|
} |
||||
|
|
||||
|
private async Task QueueCleaningJob() |
||||
|
{ |
||||
|
var cleaningJob = BuildCleaningJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(cleaningJob); |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildKeepAliveJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("8F50C5D9-5691-4B99-A52B-CABD91D93C89"), |
||||
|
Name = nameof(BackgroundKeepAliveJob), |
||||
|
Group = "Default", |
||||
|
Description = "Add periodic tasks", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
JobType = JobType.Once, |
||||
|
Priority = JobPriority.High, |
||||
|
MaxCount = 1, |
||||
|
Type = typeof(BackgroundKeepAliveJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildPollingJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("C51152E9-F0B8-4252-8352-283BE46083CC"), |
||||
|
Name = nameof(BackgroundPollingJob), |
||||
|
Group = "Default", |
||||
|
Description = "Polling tasks to be executed", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Cron = _options.JobFetchCronExpression, |
||||
|
JobType = JobType.Period, |
||||
|
Priority = JobPriority.High, |
||||
|
LockTimeOut = _options.JobFetchLockTimeOut, |
||||
|
Type = typeof(BackgroundPollingJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildCleaningJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("AAAF8783-FA06-4CF9-BDCA-11140FB2478F"), |
||||
|
Name = nameof(BackgroundCleaningJob), |
||||
|
Group = "Default", |
||||
|
Description = "Cleaning tasks to be executed", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Cron = _options.JobCleanCronExpression, |
||||
|
JobType = JobType.Period, |
||||
|
Priority = JobPriority.High, |
||||
|
Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[Dependency(TryRegister = true)] |
||||
|
internal class InMemoryJobStore : IJobStore, ISingletonDependency |
||||
|
{ |
||||
|
private readonly List<JobInfo> _memoryJobStore; |
||||
|
|
||||
|
public InMemoryJobStore() |
||||
|
{ |
||||
|
_memoryJobStore = new List<JobInfo>(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<JobInfo>> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobs = _memoryJobStore |
||||
|
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ToList(); |
||||
|
|
||||
|
return Task.FromResult(jobs); |
||||
|
} |
||||
|
|
||||
|
public Task<List<JobInfo>> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var now = DateTime.Now; |
||||
|
var jobs = _memoryJobStore |
||||
|
.Where(x => !x.IsAbandoned && x.JobType != JobType.Period && x.Status == JobStatus.Running) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ThenBy(x => x.TryCount) |
||||
|
.ThenBy(x => x.NextRunTime) |
||||
|
.Take(maxResultCount) |
||||
|
.ToList(); |
||||
|
|
||||
|
return Task.FromResult(jobs); |
||||
|
} |
||||
|
|
||||
|
public Task<JobInfo> FindAsync(Guid jobId) |
||||
|
{ |
||||
|
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobId)); |
||||
|
return Task.FromResult(job); |
||||
|
} |
||||
|
|
||||
|
public Task StoreAsync(JobInfo jobInfo) |
||||
|
{ |
||||
|
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobInfo.Id)); |
||||
|
if (job != null) |
||||
|
{ |
||||
|
job.NextRunTime = jobInfo.NextRunTime; |
||||
|
job.LastRunTime = jobInfo.LastRunTime; |
||||
|
job.Status = jobInfo.Status; |
||||
|
job.TriggerCount = jobInfo.TriggerCount; |
||||
|
job.TryCount = jobInfo.TryCount; |
||||
|
job.IsAbandoned = jobInfo.IsAbandoned; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_memoryJobStore.Add(jobInfo); |
||||
|
} |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task StoreLogAsync(JobEventData eventData) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task CleanupAsync(int maxResultCount, TimeSpan jobExpiratime, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var expiratime = DateTime.Now - jobExpiratime; |
||||
|
|
||||
|
var expriaJobs = _memoryJobStore.Where( |
||||
|
x => x.Status == JobStatus.Completed && |
||||
|
expiratime.CompareTo(x.LastRunTime ?? x.EndTime ?? x.CreationTime) <= 0) |
||||
|
.Take(maxResultCount); |
||||
|
|
||||
|
_memoryJobStore.RemoveAll(expriaJobs); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class JobEventProvider : IJobEventProvider, ISingletonDependency |
||||
|
{ |
||||
|
private readonly Lazy<List<IJobEvent>> _lazyEvents; |
||||
|
private List<IJobEvent> _events => _lazyEvents.Value; |
||||
|
|
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
private readonly AbpBackgroundTasksOptions _options; |
||||
|
public JobEventProvider( |
||||
|
IOptions<AbpBackgroundTasksOptions> options, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
_serviceProvider = serviceProvider; |
||||
|
|
||||
|
_lazyEvents = new Lazy<List<IJobEvent>>(CreateJobEvents); |
||||
|
} |
||||
|
public IReadOnlyCollection<IJobEvent> GetAll() |
||||
|
{ |
||||
|
return _events.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
private List<IJobEvent> CreateJobEvents() |
||||
|
{ |
||||
|
var jobEvents = _options |
||||
|
.JobMonitors |
||||
|
.Select(p => _serviceProvider.GetRequiredService(p) as IJobEvent) |
||||
|
.ToList(); |
||||
|
|
||||
|
return jobEvents; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected override async Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
var job = await store.FindAsync(context.EventData.Key); |
||||
|
if (job != null) |
||||
|
{ |
||||
|
job.TriggerCount += 1; |
||||
|
job.NextRunTime = context.EventData.NextRunTime; |
||||
|
job.LastRunTime = context.EventData.LastRunTime; |
||||
|
job.Result = context.EventData.Result; |
||||
|
|
||||
|
// 一次性任务执行一次后标记为已完成
|
||||
|
if (job.JobType == JobType.Once) |
||||
|
{ |
||||
|
job.Status = JobStatus.Completed; |
||||
|
} |
||||
|
|
||||
|
// 任务异常后可重试
|
||||
|
if (context.EventData.Exception != null) |
||||
|
{ |
||||
|
job.TryCount += 1; |
||||
|
job.Status = JobStatus.Running; |
||||
|
job.Result = context.EventData.Exception.Message; |
||||
|
|
||||
|
if (job.TryCount > job.MaxTryCount) |
||||
|
{ |
||||
|
job.Status = JobStatus.Stopped; |
||||
|
job.IsAbandoned = true; |
||||
|
|
||||
|
await RemoveJobAsync(context, job); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 所有任务达到上限则标记已完成
|
||||
|
if (job.MaxCount > 0 && job.TriggerCount > job.MaxCount) |
||||
|
{ |
||||
|
job.Status = JobStatus.Completed; |
||||
|
|
||||
|
await RemoveJobAsync(context, job); |
||||
|
} |
||||
|
|
||||
|
await store.StoreAsync(job); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task RemoveJobAsync(JobEventContext context, JobInfo jobInfo) |
||||
|
{ |
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
await jobScheduler.RemoveAsync(jobInfo); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 存储任务日志
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 任务类型标记了<see cref="DisableAuditingAttribute"/> 特性则不会记录日志
|
||||
|
/// </remarks>
|
||||
|
internal class JobLogEvent : JobEventBase<JobLogEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected async override Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
if (context.EventData.Type.IsDefined(typeof(DisableAuditingAttribute), true)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
await store.StoreLogAsync(context.EventData); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public abstract class JobEventBase<TEvent> : IJobEvent |
||||
|
{ |
||||
|
public ILogger<TEvent> Logger { protected get; set; } |
||||
|
protected JobEventBase() |
||||
|
{ |
||||
|
Logger = NullLogger<TEvent>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task OnJobAfterExecuted(JobEventContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await OnJobAfterExecutedAsync(context); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError("Failed to execute event, error:" + GetSourceException(ex).Message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task OnJobBeforeExecuted(JobEventContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await OnJobBeforeExecutedAsync(context); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError("Failed to execute preprocessing event, error:" + GetSourceException(ex).Message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Task OnJobBeforeExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Exception GetSourceException(Exception exception) |
||||
|
{ |
||||
|
if (exception.InnerException != null) |
||||
|
{ |
||||
|
return GetSourceException(exception.InnerException); |
||||
|
} |
||||
|
return exception; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
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 = "job:{0},key:{1}"; |
||||
|
|
||||
|
public async virtual Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
Guid? tenantId = null; |
||||
|
if (context.JobData.TryGetValue(nameof(IMultiTenant.TenantId), out var tenant) && |
||||
|
Guid.TryParse(tenant?.ToString(), out var tid)) |
||||
|
{ |
||||
|
tenantId = tid; |
||||
|
} |
||||
|
|
||||
|
var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>(); |
||||
|
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, context.JobType.Name, jobId); |
||||
|
var distributedLock = context.ServiceProvider.GetRequiredService<IAbpDistributedLock>(); |
||||
|
await using (await distributedLock.TryAcquireAsync(jobLockKey, TimeSpan.FromSeconds(time))) |
||||
|
{ |
||||
|
await InternalExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await InternalExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task InternalExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var jobRunnable = context.ServiceProvider.GetService(context.JobType); |
||||
|
if (jobRunnable == null) |
||||
|
{ |
||||
|
jobRunnable = Activator.CreateInstance(context.JobType); |
||||
|
} |
||||
|
await ((IJobRunnable)jobRunnable).ExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Primitives; |
||||
|
|
||||
|
public class ConsoleJob : IJobRunnable |
||||
|
{ |
||||
|
public Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
Console.WriteLine($"This message comes from the job: {GetType()}"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Primitives; |
||||
|
|
||||
|
public class EmailingJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
# LINGYUN.Abp.BackgroundTasks |
||||
|
|
||||
|
后台任务(队列)模块,Abp提供的后台作业与后台工作者不支持Cron表达式, 提供可管理的后台任务(队列)功能. |
||||
|
|
||||
|
实现了**Volo.Abp.BackgroundJobs.IBackgroundJobManager**, 意味着您也能通过框架后台作业接口添加新作业. |
||||
|
|
||||
|
## 任务类别 |
||||
|
|
||||
|
* JobType.Once: 一次性任务, 此类型只会被执行一次, 适用于邮件通知等场景 |
||||
|
* JobType.Period: 周期性任务, 此类型任务会根据Cron表达式来决定运行方式, 适用于报表分析等场景 |
||||
|
* JobType.Persistent: 持续性任务, 此类型任务按照给定重复次数、重复间隔运行, 适用于接口压测等场景 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpBackgroundTasksModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
```csharp |
||||
|
public class DemoClass |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public DemoClass(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task Some() |
||||
|
{ |
||||
|
var scheduler = ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
// 将周期性(5秒一次)任务添加到队列 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Period", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Period, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Cron = "0/5 * * * * ? ", |
||||
|
TryCount = 10, |
||||
|
Status = JobStatus.Running, |
||||
|
}); |
||||
|
|
||||
|
// 将一次性任务添加到队列, 将在10(Interval)秒后被执行 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Once", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Once, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Interval = 10, |
||||
|
TryCount = 10, |
||||
|
Status = JobStatus.Running, |
||||
|
}); |
||||
|
|
||||
|
// 将持续性任务添加到队列, 将在10(Interval)秒后被执行, 最大执行5(MaxCount)次 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Persistent", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Persistent, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Interval = 10, |
||||
|
TryCount = 10, |
||||
|
MaxCount = 5, |
||||
|
Status = JobStatus.Running, |
||||
|
}); |
||||
|
|
||||
|
// 同样可以把框架后台作业添加到作业调度器中, 不需要更改使用习惯 |
||||
|
var backgroundJobManager = ServiceProvider.GetRequiredService<IBackgroundJobManager>(); |
||||
|
await jobManager.EnqueueAsync( |
||||
|
new SmsJobArgs |
||||
|
{ |
||||
|
PhoneNumber = "13800138000", |
||||
|
Message = "来自框架的后台工作者" |
||||
|
}, |
||||
|
BackgroundJobPriority.High, |
||||
|
TimeSpan.FromSeconds(10)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SmsJobArgs |
||||
|
{ |
||||
|
public string PhoneNumber { get; set; } |
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SmsJob : AsyncBackgroundJob<SmsJobArgs>, ITransientDependency |
||||
|
{ |
||||
|
public override Task ExecuteAsync(SmsJobArgs args) |
||||
|
{ |
||||
|
Console.WriteLine($"Send sms message: {args.Message}"); |
||||
|
|
||||
|
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,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoCreateDto : BackgroundJobInfoCreateOrUpdateDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public abstract class BackgroundJobInfoCreateOrUpdateDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public ExtraPropertyDictionary Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public ExtraPropertyDictionary Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下一次执行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int TriggerCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试次数
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoGetListInput: PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus? Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次起始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次截止触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 起始创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 截止创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否已放弃任务
|
||||
|
/// </summary>
|
||||
|
public bool? IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否持续性任务
|
||||
|
/// </summary>
|
||||
|
public bool? IsPeriod { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority? Priority { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoUpdateDto : BackgroundJobInfoCreateOrUpdateDto, IHasConcurrencyStamp |
||||
|
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLogDto : EntityDto<long> |
||||
|
{ |
||||
|
public string JobName { get; set; } |
||||
|
public string JobGroup { get; set; } |
||||
|
public string JobType { get; set; } |
||||
|
public string Message { get; set; } |
||||
|
public DateTime RunTime { get; set; } |
||||
|
public string Exception { get; set; } |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLogGetListInput : PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 存在异常
|
||||
|
/// </summary>
|
||||
|
public bool? HasExceptions { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndRunTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobInfoAppService : |
||||
|
ICrudAppService< |
||||
|
BackgroundJobInfoDto, |
||||
|
Guid, |
||||
|
BackgroundJobInfoGetListInput, |
||||
|
BackgroundJobInfoCreateDto, |
||||
|
BackgroundJobInfoUpdateDto> |
||||
|
{ |
||||
|
Task TriggerAsync(Guid id); |
||||
|
|
||||
|
Task PauseAsync(Guid id); |
||||
|
|
||||
|
Task ResumeAsync(Guid id); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobLogAppService : |
||||
|
IReadOnlyAppService< |
||||
|
BackgroundJobLogDto, |
||||
|
long, |
||||
|
BackgroundJobLogGetListInput> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
|
||||
|
public static class TaskManagementPermissions |
||||
|
{ |
||||
|
public const string GroupName = "TaskManagement"; |
||||
|
|
||||
|
public static class BackgroundJobs |
||||
|
{ |
||||
|
public const string Default = GroupName + ".BackgroundJobs"; |
||||
|
public const string Create = Default + ".Create"; |
||||
|
public const string Update = Default + ".Update"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
public const string Trigger = Default + ".Trigger"; |
||||
|
public const string Pause = Default + ".Pause"; |
||||
|
public const string Resume = Default + ".Resume"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Application; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(TaskManagementDomainSharedModule))] |
||||
|
[DependsOn(typeof(AbpDddApplicationContractsModule))] |
||||
|
public class TaskManagementApplicationContractsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -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,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,161 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Default)] |
||||
|
public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBackgroundJobInfoAppService |
||||
|
{ |
||||
|
protected BackgroundJobManager BackgroundJobManager { get; } |
||||
|
protected IBackgroundJobInfoRepository BackgroundJobInfoRepository { get; } |
||||
|
|
||||
|
public BackgroundJobInfoAppService( |
||||
|
BackgroundJobManager backgroundJobManager, |
||||
|
IBackgroundJobInfoRepository backgroundJobInfoRepository) |
||||
|
{ |
||||
|
BackgroundJobManager = backgroundJobManager; |
||||
|
BackgroundJobInfoRepository = backgroundJobInfoRepository; |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Create)] |
||||
|
public virtual async Task<BackgroundJobInfoDto> CreateAsync(BackgroundJobInfoCreateDto input) |
||||
|
{ |
||||
|
if (await BackgroundJobInfoRepository.CheckNameAsync(input.Name, input.Group)) |
||||
|
{ |
||||
|
throw new BusinessException(TaskManagementErrorCodes.JobNameAlreadyExists) |
||||
|
.WithData("Group", input.Group) |
||||
|
.WithData("Name", input.Name); |
||||
|
} |
||||
|
|
||||
|
var backgroundJobInfo = new BackgroundJobInfo( |
||||
|
GuidGenerator.Create(), |
||||
|
input.Name, |
||||
|
input.Group, |
||||
|
input.Type, |
||||
|
input.Args, |
||||
|
input.BeginTime, |
||||
|
input.EndTime, |
||||
|
input.Priority, |
||||
|
input.MaxCount, |
||||
|
input.MaxTryCount); |
||||
|
|
||||
|
UpdateByInput(backgroundJobInfo, input); |
||||
|
|
||||
|
await BackgroundJobInfoRepository.InsertAsync(backgroundJobInfo, autoSave: true); |
||||
|
|
||||
|
if (backgroundJobInfo.IsEnabled && backgroundJobInfo.JobType == JobType.Period) |
||||
|
{ |
||||
|
await BackgroundJobManager.QueueAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Delete)] |
||||
|
public virtual async Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.DeleteAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BackgroundJobInfoDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<PagedResultDto<BackgroundJobInfoDto>> GetListAsync(BackgroundJobInfoGetListInput input) |
||||
|
{ |
||||
|
var filter = new BackgroundJobInfoFilter |
||||
|
{ |
||||
|
IsAbandoned = input.IsAbandoned, |
||||
|
IsPeriod = input.IsPeriod, |
||||
|
BeginCreationTime = input.BeginCreationTime, |
||||
|
EndCreationTime = input.EndCreationTime, |
||||
|
BeginLastRunTime = input.BeginLastRunTime, |
||||
|
EndLastRunTime = input.EndLastRunTime, |
||||
|
BeginTime = input.BeginTime, |
||||
|
EndTime = input.EndTime, |
||||
|
Filter = input.Filter, |
||||
|
Group = input.Group, |
||||
|
Name = input.Name, |
||||
|
Priority = input.Priority, |
||||
|
Status = input.Status, |
||||
|
Type = input.Type |
||||
|
}; |
||||
|
var totalCount = await BackgroundJobInfoRepository.GetCountAsync(filter); |
||||
|
var backgroundJobInfos = await BackgroundJobInfoRepository.GetListAsync( |
||||
|
filter, input.Sorting, input.MaxResultCount, input.SkipCount); |
||||
|
|
||||
|
return new PagedResultDto<BackgroundJobInfoDto>(totalCount, |
||||
|
ObjectMapper.Map<List<BackgroundJobInfo>, List<BackgroundJobInfoDto>>(backgroundJobInfos)); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Pause)] |
||||
|
public virtual async Task PauseAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.PauseAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Resume)] |
||||
|
public virtual async Task ResumeAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.ResumeAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Trigger)] |
||||
|
public virtual async Task TriggerAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.TriggerAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Update)] |
||||
|
public virtual async Task<BackgroundJobInfoDto> UpdateAsync(Guid id, BackgroundJobInfoUpdateDto input) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
var resetJob = backgroundJobInfo.JobType == input.JobType; |
||||
|
|
||||
|
UpdateByInput(backgroundJobInfo, input); |
||||
|
|
||||
|
backgroundJobInfo.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
||||
|
|
||||
|
await BackgroundJobManager.UpdateAsync(backgroundJobInfo, resetJob); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
protected virtual void UpdateByInput(BackgroundJobInfo backgroundJobInfo, BackgroundJobInfoCreateOrUpdateDto input) |
||||
|
{ |
||||
|
backgroundJobInfo.IsEnabled = input.IsEnabled; |
||||
|
backgroundJobInfo.LockTimeOut = input.LockTimeOut; |
||||
|
backgroundJobInfo.Description = input.Description; |
||||
|
switch (input.JobType) |
||||
|
{ |
||||
|
case JobType.Once: |
||||
|
backgroundJobInfo.SetOnceJob(input.Interval); |
||||
|
break; |
||||
|
case JobType.Persistent: |
||||
|
backgroundJobInfo.SetPersistentJob(input.Interval); |
||||
|
break; |
||||
|
case JobType.Period: |
||||
|
backgroundJobInfo.SetPeriodJob(input.Cron); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class TaskManagementApplicationMapperProfile : Profile |
||||
|
{ |
||||
|
public TaskManagementApplicationMapperProfile() |
||||
|
{ |
||||
|
CreateMap<BackgroundJobInfo, BackgroundJobInfoDto>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Application; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(TaskManagementApplicationContractsModule))] |
||||
|
[DependsOn(typeof(TaskManagementDomainModule))] |
||||
|
[DependsOn(typeof(AbpDddApplicationModule))] |
||||
|
public class TaskManagementApplicationModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<TaskManagementApplicationModule>(); |
||||
|
|
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<TaskManagementApplicationMapperProfile>(validate: true); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using LINGYUN.Abp.TaskManagement.Localization; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public abstract class TaskManagementApplicationService : ApplicationService |
||||
|
{ |
||||
|
protected TaskManagementApplicationService() |
||||
|
{ |
||||
|
LocalizationResource = typeof(TaskManagementResource); |
||||
|
ObjectMapperContext = typeof(TaskManagementApplicationModule); |
||||
|
} |
||||
|
} |
||||
@ -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,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\TaskManagement\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\TaskManagement\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Validation" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public static class BackgroundJobInfoConsts |
||||
|
{ |
||||
|
public static int MaxCronLength { get; set; } = 50; |
||||
|
public static int MaxNameLength { get; set; } = 100; |
||||
|
public static int MaxGroupLength { get; set; } = 50; |
||||
|
public static int MaxTypeLength { get; set; } = 200; |
||||
|
public static int MaxDescriptionLength { get; set; } = 255; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public static class BackgroundJobLogConsts |
||||
|
{ |
||||
|
public static int MaxMessageLength { get; set; } = 1000; |
||||
|
public static int MaxExceptionLength { get; set; } = 2000; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Permission:TaskManagement": "TaskManagement" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Permission:TaskManagement": "任务管理" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("TaskManagement")] |
||||
|
public class TaskManagementResource |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using LINGYUN.Abp.TaskManagement.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Localization.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(AbpValidationModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] |
||||
|
public class TaskManagementDomainSharedModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<TaskManagementDomainSharedModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<TaskManagementResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/TaskManagement/Localization/Resources"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpExceptionLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.MapCodeNamespace(TaskManagementErrorCodes.Namespace, typeof(TaskManagementResource)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement |
||||
|
{ |
||||
|
public static class TaskManagementErrorCodes |
||||
|
{ |
||||
|
public const string Namespace = "TaskManagement"; |
||||
|
|
||||
|
public const string JobNameAlreadyExists = Namespace + ":01000"; |
||||
|
} |
||||
|
} |
||||
@ -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>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,162 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfo : AuditedAggregateRoot<Guid> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public virtual string Name { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public virtual string Group { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public virtual string Type { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public virtual ExtraPropertyDictionary Args { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public virtual JobStatus Status { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsEnabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public virtual string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public virtual int LockTimeOut { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public virtual DateTime BeginTime { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public virtual DateTime? EndTime { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 上次执行时间
|
||||
|
/// </summary>
|
||||
|
public virtual DateTime? LastRunTime { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 下次执行时间
|
||||
|
/// </summary>
|
||||
|
public virtual DateTime? NextRunTime { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public virtual JobType JobType { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
public virtual string Cron { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public virtual JobPriority Priority { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public virtual int TriggerCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试次数
|
||||
|
/// </summary>
|
||||
|
public virtual int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public virtual int MaxTryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public virtual int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public virtual int Interval { get; protected set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsAbandoned { get; set; } |
||||
|
|
||||
|
protected BackgroundJobInfo() { } |
||||
|
|
||||
|
public BackgroundJobInfo( |
||||
|
Guid id, |
||||
|
string name, |
||||
|
string group, |
||||
|
string type, |
||||
|
IDictionary<string, object> args, |
||||
|
DateTime beginTime, |
||||
|
DateTime? endTime = null, |
||||
|
JobPriority priority = JobPriority.Normal, |
||||
|
int maxCount = 0, |
||||
|
int maxTryCount = 50) : base(id) |
||||
|
{ |
||||
|
Name = name; |
||||
|
Group = group; |
||||
|
Type = type; |
||||
|
Priority = priority; |
||||
|
BeginTime = beginTime; |
||||
|
EndTime = endTime; |
||||
|
|
||||
|
MaxCount = maxCount; |
||||
|
MaxTryCount = maxTryCount; |
||||
|
|
||||
|
Status = JobStatus.Running; |
||||
|
|
||||
|
Args = new ExtraPropertyDictionary(); |
||||
|
Args.AddIfNotContains(args); |
||||
|
} |
||||
|
|
||||
|
public void SetPeriodJob(string cron) |
||||
|
{ |
||||
|
Cron = cron; |
||||
|
JobType = JobType.Period; |
||||
|
} |
||||
|
|
||||
|
public void SetOnceJob(int interval) |
||||
|
{ |
||||
|
Interval = interval; |
||||
|
JobType = JobType.Once; |
||||
|
} |
||||
|
|
||||
|
public void SetPersistentJob(int interval) |
||||
|
{ |
||||
|
Interval = interval; |
||||
|
JobType = JobType.Persistent; |
||||
|
} |
||||
|
|
||||
|
public void SetLastRunTime(DateTime? lastRunTime) |
||||
|
{ |
||||
|
LastRunTime = lastRunTime; |
||||
|
} |
||||
|
|
||||
|
public void SetNextRunTime(DateTime? nextRunTime) |
||||
|
{ |
||||
|
NextRunTime = nextRunTime; |
||||
|
} |
||||
|
|
||||
|
public void SetStatus(JobStatus status) |
||||
|
{ |
||||
|
Status = status; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 后台任务过滤
|
||||
|
/// </summary>
|
||||
|
public class BackgroundJobInfoFilter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus? Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次起始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次截止触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 起始创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 截止创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否已放弃任务
|
||||
|
/// </summary>
|
||||
|
public bool? IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否持续性任务
|
||||
|
/// </summary>
|
||||
|
public bool? IsPeriod { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority? Priority { get; set; } |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLog : Entity<long> |
||||
|
{ |
||||
|
public virtual Guid? JobId { get; set; } |
||||
|
public virtual string JobName { get; protected set; } |
||||
|
public virtual string JobGroup { get; protected set; } |
||||
|
public virtual string JobType { get; protected set; } |
||||
|
public virtual string Message { get; protected set; } |
||||
|
public virtual DateTime RunTime { get; protected set; } |
||||
|
public virtual string Exception { get; protected set; } |
||||
|
protected BackgroundJobLog() { } |
||||
|
public BackgroundJobLog(string type, string group, string name) |
||||
|
{ |
||||
|
JobType = type; |
||||
|
JobGroup = group; |
||||
|
JobName = name; |
||||
|
RunTime = DateTime.Now; |
||||
|
} |
||||
|
|
||||
|
public BackgroundJobLog SetMessage(string message, Exception ex) |
||||
|
{ |
||||
|
Message = message.Length > BackgroundJobLogConsts.MaxMessageLength |
||||
|
? message.Substring(0, BackgroundJobLogConsts.MaxMessageLength - 1) |
||||
|
: message; |
||||
|
|
||||
|
if (ex != null) |
||||
|
{ |
||||
|
var errMsg = ex.ToString(); |
||||
|
Exception = errMsg.Length > BackgroundJobLogConsts.MaxExceptionLength |
||||
|
? errMsg.Substring(0, BackgroundJobLogConsts.MaxExceptionLength - 1) |
||||
|
: errMsg; |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLogFilter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 存在异常
|
||||
|
/// </summary>
|
||||
|
public bool? HasExceptions { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndRunTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobManager : DomainService |
||||
|
{ |
||||
|
protected IObjectMapper ObjectMapper { get; } |
||||
|
protected IJobScheduler JobScheduler { get; } |
||||
|
protected IBackgroundJobInfoRepository BackgroundJobInfoRepository { get; } |
||||
|
|
||||
|
public BackgroundJobManager( |
||||
|
IObjectMapper objectMapper, |
||||
|
IJobScheduler jobScheduler, |
||||
|
IBackgroundJobInfoRepository backgroundJobInfoRepository) |
||||
|
{ |
||||
|
ObjectMapper = objectMapper; |
||||
|
JobScheduler = jobScheduler; |
||||
|
BackgroundJobInfoRepository = backgroundJobInfoRepository; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BackgroundJobInfo> CreateAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
await BackgroundJobInfoRepository.InsertAsync(jobInfo); |
||||
|
|
||||
|
if (jobInfo.IsEnabled && jobInfo.JobType == JobType.Period) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.QueueAsync(job); |
||||
|
} |
||||
|
|
||||
|
return jobInfo; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BackgroundJobInfo> UpdateAsync(BackgroundJobInfo jobInfo, bool resetJob = false) |
||||
|
{ |
||||
|
await BackgroundJobInfoRepository.UpdateAsync(jobInfo); |
||||
|
|
||||
|
if (!jobInfo.IsEnabled || resetJob) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.RemoveAsync(job); |
||||
|
} |
||||
|
|
||||
|
if (resetJob && jobInfo.JobType == JobType.Period) |
||||
|
{ |
||||
|
await QueueAsync(jobInfo); |
||||
|
} |
||||
|
|
||||
|
return jobInfo; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task DeleteAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.RemoveAsync(job); |
||||
|
|
||||
|
await BackgroundJobInfoRepository.DeleteAsync(jobInfo); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task QueueAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.QueueAsync(job); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task TriggerAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.TriggerAsync(job); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task PauseAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.PauseAsync(job); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ResumeAsync(BackgroundJobInfo jobInfo) |
||||
|
{ |
||||
|
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
await JobScheduler.ResumeAsync(job); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class BackgroundJobStore : IJobStore, ITransientDependency |
||||
|
{ |
||||
|
protected IObjectMapper ObjectMapper { get; } |
||||
|
protected IBackgroundJobInfoRepository JobInfoRepository { get; } |
||||
|
protected IBackgroundJobLogRepository JobLogRepository { get; } |
||||
|
|
||||
|
public BackgroundJobStore( |
||||
|
IObjectMapper objectMapper, |
||||
|
IBackgroundJobInfoRepository jobInfoRepository, |
||||
|
IBackgroundJobLogRepository jobLogRepository) |
||||
|
{ |
||||
|
ObjectMapper = objectMapper; |
||||
|
JobInfoRepository = jobInfoRepository; |
||||
|
JobLogRepository = jobLogRepository; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<JobInfo>> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobInfos = await JobInfoRepository.GetAllPeriodTasksAsync(cancellationToken); |
||||
|
|
||||
|
return ObjectMapper.Map<List<BackgroundJobInfo>, List<JobInfo>>(jobInfos); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<JobInfo>> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobInfos = await JobInfoRepository.GetWaitingListAsync(maxResultCount, cancellationToken); |
||||
|
|
||||
|
return ObjectMapper.Map<List<BackgroundJobInfo>, List<JobInfo>>(jobInfos); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<JobInfo> FindAsync(Guid jobId) |
||||
|
{ |
||||
|
var jobInfo = await JobInfoRepository.FindAsync(jobId); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public async virtual Task StoreAsync(JobInfo jobInfo) |
||||
|
{ |
||||
|
var backgroundJobInfo = await JobInfoRepository.FindAsync(jobInfo.Id); |
||||
|
if (backgroundJobInfo != null) |
||||
|
{ |
||||
|
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime); |
||||
|
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime); |
||||
|
backgroundJobInfo.SetStatus(jobInfo.Status); |
||||
|
backgroundJobInfo.TriggerCount = jobInfo.TriggerCount; |
||||
|
backgroundJobInfo.TryCount = jobInfo.TryCount; |
||||
|
backgroundJobInfo.IsAbandoned = jobInfo.IsAbandoned; |
||||
|
|
||||
|
await JobInfoRepository.UpdateAsync(backgroundJobInfo); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
backgroundJobInfo = new BackgroundJobInfo( |
||||
|
jobInfo.Id, |
||||
|
jobInfo.Name, |
||||
|
jobInfo.Group, |
||||
|
jobInfo.Type, |
||||
|
jobInfo.Args, |
||||
|
jobInfo.BeginTime, |
||||
|
jobInfo.EndTime, |
||||
|
jobInfo.Priority, |
||||
|
jobInfo.MaxCount, |
||||
|
jobInfo.MaxTryCount); |
||||
|
|
||||
|
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime); |
||||
|
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime); |
||||
|
backgroundJobInfo.SetStatus(jobInfo.Status); |
||||
|
backgroundJobInfo.TriggerCount = jobInfo.TriggerCount; |
||||
|
backgroundJobInfo.IsAbandoned = jobInfo.IsAbandoned; |
||||
|
backgroundJobInfo.TryCount = jobInfo.TryCount; |
||||
|
backgroundJobInfo.LockTimeOut = jobInfo.LockTimeOut; |
||||
|
backgroundJobInfo.Description = jobInfo.Description; |
||||
|
switch (jobInfo.JobType) |
||||
|
{ |
||||
|
case JobType.Once: |
||||
|
backgroundJobInfo.SetOnceJob(jobInfo.Interval); |
||||
|
break; |
||||
|
case JobType.Persistent: |
||||
|
backgroundJobInfo.SetPersistentJob(jobInfo.Interval); |
||||
|
break; |
||||
|
case JobType.Period: |
||||
|
backgroundJobInfo.SetPeriodJob(jobInfo.Cron); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
await JobInfoRepository.InsertAsync(backgroundJobInfo); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public async virtual Task StoreLogAsync(JobEventData eventData) |
||||
|
{ |
||||
|
var jogLog = new BackgroundJobLog( |
||||
|
eventData.Type.Name, |
||||
|
eventData.Group, |
||||
|
eventData.Name) |
||||
|
{ |
||||
|
JobId = eventData.Key |
||||
|
}; |
||||
|
|
||||
|
jogLog.SetMessage( |
||||
|
eventData.Exception == null ? eventData.Result ?? "OK" : "Failed", |
||||
|
eventData.Exception); |
||||
|
|
||||
|
await JobLogRepository.InsertAsync(jogLog); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public async virtual Task CleanupAsync( |
||||
|
int maxResultCount, |
||||
|
TimeSpan jobExpiratime, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobs = await JobInfoRepository.GetExpiredJobsAsync( |
||||
|
maxResultCount, |
||||
|
jobExpiratime, |
||||
|
cancellationToken); |
||||
|
|
||||
|
await JobInfoRepository.DeleteManyAsync(jobs, cancellationToken: cancellationToken); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobInfoRepository : IRepository<BackgroundJobInfo, Guid> |
||||
|
{ |
||||
|
Task<bool> CheckNameAsync( |
||||
|
string group, |
||||
|
string name, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取过期任务列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="maxResultCount"></param>
|
||||
|
/// <param name="jobExpiratime"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<BackgroundJobInfo>> GetExpiredJobsAsync( |
||||
|
int maxResultCount, |
||||
|
TimeSpan jobExpiratime, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取所有周期性任务
|
||||
|
/// 指定了Cron表达式的任务需要作为持续性任务交给任务引擎
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<BackgroundJobInfo>> GetAllPeriodTasksAsync( |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取等待入队的任务列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="maxResultCount"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<BackgroundJobInfo>> GetWaitingListAsync( |
||||
|
int maxResultCount, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取过滤后的任务数量
|
||||
|
/// </summary>
|
||||
|
/// <param name="filter"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<int> GetCountAsync( |
||||
|
BackgroundJobInfoFilter filter, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取过滤后的任务列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="filter"></param>
|
||||
|
/// <param name="sorting"></param>
|
||||
|
/// <param name="maxResultCount"></param>
|
||||
|
/// <param name="skipCount"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<BackgroundJobInfo>> GetListAsync( |
||||
|
BackgroundJobInfoFilter filter, |
||||
|
string sorting = nameof(BackgroundJobInfo.Name), |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 0, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobLogRepository : IRepository<BackgroundJobLog, long> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取过滤后的任务日志数量
|
||||
|
/// </summary>
|
||||
|
/// <param name="filter"></param>
|
||||
|
/// <param name="jobId"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<int> GetCountAsync( |
||||
|
BackgroundJobLogFilter filter, |
||||
|
Guid? jobId = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
/// <summary>
|
||||
|
/// 获取过滤后的任务日志列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="filter"></param>
|
||||
|
/// <param name="jobId"></param>
|
||||
|
/// <param name="sorting"></param>
|
||||
|
/// <param name="maxResultCount"></param>
|
||||
|
/// <param name="skipCount"></param>
|
||||
|
/// <param name="cancellationToken"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<List<BackgroundJobLog>> GetListAsync( |
||||
|
BackgroundJobLogFilter filter, |
||||
|
Guid? jobId = null, |
||||
|
string sorting = nameof(BackgroundJobLog.RunTime), |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 0, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public static class TaskManagementDbProperties |
||||
|
{ |
||||
|
public static string DbTablePrefix { get; set; } = "TK_"; |
||||
|
|
||||
|
public static string DbSchema { get; set; } = null; |
||||
|
|
||||
|
|
||||
|
public const string ConnectionStringName = "TaskManagement"; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using AutoMapper; |
||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class TaskManagementDomainMapperProfile : Profile |
||||
|
{ |
||||
|
public TaskManagementDomainMapperProfile() |
||||
|
{ |
||||
|
CreateMap<BackgroundJobInfo, JobInfo>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Domain; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(TaskManagementDomainSharedModule))] |
||||
|
[DependsOn(typeof(AbpAutoMapperModule))] |
||||
|
[DependsOn(typeof(AbpDddDomainModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundTasksModule))] |
||||
|
public class TaskManagementDomainModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<TaskManagementDomainModule>(); |
||||
|
|
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<TaskManagementDomainMapperProfile>(validate: true); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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,19 @@ |
|||||
|
<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.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,124 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCoreBackgroundJobInfoRepository : |
||||
|
EfCoreRepository<TaskManagementDbContext, BackgroundJobInfo, Guid>, |
||||
|
IBackgroundJobInfoRepository |
||||
|
{ |
||||
|
protected IClock Clock { get; } |
||||
|
|
||||
|
public EfCoreBackgroundJobInfoRepository( |
||||
|
IClock clock, |
||||
|
IDbContextProvider<TaskManagementDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> CheckNameAsync( |
||||
|
string group, |
||||
|
string name, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.AllAsync(x => x.Group.Equals(group) && x.Name.Equals(name), |
||||
|
GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<BackgroundJobInfo>> GetExpiredJobsAsync( |
||||
|
int maxResultCount, |
||||
|
TimeSpan jobExpiratime, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var expiratime = Clock.Now - jobExpiratime; |
||||
|
|
||||
|
return await (await GetDbSetAsync()) |
||||
|
.Where(x => x.Status == JobStatus.Completed && |
||||
|
DateTime.Compare(x.LastRunTime.Value, expiratime) <= 0) |
||||
|
.OrderBy(x => x.CreationTime) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<BackgroundJobInfo>> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.Where(x => x.IsEnabled && !x.IsAbandoned) |
||||
|
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running) |
||||
|
.Where(x => x.TriggerCount < x.MaxCount && x.TryCount < x.MaxTryCount) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<int> GetCountAsync(BackgroundJobInfoFilter filter, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(!filter.Type.IsNullOrWhiteSpace(), x => x.Type.Contains(filter.Type)) |
||||
|
.WhereIf(!filter.Group.IsNullOrWhiteSpace(), x => x.Group.Equals(filter.Group)) |
||||
|
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.Name.Equals(filter.Name)) |
||||
|
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter.Filter) || |
||||
|
x.Group.Contains(filter.Filter) || x.Type.Contains(filter.Filter) || x.Description.Contains(filter.Filter)) |
||||
|
.WhereIf(filter.IsPeriod.HasValue && filter.IsPeriod.Value, x => x.JobType == JobType.Period) |
||||
|
.WhereIf(filter.IsPeriod.HasValue && !filter.IsPeriod.Value, x => x.JobType != JobType.Period) |
||||
|
.WhereIf(filter.Priority.HasValue, x => x.Priority == filter.Priority.Value) |
||||
|
.WhereIf(filter.Status.HasValue, x => x.Status == filter.Status.Value) |
||||
|
.WhereIf(filter.IsAbandoned.HasValue, x => x.IsAbandoned == filter.IsAbandoned.Value) |
||||
|
.WhereIf(filter.BeginLastRunTime.HasValue, x => filter.BeginLastRunTime.Value.CompareTo(x.LastRunTime) <= 0) |
||||
|
.WhereIf(filter.EndLastRunTime.HasValue, x => filter.EndLastRunTime.Value.CompareTo(x.LastRunTime) >= 0) |
||||
|
.WhereIf(filter.BeginTime.HasValue, x => x.BeginTime.CompareTo(x.BeginTime) >= 0) |
||||
|
.WhereIf(filter.EndTime.HasValue, x => filter.EndTime.Value.CompareTo(x.EndTime) >= 0) |
||||
|
.WhereIf(filter.BeginCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.BeginCreationTime.Value) >= 0) |
||||
|
.WhereIf(filter.EndCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.EndCreationTime.Value) <= 0) |
||||
|
.CountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<BackgroundJobInfo>> GetListAsync(BackgroundJobInfoFilter filter, string sorting = "Name", int maxResultCount = 10, int skipCount = 0, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(!filter.Type.IsNullOrWhiteSpace(), x => x.Type.Contains(filter.Type)) |
||||
|
.WhereIf(!filter.Group.IsNullOrWhiteSpace(), x => x.Group.Equals(filter.Group)) |
||||
|
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.Name.Equals(filter.Name)) |
||||
|
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter.Filter) || |
||||
|
x.Group.Contains(filter.Filter) || x.Type.Contains(filter.Filter) || x.Description.Contains(filter.Filter)) |
||||
|
.WhereIf(filter.IsPeriod.HasValue && filter.IsPeriod.Value, x => !string.IsNullOrWhiteSpace(x.Cron)) |
||||
|
.WhereIf(filter.IsPeriod.HasValue && !filter.IsPeriod.Value, x => string.IsNullOrWhiteSpace(x.Cron)) |
||||
|
.WhereIf(filter.Status.HasValue, x => x.Status == filter.Status.Value) |
||||
|
.WhereIf(filter.Priority.HasValue, x => x.Priority == filter.Priority.Value) |
||||
|
.WhereIf(filter.IsAbandoned.HasValue, x => x.IsAbandoned == filter.IsAbandoned.Value) |
||||
|
.WhereIf(filter.BeginLastRunTime.HasValue, x => filter.BeginLastRunTime.Value.CompareTo(x.LastRunTime) <= 0) |
||||
|
.WhereIf(filter.EndLastRunTime.HasValue, x => filter.EndLastRunTime.Value.CompareTo(x.LastRunTime) >= 0) |
||||
|
.WhereIf(filter.BeginTime.HasValue, x => x.BeginTime.CompareTo(x.BeginTime) >= 0) |
||||
|
.WhereIf(filter.EndTime.HasValue, x => filter.EndTime.Value.CompareTo(x.EndTime) >= 0) |
||||
|
.WhereIf(filter.BeginCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.BeginCreationTime.Value) >= 0) |
||||
|
.WhereIf(filter.EndCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.EndCreationTime.Value) <= 0) |
||||
|
.OrderBy(sorting ?? nameof(BackgroundJobInfo.Name)) |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<BackgroundJobInfo>> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var now = Clock.Now; |
||||
|
|
||||
|
return await (await GetDbSetAsync()) |
||||
|
.Where(x => x.IsEnabled && !x.IsAbandoned) |
||||
|
.Where(x => x.JobType != JobType.Period && x.Status == JobStatus.Running) |
||||
|
.Where(x => x.TriggerCount < x.MaxCount && x.TryCount < x.MaxTryCount) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ThenBy(x => x.TryCount) |
||||
|
.ThenBy(x => x.NextRunTime) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCoreBackgroundJobLogRepository : |
||||
|
EfCoreRepository<TaskManagementDbContext, BackgroundJobLog, long>, |
||||
|
IBackgroundJobLogRepository |
||||
|
{ |
||||
|
public EfCoreBackgroundJobLogRepository( |
||||
|
IDbContextProvider<TaskManagementDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<int> GetCountAsync( |
||||
|
BackgroundJobLogFilter filter, |
||||
|
Guid? jobId = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(jobId.HasValue, x => x.JobId.Equals(jobId)) |
||||
|
.WhereIf(!filter.Type.IsNullOrWhiteSpace(), x => x.JobType.Contains(filter.Type)) |
||||
|
.WhereIf(!filter.Group.IsNullOrWhiteSpace(), x => x.JobGroup.Equals(filter.Group)) |
||||
|
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.JobName.Equals(filter.Name)) |
||||
|
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.JobName.Contains(filter.Filter) || |
||||
|
x.JobGroup.Contains(filter.Filter) || x.JobType.Contains(filter.Filter) || x.Message.Contains(filter.Filter)) |
||||
|
.WhereIf(filter.HasExceptions.HasValue, x => !string.IsNullOrWhiteSpace(x.Exception)) |
||||
|
.WhereIf(filter.BeginRunTime.HasValue, x => x.RunTime.CompareTo(filter.BeginRunTime.Value) >= 0) |
||||
|
.WhereIf(filter.EndRunTime.HasValue, x => x.RunTime.CompareTo(filter.EndRunTime.Value) <= 0) |
||||
|
.CountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<List<BackgroundJobLog>> GetListAsync( |
||||
|
BackgroundJobLogFilter filter, |
||||
|
Guid? jobId = null, |
||||
|
string sorting = nameof(BackgroundJobLog.RunTime), |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 0, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetDbSetAsync()) |
||||
|
.WhereIf(jobId.HasValue, x => x.JobId.Equals(jobId)) |
||||
|
.WhereIf(!filter.Type.IsNullOrWhiteSpace(), x => x.JobType.Contains(filter.Type)) |
||||
|
.WhereIf(!filter.Group.IsNullOrWhiteSpace(), x => x.JobGroup.Equals(filter.Group)) |
||||
|
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.JobName.Equals(filter.Name)) |
||||
|
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.JobName.Contains(filter.Filter) || |
||||
|
x.JobGroup.Contains(filter.Filter) || x.JobType.Contains(filter.Filter) || x.Message.Contains(filter.Filter)) |
||||
|
.WhereIf(filter.HasExceptions.HasValue, x => !string.IsNullOrWhiteSpace(x.Exception)) |
||||
|
.WhereIf(filter.BeginRunTime.HasValue, x => x.RunTime.CompareTo(filter.BeginRunTime.Value) >= 0) |
||||
|
.WhereIf(filter.EndRunTime.HasValue, x => x.RunTime.CompareTo(filter.EndRunTime.Value) <= 0) |
||||
|
.OrderBy(sorting ?? nameof(BackgroundJobInfo.Name)) |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue