需求描述:我在使用ABP框架,现在想实现一个定时任务功能,描述:每天八点调用特定接口,调用n次后结束不再调用。相关的数据都来自于一个特定的实体“fuck”。
解决方案:可以使用ABP框架自带的定时任务功能来实现
解决步骤:
- 创建一个名为 "FuckJob" 的后台任务类,继承 BackgroundJob 类。
- 在 ExecuteAsync 方法中编写调用特定接口的逻辑,并在每次调用后将 "fuck" 实体中的相关数据进行更新。
- 在启动类 YourProjectNameWebHostModule 中,使用 Configure<AbpBackgroundJobOptions> 方法来配置任务的执行时间。例如:
详细代码:
1. 创建一个名为 "FuckJob" 的后台任务类,继承 BackgroundJob 类,并实现 ExecuteAsync 方法:
public class FuckJob : BackgroundJob
{
private readonly IRepository<Fuck, Guid> _fuckRepository;
public FuckJob(IRepository<Fuck, Guid> fuckRepository)
{
_fuckRepository = fuckRepository;
}
public override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 获取 "fuck" 实体
var fuck = await _fuckRepository.FirstOrDefaultAsync();
// 调用特定接口的逻辑
// ...
// 更新 "fuck" 实体的相关数据
fuck.SomeProperty = someValue;
await _fuckRepository.UpdateAsync(fuck);
}
}
2. 在启动类 YourProjectNameWebHostModule 中,注册 "FuckJob" 类,并配置任务的执行时间
[DependsOn(
typeof(AbpBackgroundJobsModule),
typeof(YourProjectNameEntityFrameworkCoreModule)
)]
public class YourProjectNameWebHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 注册 "FuckJob" 类
context.Services.AddTransient<FuckJob>();
Configure<AbpBackgroundJobOptions>(options =>
{
options.AddJob<FuckJob>(job => job
.WithInterval(TimeSpan.FromDays(1)) // 每天执行一次
.WithDescription("每天八点调用特定接口,调用n次后结束不再调用")
.WithTolerateTime(TimeSpan.FromMinutes(30)) // 允许任务延迟30分钟
.WithRunTimes(10) // 执行10次后结束
.Build());
});
}
}
优化代码,相关数据取自仓储的实体
public class YourProjectNameWebHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<FuckJob>(provider =>
{
var fuckRepository = provider.GetService<IRepository<Fuck, Guid>>();
// 获取 "fuck" 实体中的数据
var fuck = fuckRepository.FirstOrDefault();
var executeTimes = fuck.ExecuteTimes;
var maxRunTimes = fuck.MaxRunTimes;
return new FuckJob(fuckRepository, executeTimes, maxRunTimes);
});
Configure<AbpBackgroundJobOptions>(options =>
{
options.AddJob<FuckJob>(job => job
.WithInterval(TimeSpan.FromDays(1)) // 每天执行一次
.WithDescription("每天八点调用特定接口,调用n次后结束不再调用")
.WithTolerateTime(TimeSpan.FromMinutes(30)) // 允许任务延迟30分钟
.WithRunTimes(maxRunTimes) // 执行 n 次后结束
.Build());
});
}
}
结合业务拓展
实际业务需求中,有一个开关来添加和删除后台任务。
翻译为技术需求:在启动类之外的地方AddJob<FuckJob>?因为我的想法是前端调用某个接口然后启动后台定时任务。同时,还存在一个接口也可以删除这个定时任务。
1. 在前端调用某个接口时,通过 IBackgroundJobManager 接口的 AddJobAsync 方法来添加后台任务,例如:
返回值为添加的job的Guid
应该有另外一张表来存储这些后台任务
public class YourController : YourProjectNameControllerBase
{
private readonly IBackgroundJobManager _backgroundJobManager;
public YourController(IBackgroundJobManager backgroundJobManager)
{
_backgroundJobManager = backgroundJobManager;
}
public async Task<IActionResult> AddFuckJob()
{
// 获取 "fuck" 实体中的数据
var fuck = await _fuckRepository.FirstOrDefaultAsync();
var executeTimes = fuck.ExecuteTimes;
var maxRunTimes = fuck.MaxRunTimes;
// 添加后台任务
await _backgroundJobManager.AddJobAsync(
new JobInvocation(
typeof(FuckJob),
new JobArgs
{
Args = new object[] { executeTimes, maxRunTimes }
}
)
);
// 返回任务标识符
return Ok(jobId);
}
}
2. 在另一个接口中,通过 IBackgroundJobManager 接口的 RemoveAsync 方法来删除后台任务,例如:
public class YourController : YourProjectNameControllerBase
{
private readonly IBackgroundJobManager _backgroundJobManager;
public YourController(IBackgroundJobManager backgroundJobManager)
{
_backgroundJobManager = backgroundJobManager;
}
public async Task<IActionResult> RemoveFuckJob()
{
// 删除后台任务
await _backgroundJobManager.RemoveAsync(typeof(FuckJob));
return Ok();
}
}
整体实现思路
在application层 推送一个eto出去,ILocalEventHandler内去触发一个定时任务,那么这个定时任务的逻辑被定义在domian层(manager结尾的文件)。
domain层处理backgroundJob,多个接口相关后台任务的增加删除。
那么数据源独自一张单独的表,每次触发开关时候要做两件事
1. 修改数据源实体
2. 删除/增加 typeof(xxxx)的backgroundJob
和 quartz的区别?
我的实现方式是使用 ABP 框架提供的后台任务管理功能,而 Quartz 是一个独立的任务调度框架。它们的区别主要有以下几点:
-
ABP 框架的后台任务管理功能是直接集成在框架中的,使用起来比较方便,而 Quartz 则需要单独引入和配置。
-
ABP 框架的后台任务管理功能支持分布式环境下的任务调度,可以通过分布式锁来避免任务重复执行,而 Quartz 则需要额外配置分布式环境下的任务调度。
-
ABP 框架的后台任务管理功能支持多种存储方式,可以使用数据库、Redis 等作为后台任务的存储介质,而 Quartz 则默认使用数据库作为存储介质。
-
ABP 框架的后台任务管理功能提供了一些常用的任务调度方式,例如简单定时、循环定时、延迟执行等,使用起来比较方便,而 Quartz 则需要手动编写 Cron 表达式等。
综上所述,ABP 框架的后台任务管理功能适用于一些简单的任务调度场景,而 Quartz 则适用于更加复杂和灵活的任务调度场景。但是,在使用 Quartz 时需要额外进行配置和学习成本比较高,而 ABP 框架的后台任务管理功能则是直接集成在框架中,使用起来比较方便。
用quartz来实现一遍这个需求
要定义一个Job类,你需要继承Quartz的Job类,并实现execute方法。在这个方法中,你可以编写需要执行的逻辑。
在你的情况下,你想传入一些参数来执行定时任务,你可以使用JobDataMap来传递参数。JobDataMap是JobExecutionContext的一部分,可以在执行任务时获取。
以下是一个示例代码,展示了如何定义一个Job类,并传递参数:
public class MyJob : IJob
{
public void Execute(IJobExecutionContext context)
{
// 从JobDataMap中获取参数
JobDataMap dataMap = context.JobDetail.JobDataMap;
int x = dataMap.GetInt("x");
int n = dataMap.GetInt("n");
// 执行你的逻辑
// 调用指定接口,执行n天后结束
}
}
接下来,你需要实现一个接口,这个接口的功能是添加一个定时任务,并传入相关参数。你可以创建一个定时任务管理类,其中包含添加任务的方法。
以下是一个示例代码,展示了如何添加定时任务并传递参数:
public class SchedulerManager
{
private readonly IScheduler _scheduler;
public SchedulerManager(IScheduler scheduler)
{
_scheduler = scheduler;
}
public async Task AddJob(FuckEntity fuckEntity)
{
// 创建一个JobDetail实例,传入参数
JobDataMap dataMap = new JobDataMap();
dataMap.Put("x", fuckEntity.X);
dataMap.Put("n", fuckEntity.N);
IJobDetail job = JobBuilder.Create<MyJob>()
.WithIdentity("myJob")
.UsingJobData(dataMap)
.Build();
// 创建一个Trigger实例,设置定时调度规则
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger")
.WithDailyTimeIntervalSchedule(x => x.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(fuckEntity.X, 0)))
.EndAt(DateTimeOffset.Now.AddDays(fuckEntity.N))
.Build();
// 将Job和Trigger添加到调度器中
await _scheduler.ScheduleJob(job, trigger);
}
}
在上述代码中,IScheduler
是Quartz框架提供的接口,用于管理和调度Job和Trigger。你可以通过依赖注入将其注入到 SchedulerManager
类中。
请注意,上述代码中的FuckEntity
是一个占位符,你需要替换为你自己的实体类。
最后,你可以通过调用SchedulerManager
的AddJob
方法来添加一个定时任务,并传入相关参数:
FuckEntity fuckEntity = new FuckEntity { X = 12, N = 7 };
await schedulerManager.AddJob(fuckEntity);
这样,你就可以实现一个定时任务,并传递相关参数。每天指定的时间点,定时任务将会执行调用指定的接口,执行n天后结束。
TriggerBuilder.Create().WithIdentity("myTrigger")的WithIdentity是什么?
在ABP(ASP.NET Boilerplate)框架中,TriggerBuilder.Create().WithIdentity("myTrigger")中的WithIdentity方法用于为触发器分配一个唯一的标识符,这个标识符通常被用于后续操作。
WithIdentity方法接受一个字符串参数作为触发器的标识符。这个参数可以是任何你指定的字符串,用于唯一标识触发器。在后续操作中,你可以使用这个标识符来引用和管理这个触发器。
一个常见的用例是在调度器中创建多个触发器时,使用不同的标识符来区分它们。这样,你可以根据标识符来管理和操作各个触发器,例如启动、停止或删除它们。标识符还可以用于记录日志或其他跟踪目的。
示例代码如下:
TriggerBuilder.Create()
.WithIdentity("myTrigger") // 设置触发器标识符为 "myTrigger"
// 其他设置...
.Build();
Conversation History
https://monica.im/s/2e53179e?locale=enhttps://monica.im/s/2e53179e?locale=en