.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
相关产品推荐
相关产品推荐

