C#调用Quartz.NET的完整实现。

业务场景:电商订单超时自动取消系统

假设我们需要实现一个电商系统,当客户下单后30分钟未支付,系统自动取消订单并释放库存。

1. 项目结构和依赖安装

首先安装必要的NuGet包:

复制代码
<PackageReference Include="Quartz" Version="3.8.1" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />

2. 领域模型和数据库上下文

复制代码
// 订单实体
public class Order
{
    public int Id { get; set; }
    public string OrderNo { get; set; }
    public decimal Amount { get; set; }
    public OrderStatus Status { get; set; } = OrderStatus.PendingPayment;
    public DateTime CreateTime { get; set; } = DateTime.Now;
    public DateTime? PaymentTime { get; set; }
    public DateTime? CancelTime { get; set; }
}

public enum OrderStatus
{
    PendingPayment = 0,    // 待支付
    Paid = 1,             // 已支付
    Cancelled = 2         // 已取消
}

// DbContext
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    
    public DbSet<Order> Orders { get; set; }
}

3. 定义Quartz作业

复制代码
using Quartz;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// 订单超时取消作业
[DisallowConcurrentExecution] // 防止并发执行
public class OrderTimeoutCancelJob : IJob
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<OrderTimeoutCancelJob> _logger;

    public OrderTimeoutCancelJob(IServiceProvider serviceProvider, ILogger<OrderTimeoutCancelJob> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation($"订单超时取消作业开始执行,时间:{DateTime.Now}");
        
        using var scope = _serviceProvider.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        try
        {
            // 查找创建时间超过30分钟且未支付的订单
            var timeoutOrders = await dbContext.Orders
                .Where(o => o.Status == OrderStatus.PendingPayment && 
                           o.CreateTime <= DateTime.Now.AddMinutes(-30) &&
                           !o.CancelTime.HasValue)
                .ToListAsync();

            if (!timeoutOrders.Any())
            {
                _logger.LogInformation("没有找到需要取消的超时订单");
                return;
            }

            // 批量取消订单
            foreach (var order in timeoutOrders)
            {
                order.Status = OrderStatus.Cancelled;
                order.CancelTime = DateTime.Now;
                
                _logger.LogWarning($"订单 {order.OrderNo} 因超时未支付已被自动取消");
                
                // 这里可以添加释放库存的逻辑
                await ReleaseInventory(order.Id);
            }

            await dbContext.SaveChangesAsync();
            
            _logger.LogInformation($"成功取消 {timeoutOrders.Count} 个超时订单");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "订单超时取消作业执行失败");
            throw; // 抛出异常让Quartz记录失败状态
        }
    }

    // 释放库存方法(示例)
    private async Task ReleaseInventory(int orderId)
    {
        // 实际项目中这里会调用库存服务
        await Task.Delay(10); // 模拟异步操作
        _logger.LogInformation($"订单 {orderId} 的库存已释放");
    }
}

4. 动态创建单次触发器作业(为每个新订单)

复制代码
// 动态订单超时监控作业
public class OrderMonitorJob : IJob
{
    private readonly ILogger<OrderMonitorJob> _logger;

    public OrderMonitorJob(ILogger<OrderMonitorJob> logger)
    {
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        var orderId = context.JobDetail.JobDataMap.GetInt("OrderId");
        var orderNo = context.JobDetail.JobDataMap.GetString("OrderNo");
        
        _logger.LogInformation($"监控订单 {orderNo} (ID:{orderId}) 的支付状态");

        // 这里可以添加检查订单是否已支付的业务逻辑
        // 如果已支付,可以删除这个监控作业
        await Task.Delay(100);
    }
}

5. 调度服务和管理器

复制代码
using Quartz;
using Quartz.Impl;
using Quartz.Spi;

