Hangfire 背景作业管理器
Hangfire 是一款先进的背景作业管理器。您可以将 Hangfire 与 ABP 框架集成,用以替代 默认的背景作业管理器 。通过这种方式,您可以使用统一的背景作业 API,使代码与 Hangfire 解耦。当然,您也可以直接使用 Hangfire 的原生 API。
关于如何使用背景作业系统,请参阅 背景作业文档 。本文档仅展示如何安装和配置 Hangfire 集成。
安装
建议使用 ABP CLI 安装此包。
使用 ABP CLI
在项目文件夹(.csproj 文件所在目录)打开命令行窗口,输入以下命令:
abp add-package Volo.Abp.BackgroundJobs.HangFire
如果您尚未安装 ABP CLI,请先进行安装。关于其他安装方式,请参阅包详情页面。
手动安装
如需手动安装:
将 Volo.Abp.BackgroundJobs.HangFire NuGet 包添加到您的项目:
dotnet add package Volo.Abp.BackgroundJobs.HangFire在模块的依赖列表中添加
AbpBackgroundJobsHangfireModule:
[DependsOn(
//...其他依赖
typeof(AbpBackgroundJobsHangfireModule) //添加新模块依赖
)]
public class YourModule : AbpModule
{
}
配置
您可以为 Hangfire 安装任意存储方式,最常见的是 SQL Server(参见 Hangfire.SqlServer NuGet 包)。
安装这些 NuGet 包后,需配置项目以使用 Hangfire。
- 首先,修改
Module类(例如:<YourProjectName>HttpApiHostModule),在ConfigureServices方法中添加 Hangfire 的存储和连接字符串配置:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... 其他配置
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
必须为 Hangfire 配置存储。
- 如需使用 Hangfire 仪表板,可在
Module类的OnApplicationInitialization方法中添加UseAbpHangfireDashboard调用:
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// ... 其他中间件
app.UseAbpHangfireDashboard(); //应在 app.UseConfiguredEndpoints() 之前添加到请求管道
app.UseConfiguredEndpoints();
}
AbpHangfireOptions
您可以通过配置 AbpHangfireOptions 中的 BackgroundJobServerOptions 来自定义服务器:
Configure<AbpHangfireOptions>(options =>
{
// 若不设置 ServerOptions,ABP 将使用默认的 BackgroundJobServerOptions 实例
options.ServerOptions = new BackgroundJobServerOptions
{
Queues = ["default", "alpha"],
//... 其他属性
};
});
无需调用
AddHangfireServer方法,ABP 将使用 AbpHangfireOptions 的ServerOptions创建服务器。
指定队列
可使用 QueueAttribute 指定队列:
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
namespace MyProject
{
[Queue("alpha")]
public class EmailSendingJob
: AsyncBackgroundJob<EmailSendingArgs>, ITransientDependency
{
private readonly IEmailSender _emailSender;
public EmailSendingJob(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public override async Task ExecuteAsync(EmailSendingArgs args)
{
await _emailSender.SendAsync(
args.EmailAddress,
args.Subject,
args.Body
);
}
}
}
仪表板授权
Hangfire 仪表板提供背景作业的详细信息,包括方法名称和序列化参数,并允许执行重试、删除、触发等管理操作。因此,限制对仪表板的访问至关重要。 默认情况下仅允许本地请求,但您可参照 Hangfire 的官方文档进行调整。
通过 AbpHangfireAuthorizationFilter 类,可将 Hangfire 仪表板集成到 ABP 授权系统。该类定义于 Volo.Abp.Hangfire 包中。以下示例检查当前用户是否已登录应用:
app.UseAbpHangfireDashboard("/hangfire", options =>
{
options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() };
});
AbpHangfireAuthorizationFilter是授权过滤器的实现。
AbpHangfireAuthorizationFilter
AbpHangfireAuthorizationFilter 类包含以下字段:
enableTenant(bool,默认:false): 启用/禁用租户用户访问 Hangfire 仪表板。requiredPermissionName(string,默认:null): 仅当当前用户拥有指定权限时才可访问仪表板。requiredRoleNames(string[],默认:[]): 仅当当前用户拥有指定角色之一时才可访问仪表板。
如需更多策略,可使用 AbpHangfireAuthorizationFilter 类的 PolicyBuilder 属性:
app.UseAbpHangfireDashboard("/hangfire", options =>
{
var hangfireAuthorizationFilter = new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName");
//hangfireAuthorizationFilter.PolicyBuilder.AddRequirements(new PermissionRequirement("YourPermissionName"));
//hangfireAuthorizationFilter.PolicyBuilder.RequireRole("YourCustomRole");
//hangfireAuthorizationFilter.PolicyBuilder.Requirements.Add(new YourCustomRequirement());
options.AsyncAuthorization = new[]
{
hangfireAuthorizationFilter
};
});
重要提示:UseAbpHangfireDashboard 应在 Startup 类中的认证和授权中间件之后调用(通常位于最后一行),否则授权将始终失败!
API 项目中的仪表板授权
若在采用非 Cookie 认证(如 JWT Bearer)的 API 项目中使用 Hangfire 仪表板,/hangfire 页面无法认证用户。
此时,可添加 Cookie 授权方案进行用户认证。最佳实践是结合使用 Cookie 和 OpenIdConnect 认证方案,这需要创建新的 OAuth2 客户端并在 appsettings.json 文件的 AuthServer 部分添加 ClientId 和 ClientSecret 属性。
最终代码示例如下:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddAbpJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
options.Audience = "MyProjectName";
options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)
? CookieAuthenticationDefaults.AuthenticationScheme
: null;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.ResponseType = OpenIdConnectResponseType.Code;
options.ClientId = configuration["AuthServer:HangfireClientId"];
options.ClientSecret = configuration["AuthServer:HangfireClientSecret"];
options.UsePkce = true;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("roles");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("MyProjectName");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
}
app.Use(async (httpContext, next) =>
{
if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase))
{
var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!authenticateResult.Succeeded)
{
await httpContext.ChallengeAsync(
OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString
});
return;
}
}
await next.Invoke();
});
app.UseAbpHangfireDashboard("/hangfire", options =>
{
options.AsyncAuthorization = new[]
{
new AbpHangfireAuthorizationFilter()
};
});
抠丁客


