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