ABP:BackgroundJob/Quartz 结合业务实现定时发送

需求描述:我在使用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 是一个独立的任务调度框架。它们的区别主要有以下几点:

  1. ABP 框架的后台任务管理功能是直接集成在框架中的,使用起来比较方便,而 Quartz 则需要单独引入和配置。

  2. ABP 框架的后台任务管理功能支持分布式环境下的任务调度,可以通过分布式锁来避免任务重复执行,而 Quartz 则需要额外配置分布式环境下的任务调度。

  3. ABP 框架的后台任务管理功能支持多种存储方式,可以使用数据库、Redis 等作为后台任务的存储介质,而 Quartz 则默认使用数据库作为存储介质。

  4. 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是一个占位符,你需要替换为你自己的实体类。

最后,你可以通过调用SchedulerManagerAddJob方法来添加一个定时任务,并传入相关参数:

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

猜你喜欢

转载自blog.csdn.net/dongnihao/article/details/132123647
今日推荐