You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core API无影响后台导出任务改造方案技术问询

CRM大数量数据导出改造为后台任务方案

一、核心改造思路

将同步等待的导出逻辑拆分为即时响应和后台异步执行两部分:用户请求后立即返回提示,导出任务交由后台进程处理,完成后自动发送邮件。

二、具体修改步骤

1. 调整API端点返回值

原API返回FileContentResult,现在改为返回提示信息,无需等待导出完成:

[HttpPost("export-data")]
public IActionResult TriggerExport()
{
    // 获取当前用户邮箱(用于后续发送邮件)
    var userEmail = User.FindFirstValue(ClaimTypes.Email);

    // 将导出任务加入后台队列
    _backgroundTaskQueue.QueueBackgroundWorkItem(async token =>
    {
        await _exportService.ExportAndSendEmailAsync(userEmail, token);
    });

    return Ok(new { Message = "您的下载流程已启动,15分钟内将收到含报告的邮件" });
}

2. 重构导出服务逻辑

将原ExportData方法拆分为数据处理、文件生成、邮件发送三部分,新增异步方法:

public async Task ExportAndSendEmailAsync(string userEmail, CancellationToken cancellationToken)
{
    try
    {
        // 1. 分批查询数据(避免一次性加载50万条导致内存溢出)
        var data = await _dbContext.Customers
            .Where(c => c.CreatedDate >= DateTime.Now.AddMonths(-6))
            .AsNoTracking()
            .ToListAsync(cancellationToken);

        // 2. 生成Excel文件(建议用EPPlus等高效库,流式写入降低内存压力)
        byte[] excelBytes;
        using (var package = new ExcelPackage())
        {
            var worksheet = package.Workbook.Worksheets.Add("近6个月数据");
            // 填充表头和数据(此处省略具体逻辑)
            excelBytes = package.GetAsByteArray();
        }

        // 3. 发送带附件的邮件
        var message = new MimeMessage();
        message.From.Add(new MailboxAddress("CRM系统", "crm@yourcompany.com"));
        message.To.Add(new MailboxAddress("", userEmail));
        message.Subject = "您的CRM数据导出报告已生成";
        message.Body = new TextPart("plain") { Text = "请查收附件中的数据报告" };

        // 添加附件
        var attachment = new MimePart("application", "vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        {
            Content = new MimeContent(new MemoryStream(excelBytes)),
            ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
            ContentTransferEncoding = ContentEncoding.Base64,
            FileName = $"Data_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
        };
        var multipart = new Multipart("mixed");
        multipart.Add(message.Body);
        multipart.Add(attachment);
        message.Body = multipart;

        using (var client = new SmtpClient())
        {
            await client.ConnectAsync("smtp.yourcompany.com", 587, SecureSocketOptions.StartTls, cancellationToken);
            await client.AuthenticateAsync("smtp-user", "smtp-password", cancellationToken);
            await client.SendAsync(message, cancellationToken);
            await client.DisconnectAsync(true, cancellationToken);
        }
    }
    catch (Exception ex)
    {
        // 记录错误日志,可追加发送失败通知邮件逻辑
        _logger.LogError(ex, "导出任务执行失败,用户邮箱:{UserEmail}", userEmail);
    }
}

3. 实现后台任务队列

使用ASP.NET Core内置的IHostedService实现可靠的后台任务队列,避免任务因应用重启丢失:

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
    Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new();
    private readonly SemaphoreSlim _signal = new(0);

    public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
    {
        if (workItem == null) throw new ArgumentNullException(nameof(workItem));
        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);
        return workItem;
    }
}

public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;
    private readonly IBackgroundTaskQueue _taskQueue;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger)
    {
        _taskQueue = taskQueue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("后台任务队列服务启动");

        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = await _taskQueue.DequeueAsync(stoppingToken);
            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "后台任务执行出错");
            }
        }

        _logger.LogInformation("后台任务队列服务停止");
    }
}

4. 注册服务依赖

在Program.cs中注册后台队列和托管服务:

builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddHostedService<QueuedHostedService>();

三、关键注意事项

  • 数据分批处理:避免一次性加载50万条数据到内存,可使用分页查询或流式读取
  • 任务可靠性:若需更高可靠性,可使用第三方消息队列(如RabbitMQ)替代内存队列,防止应用重启丢失任务
  • 超时与取消:所有异步操作传入CancellationToken,支持任务取消
  • 错误处理:记录详细错误日志,必要时给用户发送失败通知
  • 邮件服务:使用异步邮件客户端,避免阻塞后台任务

内容的提问来源于stack exchange,提问作者user2837480

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:10:32