// 自定义Job工厂(用于依赖注入)
public class IoCJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;

    public IoCJobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var jobDetail = bundle.JobDetail;
        var jobType = jobDetail.JobType;
        
        // 使用ActivatorUtilities创建作业实例,支持依赖注入
        return (IJob)ActivatorUtilities.CreateInstance(_serviceProvider, jobType);
    }

    public void ReturnJob(IJob job)
    {
        // 如果需要清理资源可以在这里处理
        if (job is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

// 调度服务
public interface ISchedulerService
{
    Task InitializeAsync();
    Task ScheduleOrderMonitoring(int orderId, string orderNo);
    Task CancelOrderMonitoring(int orderId);
    Task TriggerOrderTimeoutCheckNow();
}

public class SchedulerService : ISchedulerService
{
    private readonly IScheduler _scheduler;
    private readonly IConfiguration _configuration;
    private readonly ILogger<SchedulerService> _logger;

    public SchedulerService(
        IScheduler scheduler, 
        IConfiguration configuration,
        ILogger<SchedulerService> logger)
    {
        _scheduler = scheduler;
        _configuration = configuration;
        _logger = logger;
    }

    public async Task InitializeAsync()
    {
        try
        {
            // 配置调度器
            _scheduler.JobFactory = _scheduler.SchedulerServices?.JobFactory as IoCJobFactory;
            
            // 启动调度器
            await _scheduler.Start();
            _logger.LogInformation("Quartz调度器启动成功");

            // 创建定时全量检查任务(每分钟执行一次作为备用)
            await CreateRecurringOrderCheckJob();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "初始化调度器失败");
            throw;
        }
    }

    private async Task CreateRecurringOrderCheckJob()
    {
        // 创建重复执行的触发器(每分钟检查一次所有超时订单)
        var trigger = TriggerBuilder.Create()
            .WithIdentity("OrderTimeoutCheckTrigger", "OrderGroup")
            .WithCronSchedule("0 * * * * ?") // 每分钟第0秒执行
            .Build();

        var jobDetail = JobBuilder.Create<OrderTimeoutCancelJob>()
            .WithIdentity("OrderTimeoutCheckJob", "OrderGroup")
            .WithDescription("定时检查并取消超时订单")
            .Build();

        await _scheduler.ScheduleJob(jobDetail, trigger);
        _logger.LogInformation("定时订单检查任务创建成功");
    }

    public async Task ScheduleOrderMonitoring(int orderId, string orderNo)
    {
        try
        {
            // 计算精确的超时时间点(30分钟后)
            var timeoutTime = DateTimeOffset.Now.AddMinutes(30);
            
            // 创建一次性触发器
            var trigger = TriggerBuilder.Create()
                .WithIdentity($"OrderMonitor_{orderId}", "OrderMonitorGroup")
                .StartAt(timeoutTime) // 在指定时间触发一次
                .WithSimpleSchedule(x => x.WithMisfireHandlingInstructionFireNow()) // 错过执行立即触发
                .Build();

            var jobDetail = JobBuilder.Create<OrderMonitorJob>()
                .WithIdentity($"OrderMonitorJob_{orderId}", "OrderMonitorGroup")
                .UsingJobData("OrderId", orderId)
                .UsingJobData("OrderNo", orderNo)
                .Build();

            await _scheduler.ScheduleJob(jobDetail, trigger);
            
            _logger.LogInformation($"订单 {orderNo} 的监控任务已安排,将在 {timeoutTime} 执行");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"安排订单 {orderNo} 监控任务失败");
            throw;
        }
    }

    public async Task CancelOrderMonitoring(int orderId)
    {
        try
        {
            var jobKey = new JobKey($"OrderMonitorJob_{orderId}", "OrderMonitorGroup");
            var triggerKey = new TriggerKey($"OrderMonitor_{orderId}", "OrderMonitorGroup");
            
            // 删除特定的监控任务
            await _scheduler.UnscheduleJob(triggerKey);
            await _scheduler.DeleteJob(jobKey);
            
            _logger.LogInformation($"订单 {orderId} 的监控任务已取消");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"取消订单 {orderId} 监控任务失败");
        }
    }

    // 手动触发立即检查(用于测试或紧急处理)
    public async Task TriggerOrderTimeoutCheckNow()
    {
        var jobKey = new JobKey("OrderTimeoutCheckJob", "OrderGroup");
        await _scheduler.TriggerJob(jobKey);
        _logger.LogInformation("手动触发订单超时检查");
    }
}

6. 依赖注入配置

