控制器内Fire and Forget Worker实现:长查询异步邮件通知方案问询
解决长耗时查询的Fire-and-Forget方案(Node.js+Express+Postgres)
嘿,这个场景我太熟悉了!云服务的超时限制真的头疼,不过用异步任务队列就能完美解决你的问题。核心思路就是把耗时的查询和邮件发送从HTTP请求的上下文里剥离出来,让请求一进来就返回200,剩下的脏活交给后台的工作进程去做。
下面是具体的实现步骤和代码修改:
第一步:安装必要依赖
我们用bullmq来做任务队列(可靠、支持重试和持久化),用nodemailer来发邮件:
npm install bullmq nodemailer
第二步:重构queries.js,把查询任务丢进队列
原来的getReport直接在请求里执行查询,现在改成只接收参数并添加任务到队列,立即返回响应:
const Queue = require('bullmq').Queue; // 创建一个名为'report-generator'的任务队列,默认依赖Redis(如果没有Redis,也可配置其他持久化存储) const reportQueue = new Queue('report-generator'); const getReport = (request, response) => { const { business_group_id, initial_date, final_date, user_email } = request.body; // 注意:需要让用户提供接收结果的邮箱地址 // 将查询参数和用户邮箱打包成任务,加入队列 reportQueue.add('generate-and-send-report', { business_group_id, initial_date, final_date, user_email }); // 立即返回200状态码,告知用户任务已启动 response.status(200).json({ message: '报告生成任务已启动,结果将发送到你的邮箱,请耐心等待' }); }; module.exports = { getReport };
第三步:创建工作进程worker.js处理队列任务
这个文件会单独运行,负责从队列取出任务、执行查询并发送邮件:
const Worker = require('bullmq').Worker; const Pool = require('pg').Pool; const nodemailer = require('nodemailer'); // 1. 初始化Postgres连接池(复用你原有的配置) const pool = new Pool({ user: 'xxx', host: 'xx.xxx.xx.xxx', database: 'xxxxxxxx', password: 'xxxxxxxx', port: xxxx, }); // 2. 配置邮件发送器(这里以Gmail为例,可替换为你的企业邮箱服务) const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: 'your-service-email@gmail.com', pass: 'your-app-specific-password' // Gmail需开启两步验证后使用应用专用密码 } }); // 3. 创建工作进程处理队列任务 const worker = new Worker('report-generator', async (job) => { const { business_group_id, initial_date, final_date, user_email } = job.data; try { // 执行耗时的查询操作 const results = await pool.query(`SELECT GIANT QUERY`, [business_group_id, initial_date, final_date]); // 格式化查询结果为友好的邮件内容(这里用JSON示例,也可导出为CSV附件) const emailContent = ` <h3>你的报告已生成</h3> <p>查询结果详情:</p> <pre>${JSON.stringify(results.rows, null, 2)}</pre> `; // 发送结果邮件 await transporter.sendMail({ from: '报告服务 <your-service-email@gmail.com>', to: user_email, subject: '你的数据报告已完成', html: emailContent }); return '任务完成:报告已发送至用户邮箱'; } catch (error) { // 错误处理:给用户发送失败通知 await transporter.sendMail({ from: '报告服务 <your-service-email@gmail.com>', to: user_email, subject: '报告生成失败', text: `抱歉,报告生成过程中出现错误:${error.message}` }); throw error; // 抛出错误让BullMQ自动重试(可配置重试次数和间隔) } }); // 监听工作进程日志,方便排查问题 worker.on('completed', (job) => { console.log(`任务 ${job.id} 处理完成`); }); worker.on('failed', (job, err) => { console.log(`任务 ${job.id} 处理失败: ${err.message}`); });
第四步:启动服务和工作进程
现在需要同时启动API服务和后台工作进程:
- 启动API:
node index.js - 启动工作进程:
node worker.js
生产环境中可以用PM2等进程管理器来托管这两个进程,确保它们持续运行。
方案优势
- 完全规避超时限制:HTTP请求瞬间返回200,不受云服务2分钟超时约束
- 任务可靠不丢失:BullMQ将任务持久化到Redis,就算API进程重启,任务也会被工作进程继续处理
- 可扩展性强:可启动多个工作进程并行处理大量任务,提升整体效率
可选优化点
- 返回任务ID给用户,支持查询任务状态(BullMQ内置任务状态查询API)
- 将查询结果导出为CSV附件发送,提升用户体验
- 自定义BullMQ的重试策略(如失败后5分钟重试,最多重试3次)
内容的提问来源于stack exchange,提问作者Matheus Lacerda
相关产品推荐
相关产品推荐

