diff --git a/aspnet-core/.editorconfig b/aspnet-core/.editorconfig index 2289110b2..9788d77d3 100644 --- a/aspnet-core/.editorconfig +++ b/aspnet-core/.editorconfig @@ -1,4 +1,5 @@ -# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2021-12-27 +# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2022-01-07 +# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location. # You can modify the rules from these initially generated values to suit your own policies # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference [*.cs] @@ -26,8 +27,8 @@ csharp_new_line_before_else = true csharp_new_line_before_members_in_anonymous_types = true #require members of object initializers to be on the same line csharp_new_line_before_members_in_object_initializers = false -#require braces to be on a new line for anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, and anonymous_methods (also known as "Allman" style) -csharp_new_line_before_open_brace = anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, anonymous_methods +#require braces to be on a new line for methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, and anonymous_types (also known as "Allman" style) +csharp_new_line_before_open_brace = methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, anonymous_types #Formatting - organize using options diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 6448be090..cd76d0186 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -378,7 +378,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Sms.Tencent", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BlobStoring.Tencent", "modules\cloud-tencent\LINGYUN.Abp.BlobStoring.Tencent\LINGYUN.Abp.BlobStoring.Tencent.csproj", "{A4B972EC-9F0B-4405-9965-766FABC9B07E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Tencent", "modules\oss-management\LINGYUN.Abp.OssManagement.Tencent\LINGYUN.Abp.OssManagement.Tencent.csproj", "{31E60E23-FD98-4D5E-A137-2B3F2968BA09}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.Tencent", "modules\oss-management\LINGYUN.Abp.OssManagement.Tencent\LINGYUN.Abp.OssManagement.Tencent.csproj", "{31E60E23-FD98-4D5E-A137-2B3F2968BA09}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN.Abp.BackgroundTasks.Abstractions.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN.Abp.BackgroundTasks.Abstractions.csproj new file mode 100644 index 000000000..958d8b36d --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN.Abp.BackgroundTasks.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + + + + netstandard2.0 + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksAbstractionsModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksAbstractionsModule.cs new file mode 100644 index 000000000..3e349c78c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksAbstractionsModule.cs @@ -0,0 +1,7 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.BackgroundTasks; + +public class AbpBackgroundTasksAbstractionsModule : AbpModule +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnable.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnable.cs new file mode 100644 index 000000000..e0471be11 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnable.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BackgroundTasks; + +/// +/// 任务类需要实现此接口 +/// +public interface IJobRunnable +{ + Task ExecuteAsync(JobRunnableContext context); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnableExecuter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnableExecuter.cs new file mode 100644 index 000000000..67bceac9c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnableExecuter.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BackgroundTasks; + +/// +/// 定义任务执行者接口 +/// 可以通过它实现一些限制(例如分布式锁) +/// +public interface IJobRunnableExecuter +{ + Task ExecuteAsync(JobRunnableContext context); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventContext.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventContext.cs new file mode 100644 index 000000000..5fb3042e2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventContext.cs @@ -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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventData.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventData.cs new file mode 100644 index 000000000..fc1852921 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventData.cs @@ -0,0 +1,90 @@ +using System; + +namespace LINGYUN.Abp.BackgroundTasks; + +public class JobEventData +{ + /// + /// 任务类别 + /// + public Type Type { get; } + /// + /// 任务组别 + /// + public string Group { get; } + /// + /// 任务名称 + /// + public string Name { get; } + /// + /// 任务标识 + /// + public Guid Key { get; } + /// + /// 任务状态 + /// + public JobStatus Status { get; set; } + /// + /// 执行者租户 + /// + public Guid? TenantId { get; set; } + /// + /// 错误明细 + /// + public Exception Exception { get; } + /// + /// 任务描述 + /// + public string Description { get; set; } + /// + /// 返回参数 + /// + public string Result { get; set; } + /// + /// 触发次数 + /// + public int Triggered { get; set; } + /// + /// 最大可执行次数 + /// + public int RepeatCount { get; set; } + /// + /// 失败重试上限 + /// 默认:50 + /// + public int TryCount { get; set; } + /// + /// 最大执行次数 + /// 默认:0, 无限制 + /// + public int MaxCount { get; set; } + /// + /// 运行时间 + /// + public DateTime RunTime { get; set; } + /// + /// 上次运行时间 + /// + public DateTime? LastRunTime { get; set; } + /// + /// 下次运行时间 + /// + public DateTime? NextRunTime { get; set; } + /// + /// 连续失败且不会再次执行 + /// + 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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs new file mode 100644 index 000000000..ce724e44e --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.BackgroundTasks; + +public class JobInfo +{ + /// + /// 任务标识 + /// + public Guid Id { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 返回参数 + /// + public string Result { get; set; } + /// + /// 任务参数 + /// + public IDictionary Args { get; set; } + /// + /// 任务状态 + /// + public JobStatus Status { get; set; } = JobStatus.None; + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 创建时间 + /// + public DateTime CreationTime { get; set; } + /// + /// 开始时间 + /// + public DateTime BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 上次运行时间 + /// + public DateTime? LastRunTime { get; set; } + /// + /// 下一次执行时间 + /// + public DateTime? NextRunTime { get; set; } + /// + /// 任务类别 + /// + public JobType JobType { get; set; } = JobType.Once; + /// + /// Cron表达式,如果是持续任务需要指定 + /// + public string Cron { get; set; } + /// + /// 触发次数 + /// + public int TriggerCount { get; set; } + /// + /// 失败重试次数 + /// + public int TryCount { get; set; } + /// + /// 失败重试上限 + /// 默认:50 + /// + public int MaxTryCount { get; set; } = 50; + /// + /// 最大执行次数 + /// 默认:0, 无限制 + /// + public int MaxCount { get; set; } + /// + /// 连续失败且不会再次执行 + /// + public bool IsAbandoned { get; set; } + /// + /// 间隔时间,单位秒,与Cron表达式冲突 + /// 默认: 300 + /// + public int Interval { get; set; } = 300; + /// + /// 任务优先级 + /// + public JobPriority Priority { get; set; } = JobPriority.Normal; + /// + /// 任务独占超时时长(秒) + /// 0或更小不生效 + /// + public int LockTimeOut { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobPriority.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobPriority.cs new file mode 100644 index 000000000..dac91b3fa --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobPriority.cs @@ -0,0 +1,15 @@ +namespace LINGYUN.Abp.BackgroundTasks; +/// +/// 任务优先级 +/// +/// +/// 与框架保持一致 +/// +public enum JobPriority +{ + Low = 5, + BelowNormal = 10, + Normal = 0xF, + AboveNormal = 20, + High = 25 +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContext.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContext.cs new file mode 100644 index 000000000..75af712ad --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContext.cs @@ -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 JobData { get; } + public JobRunnableContext( + Type jobType, + IServiceProvider serviceProvider, + IReadOnlyDictionary jobData) + { + JobType = jobType; + ServiceProvider = serviceProvider; + JobData = jobData; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobStatus.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobStatus.cs new file mode 100644 index 000000000..c46492592 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobStatus.cs @@ -0,0 +1,25 @@ +namespace LINGYUN.Abp.BackgroundTasks; + +public enum JobStatus +{ + /// + /// 未知 + /// + None = -1, + /// + /// 已完成 + /// + Completed = 0, + /// + /// 运行中 + /// + Running = 10, + /// + /// 已暂停 + /// + Paused = 20, + /// + /// 已停止 + /// + Stopped = 30 +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobType.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobType.cs new file mode 100644 index 000000000..2fb57a07f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobType.cs @@ -0,0 +1,19 @@ +namespace LINGYUN.Abp.BackgroundTasks; +/// +/// 任务类别 +/// +public enum JobType +{ + /// + /// 一次性 + /// + Once, + /// + /// 周期性 + /// + Period, + /// + /// 持续性 + /// + Persistent +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj new file mode 100644 index 000000000..386fea967 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj @@ -0,0 +1,19 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs new file mode 100644 index 000000000..983823e05 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs @@ -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(); + + _scheduler.ListenerManager.AddJobListener(context.ServiceProvider.GetRequiredService()); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzJobExecutorProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzJobExecutorProvider.cs new file mode 100644 index 000000000..6fd447334 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzJobExecutorProvider.cs @@ -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 +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs new file mode 100644 index 000000000..ec3ab3501 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs @@ -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 Logger { protected get; set; } + + protected IClock Clock { get; } + protected AbpBackgroundTasksOptions Options { get; } + + public QuartzJobExecutorProvider( + IClock clock, + IOptions options) + { + Clock = clock; + Options = options.Value; + + Logger = NullLogger.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(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs new file mode 100644 index 000000000..7408ffcf2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs @@ -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 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.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}"); + } + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs new file mode 100644 index 000000000..6b87ff4fd --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs @@ -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 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 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 jobs) + { + var jobDictionary = new Dictionary>(); + 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 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 ShutdownAsync() + { + await StopAsync(); + + await Scheduler.Shutdown(true); + + return Scheduler.IsShutdown; + } + + public virtual async Task StartAsync() + { + if (Scheduler.InStandbyMode) + { + await Scheduler.Start(); + } + return Scheduler.InStandbyMode; + } + + public virtual async Task 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?"); + } + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobSimpleAdapter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobSimpleAdapter.cs new file mode 100644 index 000000000..55b65a3af --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobSimpleAdapter.cs @@ -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 : 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(); + + var jobContext = new JobRunnableContext( + typeof(TJobRunnable), + ServiceProvider, + context.MergedJobDataMap.ToImmutableDictionary()); + + await jobExecuter.ExecuteAsync(jobContext); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/Quartz/IJobExecutionContextExtensions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/Quartz/IJobExecutionContextExtensions.cs new file mode 100644 index 000000000..3d46e4614 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/Quartz/IJobExecutionContextExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace Quartz; + +public static class IJobExecutionContextExtensions +{ + public static TValue GetData(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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/README.md b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/README.md new file mode 100644 index 000000000..b1dd81e5b --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/README.md @@ -0,0 +1,16 @@ +# LINGYUN.Abp.BackgroundTasks.Quartz + +后台任务(队列)模块的Quartz实现, 使用任务适配器来做到任务的幂等性控制. +并添加一个监听器用于通知管理者任务状态 + +## 配置使用 + +模块按需引用,具体配置参考Volo.Abp.Quartz模块 + +```csharp +[DependsOn(typeof(AbpBackgroundTasksQuartzModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj new file mode 100644 index 000000000..96b629b02 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj @@ -0,0 +1,22 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs new file mode 100644 index 000000000..205ccd0e9 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs @@ -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(); + } + + private static void AutoAddJobMonitors(IServiceCollection services) + { + var jobMonitors = new List(); + + services.OnRegistred(context => + { + if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(JobEventBase<>))) + { + jobMonitors.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.JobMonitors.AddIfNotContains(jobMonitors); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksOptions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksOptions.cs new file mode 100644 index 000000000..69457817b --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksOptions.cs @@ -0,0 +1,69 @@ +using System; +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.BackgroundTasks; + +public class AbpBackgroundTasksOptions +{ + /// + /// 任务监听类型列表 + /// + /// + /// 用户可以实现事件监听实现自定义逻辑 + /// + public ITypeList JobMonitors { get; } + /// + /// 任务过期时间 + /// 默认: 15 days + /// + /// + /// 任务过期时间,超出时间段清理 + /// + public TimeSpan JobExpiratime { get; set; } + /// + /// 每次清理任务批次大小 + /// 默认: 1000 + /// + public int MaxJobCleanCount { get; set; } + /// + /// 清理过期任务批次Cron表达式 + /// 默认: 600秒(0 0/10 * * * ? * ) + /// + /// + /// Cron表达式 + /// + public string JobCleanCronExpression { get; set; } + /// + /// 每次轮询任务批次大小 + /// 默认: 1000 + /// + public int MaxJobFetchCount { get; set; } + /// + /// 轮询任务批次Cron表达式 + /// 默认: 30秒(0/30 * * * * ? ) + /// + /// + /// Cron表达式 + /// + public string JobFetchCronExpression { get; set; } + /// + /// 轮询任务批次时锁定任务超时时长(秒) + /// 默认:120 + /// + /// + /// 轮询任务也属于一个后台任务, 需要对每一次轮询加锁,防止重复任务入库 + /// + 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(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs new file mode 100644 index 000000000..10e2dfd3a --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs @@ -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 : IJobRunnable +{ + public ILogger> Logger { protected get; set; } + + protected AbpBackgroundJobOptions Options { get; } + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected IBackgroundJobExecuter JobExecuter { get; } + + public BackgroundJobAdapter( + IOptions options, + IBackgroundJobExecuter jobExecuter, + IServiceScopeFactory serviceScopeFactory) + { + JobExecuter = jobExecuter; + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; + + Logger = NullLogger>.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); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs new file mode 100644 index 000000000..9df770d40 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs @@ -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 options) + { + Clock = clock; + JobStore = jobStore; + GuidGenerator = guidGenerator; + Options = options.Value; + } + + public virtual async Task EnqueueAsync( + TArgs args, + BackgroundJobPriority priority = BackgroundJobPriority.Normal, + TimeSpan? delay = null) + { + var jobConfiguration = Options.GetJob(); + var interval = 60; + if (delay.HasValue) + { + interval = delay.Value.Seconds; + } + var jobId = GuidGenerator.Create(); + var jobArgs = new Dictionary + { + { nameof(TArgs), args }, + { "ArgsType", jobConfiguration.ArgsType.AssemblyQualifiedName }, + { "JobType", typeof(BackgroundJobAdapter).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).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, + }; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEvent.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEvent.cs new file mode 100644 index 000000000..3775644b4 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEvent.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.BackgroundTasks; + +/// +/// 挂载任务事件接口 +/// +public interface IJobEvent +{ + /// + /// 任务启动前事件 + /// + /// + /// + Task OnJobBeforeExecuted(JobEventContext context); + /// + /// 任务完成后事件 + /// + /// + /// + Task OnJobAfterExecuted(JobEventContext context); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEventProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEventProvider.cs new file mode 100644 index 000000000..9b3dc6f09 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEventProvider.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.BackgroundTasks; + +/// +/// 任务事件提供者 +/// +public interface IJobEventProvider +{ + /// + /// 返回所有任务事件注册接口 + /// + /// + IReadOnlyCollection GetAll(); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobScheduler.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobScheduler.cs new file mode 100644 index 000000000..4ec76be9a --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobScheduler.cs @@ -0,0 +1,72 @@ +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace LINGYUN.Abp.BackgroundTasks; +/// +/// 作业调度接口 +/// +public interface IJobScheduler +{ + /// + /// 任务入队 + /// + /// + /// + Task QueueAsync(JobInfo job); + /// + /// 任务入队 + /// + /// + /// + Task QueuesAsync(IEnumerable jobs); + /// + /// 任务是否存在 + /// + /// + /// + /// + Task ExistsAsync(JobInfo job); + /// + /// 触发任务 + /// + /// + /// + /// + Task TriggerAsync(JobInfo job); + /// + /// 暂停任务 + /// + /// + /// + /// + Task PauseAsync(JobInfo job); + /// + /// 恢复暂停的任务 + /// + /// + /// + /// + Task ResumeAsync(JobInfo job); + /// + /// 移除任务 + /// + /// + /// + /// + Task RemoveAsync(JobInfo job); + /// + /// 启动任务协调器 + /// + /// + Task StartAsync(); + /// + /// 停止任务协调器 + /// + /// + Task StopAsync(); + /// + /// 释放任务协调器 + /// + /// + Task ShutdownAsync(); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs new file mode 100644 index 000000000..9dd69ca0c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs @@ -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> GetWaitingListAsync( + int maxResultCount, + CancellationToken cancellationToken = default); + + Task> GetAllPeriodTasksAsync( + CancellationToken cancellationToken = default); + + Task FindAsync(Guid jobId); + + Task StoreAsync(JobInfo jobInfo); + + Task StoreLogAsync(JobEventData eventData); + + Task CleanupAsync( + int maxResultCount, + TimeSpan jobExpiratime, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs new file mode 100644 index 000000000..ee767808d --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs @@ -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>().Value; + var store = context.ServiceProvider.GetRequiredService(); + + await store.CleanupAsync( + options.MaxJobCleanCount, + options.JobExpiratime); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs new file mode 100644 index 000000000..1f47228c2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs @@ -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(); + + var periodJobs = await store.GetAllPeriodTasksAsync(); + + if (!periodJobs.Any()) + { + return; + } + + var jobScheduler = context.ServiceProvider.GetRequiredService(); + + await jobScheduler.QueuesAsync(periodJobs); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs new file mode 100644 index 000000000..699c3bc72 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs @@ -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>().Value; + var store = context.ServiceProvider.GetRequiredService(); + + var waitingJobs = await store.GetWaitingListAsync(options.MaxJobFetchCount); + + if (!waitingJobs.Any()) + { + return; + } + + var jobScheduler = context.ServiceProvider.GetRequiredService(); + + foreach (var job in waitingJobs) + { + await jobScheduler.QueueAsync(job); + } + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs new file mode 100644 index 000000000..e86ae7879 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs @@ -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 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(), + 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(), + 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(), + Status = JobStatus.Running, + BeginTime = DateTime.Now, + CreationTime = DateTime.Now, + Cron = _options.JobCleanCronExpression, + JobType = JobType.Period, + Priority = JobPriority.High, + Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName, + }; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs new file mode 100644 index 000000000..b6c654ebc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs @@ -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 _memoryJobStore; + + public InMemoryJobStore() + { + _memoryJobStore = new List(); + } + + public Task> 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> 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 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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobEventProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobEventProvider.cs new file mode 100644 index 000000000..b0e6e5da2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobEventProvider.cs @@ -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> _lazyEvents; + private List _events => _lazyEvents.Value; + + private readonly IServiceProvider _serviceProvider; + private readonly AbpBackgroundTasksOptions _options; + public JobEventProvider( + IOptions options, + IServiceProvider serviceProvider) + { + _options = options.Value; + _serviceProvider = serviceProvider; + + _lazyEvents = new Lazy>(CreateJobEvents); + } + public IReadOnlyCollection GetAll() + { + return _events.ToImmutableList(); + } + + private List CreateJobEvents() + { + var jobEvents = _options + .JobMonitors + .Select(p => _serviceProvider.GetRequiredService(p) as IJobEvent) + .ToList(); + + return jobEvents; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs new file mode 100644 index 000000000..dc64301b2 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs @@ -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, ITransientDependency +{ + protected override async Task OnJobAfterExecutedAsync(JobEventContext context) + { + var store = context.ServiceProvider.GetRequiredService(); + + 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(); + await jobScheduler.RemoveAsync(jobInfo); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs new file mode 100644 index 000000000..6fb015762 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs @@ -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; + +/// +/// 存储任务日志 +/// +/// +/// 任务类型标记了 特性则不会记录日志 +/// +internal class JobLogEvent : JobEventBase, ITransientDependency +{ + protected async override Task OnJobAfterExecutedAsync(JobEventContext context) + { + if (context.EventData.Type.IsDefined(typeof(DisableAuditingAttribute), true)) + { + return; + } + var store = context.ServiceProvider.GetRequiredService(); + + await store.StoreLogAsync(context.EventData); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobEventBase.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobEventBase.cs new file mode 100644 index 000000000..a7174915f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobEventBase.cs @@ -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 : IJobEvent +{ + public ILogger Logger { protected get; set; } + protected JobEventBase() + { + Logger = NullLogger.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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs new file mode 100644 index 000000000..24ad6721f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs @@ -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(); + 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(); + 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); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/ConsoleJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/ConsoleJob.cs new file mode 100644 index 000000000..a108d492c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/ConsoleJob.cs @@ -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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/EmailingJob.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/EmailingJob.cs new file mode 100644 index 000000000..b8e3f95e8 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Primitives/EmailingJob.cs @@ -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) + { + + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/README.md b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/README.md new file mode 100644 index 000000000..a636695dc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/README.md @@ -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(); + + // 将周期性(5秒一次)任务添加到队列 + await scheduler.QueueAsync(new JobInfo + { + Type = typeof(ConsoleJob).AssemblyQualifiedName, + Args = new Dictionary(), + 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(), + 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(), + 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(); + 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, ITransientDependency +{ + public override Task ExecuteAsync(SmsJobArgs args) + { + Console.WriteLine($"Send sms message: {args.Message}"); + + return Task.CompletedTask; + } +} +``` diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj new file mode 100644 index 000000000..d7b51dbfa --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj @@ -0,0 +1,19 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs new file mode 100644 index 000000000..fe774ecec --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs @@ -0,0 +1,17 @@ +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobInfoCreateDto : BackgroundJobInfoCreateOrUpdateDto +{ + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs new file mode 100644 index 000000000..e2e17e87e --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs @@ -0,0 +1,61 @@ +using LINGYUN.Abp.BackgroundTasks; +using System; +using Volo.Abp.Data; + +namespace LINGYUN.Abp.TaskManagement; + +public abstract class BackgroundJobInfoCreateOrUpdateDto +{ + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + /// + /// 任务参数 + /// + public ExtraPropertyDictionary Args { get; set; } + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 开始时间 + /// + public DateTime BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 任务类别 + /// + public JobType JobType { get; set; } + /// + /// Cron表达式,如果是持续任务需要指定 + /// + public string Cron { get; set; } + /// + /// 失败重试上限 + /// 默认:50 + /// + public int MaxTryCount { get; set; } + /// + /// 最大执行次数 + /// 默认:0, 无限制 + /// + public int MaxCount { get; set; } + /// + /// 间隔时间,单位秒,与Cron表达式冲突 + /// 默认: 300 + /// + public int Interval { get; set; } = 300; + /// + /// 任务优先级 + /// + public JobPriority Priority { get; set; } + /// + /// 任务独占超时时长(秒) + /// 0或更小不生效 + /// + public int LockTimeOut { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs new file mode 100644 index 000000000..7506f0b38 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs @@ -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 +{ + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 返回参数 + /// + public string Result { get; set; } + /// + /// 任务参数 + /// + public ExtraPropertyDictionary Args { get; set; } + /// + /// 任务状态 + /// + public JobStatus Status { get; set; } + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 开始时间 + /// + public DateTime BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 上次运行时间 + /// + public DateTime? LastRunTime { get; set; } + /// + /// 下一次执行时间 + /// + public DateTime? NextRunTime { get; set; } + /// + /// 任务类别 + /// + public JobType JobType { get; set; } + /// + /// Cron表达式,如果是持续任务需要指定 + /// + public string Cron { get; set; } + /// + /// 触发次数 + /// + public int TriggerCount { get; set; } + /// + /// 失败重试次数 + /// + public int TryCount { get; set; } + /// + /// 失败重试上限 + /// 默认:50 + /// + public int MaxTryCount { get; set; } + /// + /// 最大执行次数 + /// 默认:0, 无限制 + /// + public int MaxCount { get; set; } + /// + /// 连续失败且不会再次执行 + /// + public bool IsAbandoned { get; set; } + /// + /// 间隔时间,单位秒,与Cron表达式冲突 + /// 默认: 300 + /// + public int Interval { get; set; } = 300; + /// + /// 任务优先级 + /// + public JobPriority Priority { get; set; } + /// + /// 任务独占超时时长(秒) + /// 0或更小不生效 + /// + public int LockTimeOut { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs new file mode 100644 index 000000000..1151449dc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs @@ -0,0 +1,65 @@ +using LINGYUN.Abp.BackgroundTasks; +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobInfoGetListInput: PagedAndSortedResultRequestDto +{ + /// + /// 其他过滤条件 + /// + public string Filter { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 任务状态 + /// + public JobStatus? Status { get; set; } + /// + /// 开始时间 + /// + public DateTime? BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 上次起始触发时间 + /// + public DateTime? BeginLastRunTime { get; set; } + /// + /// 上次截止触发时间 + /// + public DateTime? EndLastRunTime { get; set; } + /// + /// 起始创建时间 + /// + public DateTime? BeginCreationTime { get; set; } + /// + /// 截止创建时间 + /// + public DateTime? EndCreationTime { get; set; } + /// + /// 是否已放弃任务 + /// + public bool? IsAbandoned { get; set; } + /// + /// 是否持续性任务 + /// + public bool? IsPeriod { get; set; } + /// + /// 优先级 + /// + public JobPriority? Priority { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoUpdateDto.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoUpdateDto.cs new file mode 100644 index 000000000..17279a3b1 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoUpdateDto.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobInfoUpdateDto : BackgroundJobInfoCreateOrUpdateDto, IHasConcurrencyStamp +{ + public string ConcurrencyStamp { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogDto.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogDto.cs new file mode 100644 index 000000000..3411c5e9a --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobLogDto : EntityDto +{ + 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; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogGetListInput.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogGetListInput.cs new file mode 100644 index 000000000..2967be0aa --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogGetListInput.cs @@ -0,0 +1,36 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobLogGetListInput : PagedAndSortedResultRequestDto +{ + /// + /// 其他过滤条件 + /// + public string Filter { get; set; } + /// + /// 存在异常 + /// + public bool? HasExceptions { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 开始触发时间 + /// + public DateTime? BeginRunTime { get; set; } + /// + /// 结束触发时间 + /// + public DateTime? EndRunTime { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs new file mode 100644 index 000000000..c0cf3a132 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs @@ -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); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobLogAppService.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobLogAppService.cs new file mode 100644 index 000000000..cc41aae14 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobLogAppService.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.TaskManagement; + +public interface IBackgroundJobLogAppService : + IReadOnlyAppService< + BackgroundJobLogDto, + long, + BackgroundJobLogGetListInput> +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs new file mode 100644 index 000000000..8ca7655fe --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs @@ -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"; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementApplicationContractsModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementApplicationContractsModule.cs new file mode 100644 index 000000000..4288d4fdf --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementApplicationContractsModule.cs @@ -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 +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN.Abp.TaskManagement.Application.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN.Abp.TaskManagement.Application.csproj new file mode 100644 index 000000000..df4140ea7 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN.Abp.TaskManagement.Application.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs new file mode 100644 index 000000000..222683669 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs @@ -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 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); + } + + [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 GetAsync(Guid id) + { + var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); + + return ObjectMapper.Map(backgroundJobInfo); + } + + public virtual async Task> 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(totalCount, + ObjectMapper.Map, List>(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 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); + } + + 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; + } + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationMapperProfile.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationMapperProfile.cs new file mode 100644 index 000000000..11468eb23 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationMapperProfile.cs @@ -0,0 +1,11 @@ +using AutoMapper; + +namespace LINGYUN.Abp.TaskManagement; + +public class TaskManagementApplicationMapperProfile : Profile +{ + public TaskManagementApplicationMapperProfile() + { + CreateMap(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationModule.cs new file mode 100644 index 000000000..3bc12d61c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationModule.cs @@ -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(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationService.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationService.cs new file mode 100644 index 000000000..6ca7ebba1 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationService.cs @@ -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); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN.Abp.TaskManagement.Domain.Shared.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN.Abp.TaskManagement.Domain.Shared.csproj new file mode 100644 index 000000000..a268776f7 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN.Abp.TaskManagement.Domain.Shared.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobInfoConsts.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobInfoConsts.cs new file mode 100644 index 000000000..bc316921f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobInfoConsts.cs @@ -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; +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobLogConsts.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobLogConsts.cs new file mode 100644 index 000000000..618438e20 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobLogConsts.cs @@ -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; +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json new file mode 100644 index 000000000..10f2dda00 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Permission:TaskManagement": "TaskManagement" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..5b3611e36 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Permission:TaskManagement": "任务管理" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/TaskManagementResource.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/TaskManagementResource.cs new file mode 100644 index 000000000..9e8624631 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/TaskManagementResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.TaskManagement.Localization; + +[LocalizationResourceName("TaskManagement")] +public class TaskManagementResource +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementDomainSharedModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementDomainSharedModule.cs new file mode 100644 index 000000000..02c1252b3 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementDomainSharedModule.cs @@ -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(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/LINGYUN/Abp/TaskManagement/Localization/Resources"); + }); + + Configure(options => + { + options.MapCodeNamespace(TaskManagementErrorCodes.Namespace, typeof(TaskManagementResource)); + }); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementErrorCodes.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementErrorCodes.cs new file mode 100644 index 000000000..faa4bb6af --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementErrorCodes.cs @@ -0,0 +1,9 @@ +namespace LINGYUN.Abp.TaskManagement +{ + public static class TaskManagementErrorCodes + { + public const string Namespace = "TaskManagement"; + + public const string JobNameAlreadyExists = Namespace + ":01000"; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN.Abp.TaskManagement.Domain.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN.Abp.TaskManagement.Domain.csproj new file mode 100644 index 000000000..7b045dcbe --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN.Abp.TaskManagement.Domain.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs new file mode 100644 index 000000000..4704f65b9 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs @@ -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 +{ + /// + /// 任务名称 + /// + public virtual string Name { get; protected set; } + /// + /// 任务分组 + /// + public virtual string Group { get; protected set; } + /// + /// 任务类型 + /// + public virtual string Type { get; protected set; } + /// + /// 任务参数 + /// + public virtual ExtraPropertyDictionary Args { get; protected set; } + /// + /// 任务状态 + /// + public virtual JobStatus Status { get; protected set; } + /// + /// 是否启用 + /// + public virtual bool IsEnabled { get; set; } + /// + /// 描述 + /// + public virtual string Description { get; set; } + /// + /// 任务独占超时时长(秒) + /// 0或更小不生效 + /// + public virtual int LockTimeOut { get; set; } + /// + /// 开始时间 + /// + public virtual DateTime BeginTime { get; protected set; } + /// + /// 结束时间 + /// + public virtual DateTime? EndTime { get; protected set; } + /// + /// 上次执行时间 + /// + public virtual DateTime? LastRunTime { get; protected set; } + /// + /// 下次执行时间 + /// + public virtual DateTime? NextRunTime { get; protected set; } + /// + /// 任务类别 + /// + public virtual JobType JobType { get; protected set; } + /// + /// Cron表达式,如果是持续任务需要指定 + /// + public virtual string Cron { get; protected set; } + /// + /// 任务优先级 + /// + public virtual JobPriority Priority { get; protected set; } + /// + /// 触发次数 + /// + public virtual int TriggerCount { get; set; } + /// + /// 失败重试次数 + /// + public virtual int TryCount { get; set; } + /// + /// 失败重试上限 + /// 默认:50 + /// + public virtual int MaxTryCount { get; set; } + /// + /// 最大执行次数 + /// 默认:0, 无限制 + /// + public virtual int MaxCount { get; set; } + /// + /// 间隔时间,单位秒,与Cron表达式冲突 + /// 默认: 300 + /// + public virtual int Interval { get; protected set; } + /// + /// 连续失败且不会再次执行 + /// + public virtual bool IsAbandoned { get; set; } + + protected BackgroundJobInfo() { } + + public BackgroundJobInfo( + Guid id, + string name, + string group, + string type, + IDictionary 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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs new file mode 100644 index 000000000..53573fba8 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs @@ -0,0 +1,67 @@ +using LINGYUN.Abp.BackgroundTasks; +using System; + +namespace LINGYUN.Abp.TaskManagement; + +/// +/// 后台任务过滤 +/// +public class BackgroundJobInfoFilter +{ + /// + /// 其他过滤条件 + /// + public string Filter { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 任务状态 + /// + public JobStatus? Status { get; set; } + /// + /// 开始时间 + /// + public DateTime? BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 上次起始触发时间 + /// + public DateTime? BeginLastRunTime { get; set; } + /// + /// 上次截止触发时间 + /// + public DateTime? EndLastRunTime { get; set; } + /// + /// 起始创建时间 + /// + public DateTime? BeginCreationTime { get; set; } + /// + /// 截止创建时间 + /// + public DateTime? EndCreationTime { get; set; } + /// + /// 是否已放弃任务 + /// + public bool? IsAbandoned { get; set; } + /// + /// 是否持续性任务 + /// + public bool? IsPeriod { get; set; } + /// + /// 优先级 + /// + public JobPriority? Priority { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLog.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLog.cs new file mode 100644 index 000000000..9ae5ca1d0 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLog.cs @@ -0,0 +1,39 @@ +using System; +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobLog : Entity +{ + 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; + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLogFilter.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLogFilter.cs new file mode 100644 index 000000000..99bbdd984 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLogFilter.cs @@ -0,0 +1,35 @@ +using System; + +namespace LINGYUN.Abp.TaskManagement; + +public class BackgroundJobLogFilter +{ + /// + /// 其他过滤条件 + /// + public string Filter { get; set; } + /// + /// 存在异常 + /// + public bool? HasExceptions { get; set; } + /// + /// 任务名称 + /// + public string Name { get; set; } + /// + /// 任务分组 + /// + public string Group { get; set; } + /// + /// 任务类型 + /// + public string Type { get; set; } + /// + /// 开始触发时间 + /// + public DateTime? BeginRunTime { get; set; } + /// + /// 结束触发时间 + /// + public DateTime? EndRunTime { get; set; } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs new file mode 100644 index 000000000..06b39aaea --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs @@ -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 CreateAsync(BackgroundJobInfo jobInfo) + { + await BackgroundJobInfoRepository.InsertAsync(jobInfo); + + if (jobInfo.IsEnabled && jobInfo.JobType == JobType.Period) + { + var job = ObjectMapper.Map(jobInfo); + await JobScheduler.QueueAsync(job); + } + + return jobInfo; + } + + public virtual async Task UpdateAsync(BackgroundJobInfo jobInfo, bool resetJob = false) + { + await BackgroundJobInfoRepository.UpdateAsync(jobInfo); + + if (!jobInfo.IsEnabled || resetJob) + { + var job = ObjectMapper.Map(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(jobInfo); + await JobScheduler.RemoveAsync(job); + + await BackgroundJobInfoRepository.DeleteAsync(jobInfo); + } + + public virtual async Task QueueAsync(BackgroundJobInfo jobInfo) + { + var job = ObjectMapper.Map(jobInfo); + await JobScheduler.QueueAsync(job); + } + + public virtual async Task TriggerAsync(BackgroundJobInfo jobInfo) + { + var job = ObjectMapper.Map(jobInfo); + await JobScheduler.TriggerAsync(job); + } + + public virtual async Task PauseAsync(BackgroundJobInfo jobInfo) + { + var job = ObjectMapper.Map(jobInfo); + await JobScheduler.PauseAsync(job); + } + + public virtual async Task ResumeAsync(BackgroundJobInfo jobInfo) + { + var job = ObjectMapper.Map(jobInfo); + await JobScheduler.ResumeAsync(job); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs new file mode 100644 index 000000000..e5498ab6f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs @@ -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> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default) + { + var jobInfos = await JobInfoRepository.GetAllPeriodTasksAsync(cancellationToken); + + return ObjectMapper.Map, List>(jobInfos); + } + + public async virtual Task> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default) + { + var jobInfos = await JobInfoRepository.GetWaitingListAsync(maxResultCount, cancellationToken); + + return ObjectMapper.Map, List>(jobInfos); + } + + public async virtual Task FindAsync(Guid jobId) + { + var jobInfo = await JobInfoRepository.FindAsync(jobId); + + return ObjectMapper.Map(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); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs new file mode 100644 index 000000000..bffca21b9 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoRepository.cs @@ -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 +{ + Task CheckNameAsync( + string group, + string name, + CancellationToken cancellationToken = default); + /// + /// 获取过期任务列表 + /// + /// + /// + /// + /// + Task> GetExpiredJobsAsync( + int maxResultCount, + TimeSpan jobExpiratime, + CancellationToken cancellationToken = default); + /// + /// 获取所有周期性任务 + /// 指定了Cron表达式的任务需要作为持续性任务交给任务引擎 + /// + /// + Task> GetAllPeriodTasksAsync( + CancellationToken cancellationToken = default); + /// + /// 获取等待入队的任务列表 + /// + /// + /// + /// + Task> GetWaitingListAsync( + int maxResultCount, + CancellationToken cancellationToken = default); + /// + /// 获取过滤后的任务数量 + /// + /// + /// + /// + Task GetCountAsync( + BackgroundJobInfoFilter filter, + CancellationToken cancellationToken = default); + /// + /// 获取过滤后的任务列表 + /// + /// + /// + /// + /// + /// + /// + Task> GetListAsync( + BackgroundJobInfoFilter filter, + string sorting = nameof(BackgroundJobInfo.Name), + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobLogRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobLogRepository.cs new file mode 100644 index 000000000..e9036a51a --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/IBackgroundJobLogRepository.cs @@ -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 +{ + /// + /// 获取过滤后的任务日志数量 + /// + /// + /// + /// + /// + Task GetCountAsync( + BackgroundJobLogFilter filter, + Guid? jobId = null, + CancellationToken cancellationToken = default); + /// + /// 获取过滤后的任务日志列表 + /// + /// + /// + /// + /// + /// + /// + /// + Task> GetListAsync( + BackgroundJobLogFilter filter, + Guid? jobId = null, + string sorting = nameof(BackgroundJobLog.RunTime), + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDbProperties.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDbProperties.cs new file mode 100644 index 000000000..a396d4036 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDbProperties.cs @@ -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"; +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainMapperProfile.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainMapperProfile.cs new file mode 100644 index 000000000..e5e133e55 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainMapperProfile.cs @@ -0,0 +1,12 @@ +using AutoMapper; +using LINGYUN.Abp.BackgroundTasks; + +namespace LINGYUN.Abp.TaskManagement; + +public class TaskManagementDomainMapperProfile : Profile +{ + public TaskManagementDomainMapperProfile() + { + CreateMap(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainModule.cs new file mode 100644 index 000000000..2d75c167c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/TaskManagementDomainModule.cs @@ -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(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj new file mode 100644 index 000000000..60375de9f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj @@ -0,0 +1,19 @@ + + + + + + + net6.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs new file mode 100644 index 000000000..8cc7bc601 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs @@ -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, + IBackgroundJobInfoRepository +{ + protected IClock Clock { get; } + + public EfCoreBackgroundJobInfoRepository( + IClock clock, + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + Clock = clock; + } + + public virtual async Task 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> 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> 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 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> 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> 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)); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobLogRepository.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobLogRepository.cs new file mode 100644 index 000000000..4508de1a0 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobLogRepository.cs @@ -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, + IBackgroundJobLogRepository +{ + public EfCoreBackgroundJobLogRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public virtual async Task 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> 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)); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/ITaskManagementDbContext.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/ITaskManagementDbContext.cs new file mode 100644 index 000000000..c6284c0ab --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/ITaskManagementDbContext.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; + +[ConnectionStringName(TaskManagementDbProperties.ConnectionStringName)] +public interface ITaskManagementDbContext :IEfCoreDbContext +{ +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContext.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContext.cs new file mode 100644 index 000000000..a6d1f99ac --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContext.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; + +public class TaskManagementDbContext : AbpDbContext, ITaskManagementDbContext +{ + public TaskManagementDbContext( + DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureTaskManagement(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContextModelCreatingExtensions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContextModelCreatingExtensions.cs new file mode 100644 index 000000000..c32fabef7 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementDbContextModelCreatingExtensions.cs @@ -0,0 +1,81 @@ +using Microsoft.EntityFrameworkCore; +using System; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.EntityFrameworkCore.ValueComparers; +using Volo.Abp.EntityFrameworkCore.ValueConverters; + +namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; + +public static class TaskManagementDbContextModelCreatingExtensions +{ + public static void ConfigureTaskManagement( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new TaskManagementModelBuilderConfigurationOptions( + TaskManagementDbProperties.DbTablePrefix, + TaskManagementDbProperties.DbSchema + ); + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema); + + b.Property(p => p.Name) + .HasColumnName(nameof(BackgroundJobInfo.Name)) + .HasMaxLength(BackgroundJobInfoConsts.MaxNameLength) + .IsRequired(); + b.Property(p => p.Group) + .HasColumnName(nameof(BackgroundJobInfo.Group)) + .HasMaxLength(BackgroundJobInfoConsts.MaxGroupLength) + .IsRequired(); + b.Property(p => p.Type) + .HasColumnName(nameof(BackgroundJobInfo.Type)) + .HasMaxLength(BackgroundJobInfoConsts.MaxTypeLength) + .IsRequired(); + b.Property(p => p.Cron) + .HasColumnName(nameof(BackgroundJobInfo.Cron)) + .HasMaxLength(BackgroundJobInfoConsts.MaxCronLength); + b.Property(p => p.Description) + .HasColumnName(nameof(BackgroundJobInfo.Description)) + .HasMaxLength(BackgroundJobInfoConsts.MaxDescriptionLength); + b.Property(p => p.Args) + .HasColumnName(nameof(BackgroundJobInfo.Args)) + .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType)) + .Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer()); + + b.ConfigureByConvention(); + + b.HasIndex(p => new { p.Name, p.Group }); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "BackgroundJobLogs", options.Schema); + + b.Property(p => p.JobName) + .HasColumnName(nameof(BackgroundJobLog.JobName)) + .HasMaxLength(BackgroundJobInfoConsts.MaxNameLength); + b.Property(p => p.JobGroup) + .HasColumnName(nameof(BackgroundJobLog.JobGroup)) + .HasMaxLength(BackgroundJobInfoConsts.MaxGroupLength); + b.Property(p => p.JobType) + .HasColumnName(nameof(BackgroundJobLog.JobType)) + .HasMaxLength(BackgroundJobInfoConsts.MaxTypeLength); + b.Property(p => p.Message) + .HasColumnName(nameof(BackgroundJobLog.Message)) + .HasMaxLength(BackgroundJobLogConsts.MaxMessageLength); + b.Property(p => p.Exception) + .HasColumnName(nameof(BackgroundJobLog.Exception)) + .HasMaxLength(BackgroundJobLogConsts.MaxExceptionLength); + + b.ConfigureByConvention(); + + b.HasIndex(p => new { p.JobGroup, p.JobName }); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementEntityFrameworkCoreModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..f2edc4178 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementEntityFrameworkCoreModule.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; + +[DependsOn(typeof(TaskManagementDomainModule))] +[DependsOn(typeof(AbpEntityFrameworkCoreModule))] +public class TaskManagementEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddRepository(); + options.AddRepository(); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementModelBuilderConfigurationOptions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementModelBuilderConfigurationOptions.cs new file mode 100644 index 000000000..03b205c7c --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/TaskManagementModelBuilderConfigurationOptions.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace LINGYUN.Abp.TaskManagement.EntityFrameworkCore; + +public class TaskManagementModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions +{ + public TaskManagementModelBuilderConfigurationOptions( + [NotNull] string tablePrefix = "", + [CanBeNull] string schema = null) + : base( + tablePrefix, + schema) + { + + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xml b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xsd b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN.Abp.TaskManagement.HttpApi.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN.Abp.TaskManagement.HttpApi.csproj new file mode 100644 index 000000000..128b311a8 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN.Abp.TaskManagement.HttpApi.csproj @@ -0,0 +1,19 @@ + + + + + + + net6.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/TaskManagementHttpApiModule.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/TaskManagementHttpApiModule.cs new file mode 100644 index 000000000..b6d43f111 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/TaskManagementHttpApiModule.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.TaskManagement.Localization; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Validation.Localization; + +namespace LINGYUN.Abp.TaskManagement; + +[DependsOn(typeof(TaskManagementApplicationContractsModule))] +[DependsOn(typeof(AbpAspNetCoreMvcModule))] +public class TaskManagementHttpApiModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(TaskManagementHttpApiModule).Assembly); + }); + + PreConfigure(options => + { + options.AddAssemblyResource( + typeof(TaskManagementResource), + typeof(TaskManagementApplicationContractsModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpValidationResource)); + }); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN.Abp.TaskManagement.Quartz.csproj b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN.Abp.TaskManagement.Quartz.csproj new file mode 100644 index 000000000..a079141f7 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN.Abp.TaskManagement.Quartz.csproj @@ -0,0 +1,19 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/IQuartzJobExecutorProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/IQuartzJobExecutorProvider.cs new file mode 100644 index 000000000..23f8d4b4f --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/IQuartzJobExecutorProvider.cs @@ -0,0 +1,10 @@ +using Quartz; + +namespace LINGYUN.Abp.TaskManagement.Quartz; + +public interface IQuartzJobExecutorProvider +{ + IJobDetail CreateJob(JobInfo job); + + ITrigger CreateTrigger(JobInfo job); +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobExecutorProvider.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobExecutorProvider.cs new file mode 100644 index 000000000..97082fe02 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobExecutorProvider.cs @@ -0,0 +1,20 @@ +using Quartz; +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.TaskManagement.Quartz; + +public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonDependency +{ + public IJobDetail CreateJob(JobInfo job) + { + throw new NotImplementedException(); + } + + public ITrigger CreateTrigger(JobInfo job) + { + throw new NotImplementedException(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobScheduler.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobScheduler.cs new file mode 100644 index 000000000..0e8a636b9 --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobScheduler.cs @@ -0,0 +1,63 @@ +using Quartz; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.TaskManagement.Quartz; +public class QuartzJobScheduler : IJobScheduler +{ + protected IScheduler Scheduler { get; } + + public QuartzJobScheduler(IScheduler scheduler) + { + Scheduler = scheduler; + } + + public virtual async Task ExistsAsync(string group, string name) + { + throw new System.NotImplementedException(); + } + + public Task PauseAsync(string group, string name) + { + throw new System.NotImplementedException(); + } + + public Task QueueAsync(JobInfo job) + { + throw new System.NotImplementedException(); + } + + public Task RefreshAsync(JobInfo job) + { + throw new System.NotImplementedException(); + } + + public Task RemoveAsync(string group, string name) + { + throw new System.NotImplementedException(); + } + + public Task ResumeAsync(string group, string name) + { + throw new System.NotImplementedException(); + } + + public Task ShutdownAsync() + { + throw new System.NotImplementedException(); + } + + public Task StartAsync() + { + throw new System.NotImplementedException(); + } + + public Task StopAsync() + { + throw new System.NotImplementedException(); + } + + public Task TriggerAsync(JobInfo job) + { + throw new System.NotImplementedException(); + } +} diff --git a/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobSchedulerOptions.cs b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobSchedulerOptions.cs new file mode 100644 index 000000000..dbd0c774e --- /dev/null +++ b/aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Quartz/LINGYUN/Abp/TaskManagement/Quartz/QuartzJobSchedulerOptions.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LINGYUN.Abp.TaskManagement.Quartz; + +internal class QuartzJobSchedulerOptions +{ +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Controllers/HomeController.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Controllers/HomeController.cs new file mode 100644 index 000000000..f98d6c1ca --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Controllers/HomeController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace LY.MicroService.TaskManagement.Controllers; + +public class HomeController : AbpController +{ + public IActionResult Index() + { + return Redirect("/swagger/index.html"); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile new file mode 100644 index 000000000..a38f2b0c8 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +LABEL maintainer="colin.in@foxmail.com" +WORKDIR /app + +COPY . /app + +#东8区 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone + +EXPOSE 80/tcp +VOLUME [ "./app/Logs" ] +VOLUME [ "./app/Modules" ] + +ENTRYPOINT ["dotnet", "LY.MicroService.TaskManagement.HttpApi.Host.dll"] diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContext.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContext.cs new file mode 100644 index 000000000..229b8fdcc --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContext.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.TaskManagement.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LY.MicroService.TaskManagement.EntityFrameworkCore; + +public class TaskManagementMigrationsDbContext : AbpDbContext +{ + public TaskManagementMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureTaskManagement(); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContextFactory.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContextFactory.cs new file mode 100644 index 000000000..7ef7887cc --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EntityFrameworkCore/TaskManagementMigrationsDbContextFactory.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace LY.MicroService.TaskManagement.EntityFrameworkCore; + +public class TaskManagementMigrationsDbContextFactory : IDesignTimeDbContextFactory +{ + public TaskManagementMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var connectionString = configuration.GetConnectionString("TaskManagement"); + + var builder = new DbContextOptionsBuilder() + .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + + return new TaskManagementMigrationsDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false) + .AddJsonFile("appsettings.Development.json", optional: true); + + return builder.Build(); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj new file mode 100644 index 000000000..ccec8e8b2 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj @@ -0,0 +1,65 @@ + + + + False + False + False + + + + False + False + False + + + + net6.0 + LY.MicroService.TaskManagement + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Program.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Program.cs new file mode 100644 index 000000000..013d47509 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Program.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace LY.MicroService.TaskManagement; + +public class Program +{ + public static int Main(string[] args) + { + try + { + var host = CreateHostBuilder(args).Build(); + Log.Information("Starting web host."); + host.Run(); + return 0; + } + finally + { + Log.CloseAndFlush(); + } + } + + internal static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .ConfigureAppConfiguration((context, config) => + { + var configuration = config.Build(); + if (configuration.GetSection("AgileConfig").Exists()) + { + config.AddAgileConfig(new AgileConfig.Client.ConfigClient(configuration)); + } + }) + .UseSerilog((context, config) => + { + config.ReadFrom.Configuration(context.Configuration); + }) + .UseAutofac(); +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Properties/launchSettings.json b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Properties/launchSettings.json new file mode 100644 index 000000000..38de94ebd --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:43766", + "sslPort": 0 + } + }, + "profiles": { + "LY.MicroService.TaskManagement.HttpApi.Host": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://127.0.0.1:30040", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Startup.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Startup.cs new file mode 100644 index 000000000..35612530f --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Startup.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using System.IO; +using Volo.Abp.IO; +using Volo.Abp.Modularity.PlugIns; + +namespace LY.MicroService.TaskManagement; + +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(options => + { + // 搜索 Modules 目录下所有文件作为插件 + // 取消显示引用所有其他项目的模块,改为通过插件的形式引用 + var pluginFolder = Path.Combine( + Directory.GetCurrentDirectory(), "Modules"); + DirectoryHelper.CreateIfNotExists(pluginFolder); + options.PlugInSources.AddFolder( + pluginFolder, + SearchOption.AllDirectories); + }); + } + + public void Configure(IApplicationBuilder app) + { + app.InitializeApplication(); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs new file mode 100644 index 000000000..6eafb7798 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs @@ -0,0 +1,215 @@ +using LINGYUN.Abp.ExceptionHandling; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using Medallion.Threading; +using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using StackExchange.Redis; +using System; +using System.Text.Encodings.Web; +using System.Text.Unicode; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Caching; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.VirtualFileSystem; + +namespace LY.MicroService.TaskManagement; + +public partial class TaskManagementHttpApiHostModule +{ + private void PreConfigureApp() + { + AbpSerilogEnrichersConsts.ApplicationName = "TaskManagement"; + } + + private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration) + { + var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]); + services.AddSingleton(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase())); + } + + private void ConfigureDbContext() + { + // 配置Ef + Configure(options => + { + options.UseMySQL(); + }); + } + + private void ConfigureJsonSerializer() + { + // 解决某些不支持类型的序列化 + Configure(options => + { + options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + }); + // 中文序列化的编码问题 + Configure(options => + { + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); + } + + private void ConfigureExceptionHandling() + { + // 自定义需要处理的异常 + Configure(options => + { + // 加入需要处理的异常类型 + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + }); + // 自定义需要发送邮件通知的异常类型 + Configure(options => + { + // 是否发送堆栈信息 + options.SendStackTrace = true; + // 未指定异常接收者的默认接收邮件 + // 指定自己的邮件地址 + }); + } + + private void ConfigureAuditing(IConfiguration configuration) + { + Configure(options => + { + options.ApplicationName = "TaskManagement"; + // 是否启用实体变更记录 + var entitiesChangedConfig = configuration.GetSection("App:TrackingEntitiesChanged"); + if (entitiesChangedConfig.Exists() && entitiesChangedConfig.Get()) + { + options + .EntityHistorySelectors + .AddAllEntities(); + } + }); + } + + private void ConfigureCaching(IConfiguration configuration) + { + Configure(options => + { + // 最好统一命名,不然某个缓存变动其他应用服务有例外发生 + options.KeyPrefix = "LINGYUN.Abp.Application"; + // 滑动过期30天 + options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30d); + // 绝对过期60天 + options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddDays(60d); + }); + + Configure(options => + { + var redisConfig = ConfigurationOptions.Parse(options.Configuration); + options.ConfigurationOptions = redisConfig; + options.InstanceName = configuration["Redis:InstanceName"]; + }); + } + + private void ConfigureVirtualFileSystem() + { + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.TaskManagement"); + }); + } + + private void ConfigureMultiTenancy(IConfiguration configuration) + { + // 多租户 + Configure(options => + { + options.IsEnabled = true; + }); + + var tenantResolveCfg = configuration.GetSection("App:Domains"); + if (tenantResolveCfg.Exists()) + { + Configure(options => + { + var domains = tenantResolveCfg.Get(); + foreach (var domain in domains) + { + options.AddDomainTenantResolver(domain); + } + }); + } + } + + private void ConfigureSwagger(IServiceCollection services) + { + // Swagger + services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkflowManagement API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Scheme = "bearer", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new string[] { } + } + }); + }); + } + + private void ConfigureLocalization() + { + // 支持本地化语言类型 + Configure(options => + { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + // 动态语言支持 + options.Resources.AddDynamic(); + }); + } + + private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.Audience = configuration["AuthServer:ApiName"]; + }); + + if (!isDevelopment) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + services + .AddDataProtection() + .SetApplicationName("LINGYUN.Abp.Application") + .PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys"); + } + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs new file mode 100644 index 000000000..453210150 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs @@ -0,0 +1,114 @@ +using LINGYUN.Abp.AuditLogging.Elasticsearch; +using LINGYUN.Abp.BackgroundTasks.Quartz; +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.MultiTenancy.DbFinder; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Globalization; +using Volo.Abp; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.Autofac; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.EntityFrameworkCore.MySQL; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Http.Client.IdentityModel.Web; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Swashbuckle; +using Volo.Abp.TenantManagement.EntityFrameworkCore; + +namespace LY.MicroService.TaskManagement; + +[DependsOn( + typeof(AbpSerilogEnrichersApplicationModule), + typeof(AbpSerilogEnrichersUniqueIdModule), + typeof(AbpAuditLoggingElasticsearchModule), + typeof(AbpAspNetCoreSerilogModule), + typeof(AbpEntityFrameworkCoreMySQLModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpEmailingExceptionHandlingModule), + typeof(AbpHttpClientIdentityModelWebModule), + typeof(AbpAspNetCoreMultiTenancyModule), + typeof(AbpDbFinderMultiTenancyModule), + typeof(AbpBackgroundTasksQuartzModule), + //typeof(TaskManagementApplicationModule), + //typeof(TaskManagementHttpApiModule), + //typeof(TaskManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpTenantManagementEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpDataDbMigratorModule), + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpSwashbuckleModule), + typeof(AbpAutofacModule) + )] +public partial class TaskManagementHttpApiHostModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigureApp(); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + ConfigureDbContext(); + ConfigureLocalization(); + ConfigureJsonSerializer(); + ConfigureExceptionHandling(); + ConfigureVirtualFileSystem(); + ConfigureCaching(configuration); + ConfigureAuditing(configuration); + ConfigureMultiTenancy(configuration); + ConfigureSwagger(context.Services); + ConfigureDistributedLock(context.Services, configuration); + ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); + + // 开发取消权限检查 + // context.Services.AddAlwaysAllowAuthorization(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + app.UseStaticFiles(); + app.UseCorrelationId(); + app.UseRouting(); + app.UseCors(); + app.UseAuthentication(); + app.UseJwtTokenMiddleware(); + app.UseMultiTenancy(); + app.UseAbpRequestLocalization(options => options.SetDefaultCulture(CultureInfo.CurrentCulture.Name)); + app.UseAuthorization(); + app.UseSwagger(); + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support Task Management API"); + + var configuration = context.GetConfiguration(); + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); + options.OAuthScopes("TaskManagement"); + }); + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseConfiguredEndpoints(); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.Development.json b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.Development.json new file mode 100644 index 000000000..3315406f2 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.Development.json @@ -0,0 +1,118 @@ +{ + "AgileConfig": { + "env": "DEV", + "appId": "LINGYUN.Abp.TaskManagement", + "secret": "1q2w3E*", + "nodes": "http://127.0.0.1:15000", + "name": "LINGYUN.Abp.TaskManagement", + "tag": "LINGYUN.Abp.TaskManagement" + }, + "App": { + "TrackingEntitiesChanged": true + }, + "CAP": { + "EventBus": { + "DefaultGroupName": "TaskManagement", + "Version": "v1", + "FailedRetryInterval": 300, + "FailedRetryCount": 10, + "CollectorCleaningInterval": 3600000 + }, + "MySql": { + "TableNamePrefix": "tsk", + "ConnectionString": "Server=localhost;Database=Platform;User Id=root;Password=123456" + }, + "RabbitMQ": { + "HostName": "localhost", + "Port": 5672, + "UserName": "guest", + "Password": "guest", + "ExchangeName": "LINGYUN.Abp.Application", + "VirtualHost": "/" + } + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "TaskManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpFeatureManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpTenantManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" + }, + "RemoteServices": { + "AbpOssManagement": { + "BaseUrl": "http://127.0.0.1:30025", + "IdentityClient": "InternalServiceClient", + "UseCurrentAccessToken": false + } + }, + "IdentityClients": { + "InternalServiceClient": { + "Authority": "http://127.0.0.1:44385", + "RequireHttps": false, + "GrantType": "client_credentials", + "Scope": "lingyun-abp-application", + "ClientId": "InternalServiceClient", + "ClientSecret": "1q2w3E*" + } + }, + "DistributedLock": { + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=15" + } + }, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=10", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "Authority": "http://127.0.0.1:44385/", + "ApiName": "lingyun-abp-application", + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.json b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.json new file mode 100644 index 000000000..67e1bc4bd --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.json @@ -0,0 +1,80 @@ +{ + "StringEncryption": { + "DefaultPassPhrase": "s46c5q55nxpeS8Ra", + "InitVectorBytes": "s83ng0abvd02js84", + "DefaultSalt": "sf&5)s3#" + }, + "AllowedHosts": "*", + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft.EntityFrameworkCore": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "initialMinimumLevel": "Verbose", + "standardErrorFromLevel": "Verbose", + "restrictedToMinimumLevel": "Verbose", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Debug-.log", + "restrictedToMinimumLevel": "Debug", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Info-.log", + "restrictedToMinimumLevel": "Information", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Warn-.log", + "restrictedToMinimumLevel": "Warning", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Error-.log", + "restrictedToMinimumLevel": "Error", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Fatal-.log", + "restrictedToMinimumLevel": "Fatal", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/dapr.sh b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/dapr.sh new file mode 100644 index 000000000..fa1450cf4 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/dapr.sh @@ -0,0 +1 @@ +dapr run --app-id workflow --app-port 30040 -H 34178 -- dotnet run --no-build \ No newline at end of file