复制代码
// Program.cs 或 Startup.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddQuartzServices(this IServiceCollection services, IConfiguration configuration)
    {
        // 注册DbContext
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

        // 注册作业工厂
        services.AddSingleton<IJobFactory, IoCJobFactory>();
        
        // 注册调度器
        services.AddSingleton<ISchedulerFactory>(provider =>
        {
            var factory = new StdSchedulerFactory();
            return factory;
        });

        services.AddSingleton(provider =>
        {
            var factory = provider.GetRequiredService<ISchedulerFactory>();
            var scheduler = factory.GetScheduler().Result;
            scheduler.JobFactory = provider.GetRequiredService<IJobFactory>();
            return scheduler;
        });

        // 注册调度服务
        services.AddScoped<ISchedulerService, SchedulerService>();

        // 注册作业(必须注册为Scoped或Transient)
        services.AddScoped<OrderTimeoutCancelJob>();
        services.AddScoped<OrderMonitorJob>();

        // 配置Quartz hosted service(可选,用于后台服务)
        services.AddQuartz(q =>
        {
            q.UseMicrosoftDependencyInjectionJobFactory();
        });

        services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);

        return services;
    }
}

7. 使用示例

复制代码
// 在订单服务中使用
public class OrderService : IOrderService
{
    private readonly AppDbContext _dbContext;
    private readonly ISchedulerService _schedulerService;
    private readonly ILogger<OrderService> _logger;

    public OrderService(AppDbContext dbContext, ISchedulerService schedulerService, ILogger<OrderService> logger)
    {
        _dbContext = dbContext;
        _schedulerService = schedulerService;
        _logger = logger;
    }

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        // 创建订单
        var order = new Order
        {
            OrderNo = GenerateOrderNo(),
            Amount = request.Amount
        };

        _dbContext.Orders.Add(order);
        await _dbContext.SaveChangesAsync();

        // 安排订单超时监控
        await _schedulerService.ScheduleOrderMonitoring(order.Id, order.OrderNo);
        
        _logger.LogInformation($"订单 {order.OrderNo} 创建成功,已安排超时监控");
        
        return order;
    }

    public async Task<bool> PayOrderAsync(string orderNo)
    {
        var order = await _dbContext.Orders.FirstOrDefaultAsync(o => o.OrderNo == orderNo);
        if (order == null || order.Status != OrderStatus.PendingPayment)
            return false;

        // 更新订单状态
        order.Status = OrderStatus.Paid;
        order.PaymentTime = DateTime.Now;
        
        // 取消超时监控(因为已经支付了)
        await _schedulerService.CancelOrderMonitoring(order.Id);
        
        await _dbContext.SaveChangesAsync();
        
        _logger.LogInformation($"订单 {orderNo} 支付成功,已取消超时监控");
        return true;
    }

    private string GenerateOrderNo()
    {
        return $"ORD{DateTime.Now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
    }
}

执行过程详解

场景1:新订单创建

  1. 用户下单 → CreateOrderAsync被调用

  2. 订单保存到数据库,状态为"待支付"

  3. 调用_schedulerService.ScheduleOrderMonitoring()

  4. Quartz创建一个一次性触发器,设置在30分钟后执行

  5. 同时,每分钟执行的全局检查任务也在运行

场景2:正常支付

  1. 用户在30分钟内支付 → PayOrderAsync被调用

  2. 订单状态更新为"已支付"

  3. 调用_schedulerService.CancelOrderMonitoring()删除对应的监控任务

  4. 订单不会被超时取消

场景3:超时未支付

  1. 30分钟后,对应的监控任务触发(或每分钟的全局检查任务发现)

  2. OrderTimeoutCancelJob.Execute()方法执行

  3. 查询所有超时未支付的订单

  4. 批量更新订单状态为"已取消"

  5. 记录日志,释放库存

优势说明

  • 双重保障:既有针对每个订单的精确监控,又有全局定期检查作为备用

  • 资源高效:支付成功的订单会及时取消监控,避免无效检查

  • 可扩展:容易添加新的作业类型和调度策略

  • 容错性强:支持错过执行策略,确保重要业务不丢失

这个例子展示了Quartz.NET在实际业务中的完整应用,包括动态任务创建、依赖注入、错误处理等关键特性。

相关推荐
国思RDIF框架1 天前
RDIFramework.NET CS 敏捷开发框架 V6.3 版本重磅发布!.NET8+Framework双引擎,性能升级全维度进化
后端·.net
晨星shine2 天前
GC、Dispose、Unmanaged Resource 和 Managed Resource
后端·c#
用户298698530142 天前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
用户3667462526742 天前
接口文档汇总 - 2.设备状态管理
c#
用户3667462526742 天前
接口文档汇总 - 3.PLC通信管理
c#
Ray Liang3 天前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
赵榕3 天前
ClaimsPrincipal序列化为Json的正确姿势
.net
追逐时光者4 天前
一款使用 C# 编写专为 Windows 11 打造的文件资源管理器增强工具!
后端·.net