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