筆記下背景定期執行工作
簡介
首先 ABP 有內建基本的 Background Worker 與 Background Job
這邊簡單釐清一下 Job 與 Worker 這兩個東西
- Job: API 觸發一個需要背景執行的作業時,呼叫 Job 來完成
例如:產生報表 API 觸發一個 Job 後立即返回,該 Job 會花費十分鐘產生報表後,以信件寄出報表
(連續觸發 Job 會進入 Queue 依序處理報表需求) - Woker: 程式啟動時就開始按照既定排程來執行特定作業
例如:定期刪除過期 Log、定期寄信
另外 ABP 有整合 Quartz 與 Hangfire 這兩個套件
功能比較強,內建儀表板,詳細功能請自行到對應官方網站了解
安裝
因應以下問題
如果您決定使用 Hangfire 作為後台 Worker/Job #2940,DbMigrator 將不再起作用|支援中心|總部基地商業 (abp.io)
- Domain
- PS> dotnet add package Volo.Abp.BackgroundWorkers.Hangfire
- Host (Web)
- PS> dotnet add package Volo.Abp.BackgroundJobs.HangFire
- PS> dotnet add package Hangfire.SqlServer
- HostModule > DependsOn
- typeof(AbpBackgroundWorkersHangfireModule)
- typeof(AbpBackgroundJobsHangfireModule)
- HostModule > context.Services.AddHangfire
- config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
P.S. Hangfire.SqlServer 版本需與 Volo.Abp.BackgroundJobs.HangFire 中使用的 Hangfire 一致
Sample
[DependsOn(
//...other dependencies
//Add the new module dependency
typeof(AbpBackgroundJobsHangfireModule),
typeof(AbpBackgroundWorkersHangfireModule)
)]
public class YourHostModule : AbpModule
{
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
// 預設任務失敗會自動重試,如果不想要可以用下面這段來設定重試次數
// config.UseFilter(new AutomaticRetryAttribute { Attempts = 0 });
});
}
}
這樣 Migratior 可以正常運作,Job 與 Worker 可以寫在 Domain 層
P.S. Job 與 Worker 不需要與表現層互動,放在 Domain
Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/
參照
Background Jobs | Documentation Center | ABP.IO
Background Workers Hangfire | Documentation Center | ABP.IO
abp/TokenCleanupBackgroundWorker.cs at dev · abpframework/abp (github.com)
實作
參照:Background Workers Hangfire | Documentation Center | ABP.IO
實際上要執行的作業需要繼承 HangfireBackgroundWorkerBase 並實作 DoWorkAsync
備註:HangfireBackgroundWorkerBase : BackgroundWorkerBase, IHangfireBackgroundWorker
public class MyLogWorker : HangfireBackgroundWorkerBase
{
public MyLogWorker()
{
RecurringJobId = nameof(MyLogWorker);
CronExpression = Cron.Daily();
}
public override Task DoWorkAsync()
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
註冊
最後在模塊的應用程式初始化階段加入該背景作業
context.AddBackgroundWorkerAsync<MyLogWorker>();
[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
await base.OnApplicationInitializationAsync(context);
context.AddBackgroundWorkerAsync<MyLogWorker>();
//If the interface is defined
//await context.AddBackgroundWorkerAsync<IMyLogWorker>();
}
}
Queue
注意
需更新至 ABP 7.0.0 版本才能正常使用 Queue, 之前的版本有 BUG 一律都會跑
Hangfire background job unable specific queues · Issue #13789 · abpframework/abp (github.com)
Info
預設 Queue 為 default
可以依照需求
指定 Server 要跑那些 Queue
abp/AbpHangfireOptions.cs at dev · abpframework/abp (github.com)
appsettings.json
{
"AbpHangfireOptions" : {
"ServerOptions": {
"Queues": [
"default1"
]
}
}
}
HostModule
private void ConfigureAbpHangfire(IConfiguration configuration)
{
Configure<AbpHangfireOptions>(configuration.GetSection(nameof(AbpHangfireOptions)));
}
指定 Worker 跑在哪個 Queue
public class QueueWork : HangfireBackgroundWorkerBase
{
public QueueWork()
{
RecurringJobId = nameof(QueueWork);
CronExpression = Cron.Minutely();
// 只會由具有以下 Queue 名稱的 Server 執行
Queue = "queue1";
}
public override Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
Logger.LogInformation("Executed QueueWork..!");
return Task.CompletedTask;
}
}
P.S. 這邊由於 Woker 指定由 queue1
Queue執行,而 Server 並無設定要處理此 Queue (queue1
),因此不會處理
Job 版本指定 queue
public class TestJob
: AsyncBackgroundJob<TestArgs>, ITransientDependency
{
[Queue("queue1")]
public override Task ExecuteAsync(EdiTestArgs args)
{
Logger.LogInformation("Do something...");
return Task.CompletedTask;
}
}
例外
參照:AbpBackgroundWorkersHangfireModule exception without using hangfire configuration
我實際跑的時候會遇到錯誤
JobStorage.Current property value has not been initialized.
You must set it before using Hangfire Client or Server API.
需要再加上一行 Code
GlobalConfiguration.Configuration.UseSqlServerStorage(configuration.GetConnectionString("Default"));
如下所示
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
GlobalConfiguration.Configuration.UseSqlServerStorage(configuration.GetConnectionString("Default"));
}
儀錶板
- UseHangfireDashboard 應該在你的 Startup 類中的認證和授權中間件之後被調用(可能在最後一行)。
否則,授權將總是失敗。 - UseHangfireDashboard 應該在 app.UseConfiguredEndpoints() 之前添加到請求管道。
要求登入使用者具有指定權限名稱 MyHangFireDashboardPermissionName
// ...
app.UseAuthentication();
// ...
app.UseAuthorization();
// ...
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[]
{
new AbpHangfireAuthorizationFilter(
requiredPermissionName: "MyHangFireDashboardPermissionName")
}
});
app.UseConfiguredEndpoints();
僅要求登入
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }
});
全開放
app.UseHangfireDashboard();