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在实际业务中的完整应用,包括动态任务创建、依赖注入、错误处理等关键特性。

相关推荐
游乐码9 小时前
c#变长关键字和参数默认值
学习·c#
全栈小59 小时前
【C#】合理使用DeepSeek相关AI应用为我们提供强有力的开发工具,在.net core 6.0框架下使用JsonNode动态解析json字符串,如何正确使用单问号和双问号做好空值处理
人工智能·c#·json·.netcore·deepseek
wearegogog12310 小时前
基于C#的TCP/IP通信客户端与服务器
服务器·tcp/ip·c#
2501_9307077816 小时前
使用C#代码在 PowerPoint 演示文稿中插入表格
开发语言·c#·powerpoint
少控科技16 小时前
C#基础训练营 - 01 - 数据类型
开发语言·c#
1314lay_100717 小时前
Vue3 + Element Plus项目和C# .Net 7.0 Core后端API项目发布部署到服务器
服务器·前端·javascript·vue.js·elementui·c#·.net
郝亚军17 小时前
c#如何编译、通过icd文件生成static_model.c和static_model.h
开发语言·c#
Traced back17 小时前
保姆级C#进阶教程:从入门到企业级开发,小白也能秒懂!
开发语言·c#
柒儿吖18 小时前
CharLS 无损 JPEG-LS 库在 OpenHarmony 的 lycium 适配与 ctest 验证
c++·华为·c#·harmonyos