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

需求描述:我在使用ABP框架,现在想实现一个定时任务功能,描述:每天八点调用特定接口,调用n次后结束不再调用。相关的数据都来自于一个特定的实体“fuck”。

解决方案:可以使用ABP框架自带的定时任务功能来实现

解决步骤

  • 创建一个名为 "FuckJob" 的后台任务类,继承 BackgroundJob 类。
  • 在 ExecuteAsync 方法中编写调用特定接口的逻辑,并在每次调用后将 "fuck" 实体中的相关数据进行更新。
  • 在启动类 YourProjectNameWebHostModule 中,使用 Configure 方法来配置任务的执行时间。例如:

详细代码:

1. 创建一个名为 "FuckJob" 的后台任务类,继承 BackgroundJob 类,并实现 ExecuteAsync 方法:

public class FuckJob : BackgroundJob
{
    private readonly IRepository _fuckRepository;

    public FuckJob(IRepository 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();

        Configure(options =>
        {
            options.AddJob(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(provider =>
        {
            var fuckRepository = provider.GetService>();

            // 获取 "fuck" 实体中的数据
            var fuck = fuckRepository.FirstOrDefault();
            var executeTimes = fuck.ExecuteTimes;
            var maxRunTimes = fuck.MaxRunTimes;

            return new FuckJob(fuckRepository, executeTimes, maxRunTimes);
        });

        Configure(options =>
        {
            options.AddJob(job => job
                .WithInterval(TimeSpan.FromDays(1)) // 每天执行一次
                .WithDescription("每天八点调用特定接口,调用n次后结束不再调用")
                .WithTolerateTime(TimeSpan.FromMinutes(30)) // 允许任务延迟30分钟
                .WithRunTimes(maxRunTimes) // 执行 n 次后结束
                .Build());
        });
    }
}

结合业务拓展

实际业务需求中,有一个开关来添加和删除后台任务。

翻译为技术需求:在启动类之外的地方AddJob?因为我的想法是前端调用某个接口然后启动后台定时任务。同时,还存在一个接口也可以删除这个定时任务。

1. 在前端调用某个接口时,通过 IBackgroundJobManager 接口的 AddJobAsync 方法来添加后台任务,例如:

返回值为添加的job的Guid

应该有另外一张表来存储这些后台任务

public class YourController : YourProjectNameControllerBase
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public YourController(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task 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 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来实现一遍这个需求(尚未验证)

好的,下面是使用 Quartz 实现该需求的步骤:

1. 安装 Quartz NuGet 包

打开 Visual Studio,右键点击项目名称,选择“管理 NuGet 程序包”,在“浏览”选项卡中搜索“Quartz”,选择“Quartz”并安装。

2. 创建 Job 类

创建一个继承自 `IJob` 接口的 Job 类,并实现 `Execute` 方法。在该方法中编写需要执行的业务逻辑。

public class FuckJob : IJob
{
    private readonly IFuckRepository _fuckRepository;

    public FuckJob(IFuckRepository fuckRepository)
    {
        _fuckRepository = fuckRepository;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        // 获取 "fuck" 实体中的数据
        var fuck = await _fuckRepository.FirstOrDefaultAsync();
        var executeTimes = fuck.ExecuteTimes;
        var maxRunTimes = fuck.MaxRunTimes;

        // 执行业务逻辑
        if (executeTimes < maxRunTimes)
        {
            // TODO: 执行业务逻辑

            // 更新 "fuck" 实体中的数据
            fuck.ExecuteTimes++;
            await _fuckRepository.UpdateAsync(fuck);
        }
    }
}

3. 配置 JobDetail

在 `Startup.cs` 文件中,使用 `JobBuilder` 类来配置 JobDetail。其中,`WithIdentity` 方法用于设置 Job 的标识符,`OfType` 方法用于设置 Job 的类型,`Build` 方法用于创建 JobDetail。

public void ConfigureServices(IServiceCollection services)
{
    // 注册 IFuckRepository 和 IBackgroundJobManager
    services.AddScoped();
    services.AddScoped();

    // 配置 Quartz
    services.AddQuartz(q =>
    {
        q.UseMicrosoftDependencyInjectionScopedJobFactory();

        q.AddJob(j => j
            .WithIdentity("FuckJob")
            .OfType()
        );
    });

    services.AddQuartzHostedService(options =>
    {
        options.WaitForJobsToComplete = true;
    });

    // 其他配置...
}

4. 配置 Trigger

使用 `TriggerBuilder` 类来配置 Trigger。其中,`WithIdentity` 方法用于设置 Trigger 的标识符,`StartNow` 方法用于设置 Trigger 的起始时间,`WithSimpleSchedule` 方法用于设置 Trigger 的调度方式,`Build` 方法用于创建 Trigger。

public void ConfigureServices(IServiceCollection services)
{
    // 注册 IFuckRepository 和 IBackgroundJobManager
    services.AddScoped();
    services.AddScoped();

    // 配置 Quartz
    services.AddQuartz(q =>
    {
        q.UseMicrosoftDependencyInjectionScopedJobFactory();

        q.AddJob(j => j
            .WithIdentity("FuckJob")
            .OfType()
        );

        q.AddTrigger(t => t
            .WithIdentity("FuckTrigger")
            .StartNow()
            .WithSimpleSchedule(s => s
                .WithIntervalInSeconds(10)
                .RepeatForever()
            )
            .ForJob("FuckJob")
        );
    });

    services.AddQuartzHostedService(options =>
    {
        options.WaitForJobsToComplete = true;
    });

    // 其他配置...
}

在上述代码中,我使用了 `WithIntervalInSeconds(10)` 方法来设置任务调度的时间间隔为 10 秒,即每隔 10 秒执行一次任务。

5. 启动任务调度

在 `Program.cs` 文件中,启动任务调度。

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseAutofac()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup();
        })
        .UseQuartz();

6. 其他操作

在使用 Quartz 时,还可以通过 `IScheduler` 接口来操作任务调度。例如,可以使用 `scheduler.PauseAll()` 方法来暂停所有任务调度,使用 `scheduler.ResumeAll()` 方法来恢复所有任务调度。

public class YourController : YourProjectNameControllerBase
{
    private readonly IScheduler _scheduler;

    public YourController(IScheduler scheduler)
    {
        _scheduler = scheduler;
    }

    public async Task PauseAllJobs()
    {
        // 暂停所有任务调度
        await _scheduler.PauseAll();

        return Ok();
    }

    public async Task ResumeAllJobs()
    {
        // 恢复所有任务调度
        await _scheduler.ResumeAll();

        return Ok();
    }
}

上述代码中的 `YourProjectNameControllerBase` 是你的项目中定义控制器的基类,需要根据实际情况进行修改。

Conversation History

https://monica.im/s/2e53179e?locale=enhttps://monica.im/s/2e53179e?locale=en

你可能感兴趣的:(ABP,开发语言,ABP)