Hangfire定时部署(.NET 8 + SQL Server)

一、创建 .NET 8 项目

首先创建一个 .NET 8 项目(ASP.NET Core Web 应用或控制台应用),这里以 ASP.NET Core Web API 为例。

二、安装必要的 NuGet 包

复制代码
核心包(适用于 ASP.NET Core)
Install-Package Hangfire.AspNetCore

存储后端(选择一个)
Install-Package Hangfire.SqlServer      # SQL Server
Install-Package Hangfire.Redis.StackExchange  # Redis
Install-Package Hangfire.PostgreSql    # PostgreSQL

其他拓展
Install-Package Microsoft.Data.SqlClient
Install-Package Swashbuckle.AspNetCore

三、完整配置步骤(.NET 8 + SQL Server)

  1. 配置连接字符串
    在 appsettings.json 中添加数据库连接:
    需先创建一个数据库

    Windows本机连接
    {
    "ConnectionStrings": {
    "HangfireConnection": "Server=localhost;Database=HangfireNet8;Integrated Security=True;TrustServerCertificate=True"
    }
    }

    IP账号密码连接
    "ConnectionStrings": {
    "HangfireConnection": "Server=127.0.0.1;Database=HangfireDB;User Id=sa;Password=123456;TrustServerCertificate=True;Encrypt=False"
    }

  2. 配置 Program.cs

csharp 复制代码
using Hangfire;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Extensions.Logging;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

// 添加 Hangfire 服务
builder.Services.AddHangfire(configuration => configuration
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    // 配置 SQL Server 存储
    .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
    {
        CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
        SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
        QueuePollInterval = TimeSpan.Zero,
        UseRecommendedIsolationLevel = true,
        DisableGlobalLocks = true,
        EnableHeavyMigrations = true
    })
);

// 添加 Hangfire 服务器
builder.Services.AddHangfireServer(options =>
{
    options.WorkerCount = Math.Max(Environment.ProcessorCount * 5, 10);
    options.Queues = new[] { "high", "medium", "low" };
    options.ShutdownTimeout = TimeSpan.FromMinutes(1);
});

// 添加控制器和Swagger服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// 注册应用服务
builder.Services.AddScoped<ITaskService, TaskService>();

var app = builder.Build();
var isDevelopment = app.Environment.IsDevelopment();

// 配置HTTP请求管道
if (isDevelopment)
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();

// 启用 Hangfire 仪表板
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new HangfireAuthFilter(isDevelopment) }
});

// 定义任务
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var taskService = services.GetRequiredService<ITaskService>();

        // 1. 即时任务
        BackgroundJob.Enqueue(() => taskService.ProcessData("initial"));

        // 2. 延迟任务(30分钟后执行)
        BackgroundJob.Schedule(
            () => taskService.SendNotification("delayed"),
            TimeSpan.FromMinutes(1)
        );

        // 3. 循环任务(每小时执行一次)
        RecurringJob.AddOrUpdate(
            "hourly-cleanup",
            () => taskService.CleanupResources(),
            Cron.Hourly,
            TimeZoneInfo.Local
        );

        // 4. 延续任务
        var parentJobId = BackgroundJob.Enqueue(() => taskService.ProcessOrder(1001));
        BackgroundJob.ContinueWith(
            parentJobId,
            () => taskService.CompleteOrder(1001)
        );
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "注册Hangfire任务时发生错误");
    }
}

app.MapControllers();

app.Run();

// 自定义授权过滤器 - 修复空值问题
public class HangfireAuthFilter : IDashboardAuthorizationFilter
{
    private readonly bool _isDevelopment;

    public HangfireAuthFilter(bool isDevelopment)
    {
        _isDevelopment = isDevelopment;
    }

    public bool Authorize(DashboardContext context)
    {
        // 确保上下文不为空
        if (context == null)
            return false;

        // 获取HTTP上下文并检查
        var httpContext = context.GetHttpContext();
        if (httpContext == null)
            return false;

        // 开发环境直接允许访问
        if (_isDevelopment)
            return true;

        // 生产环境检查是否为本地访问
        var localIpAddress = httpContext.Connection.LocalIpAddress;
        if (localIpAddress == null)
            return false;

        // 明确检查是否为回环地址(IPv4和IPv6)
        return IPAddress.IsLoopback(localIpAddress);
    }
}

// 任务服务接口
public interface ITaskService
{
    void ProcessData(string data);
    void SendNotification(string message);
    void CleanupResources();
    void ProcessOrder(int orderId);
    void CompleteOrder(int orderId);
}

// 任务服务实现
public class TaskService : ITaskService
{
    private readonly ILogger<TaskService> _logger;

    // 确保日志服务不为空
    public TaskService(ILogger<TaskService> logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public void ProcessData(string data)
    {
        if (string.IsNullOrEmpty(data))
            throw new ArgumentException("数据不能为空", nameof(data));

        _logger.LogInformation("处理数据: {Data}", data);
    }

    public void SendNotification(string message)
    {
        if (string.IsNullOrEmpty(message))
            throw new ArgumentException("消息不能为空", nameof(message));

        _logger.LogInformation("发送通知: {Message}", message);
    }

    public void CleanupResources()
    {
        _logger.LogInformation("清理资源于 {Time}", DateTime.Now);
    }

    public void ProcessOrder(int orderId)
    {
        if (orderId <= 0)
            throw new ArgumentOutOfRangeException(nameof(orderId), "订单ID必须大于0");

        _logger.LogInformation("处理订单: {OrderId}", orderId);
    }

    public void CompleteOrder(int orderId)
    {
        if (orderId <= 0)
            throw new ArgumentOutOfRangeException(nameof(orderId), "订单ID必须大于0");

        _logger.LogInformation("订单完成: {OrderId}", orderId);
    }
}

四、数据库初始化

首次运行应用时,Hangfire 会自动在指定的数据库中创建所需的表结构(约 15 张表),无需手动创建。

五、访问 Hangfire 仪表板

启动应用后,访问 https://localhost:端口/hangfire 即可打开管理界面,在这里可以:

查看所有任务的执行状态

手动触发、暂停或删除循环任务

重试失败的任务

监控服务器状态和性能