首页 > 解决方案 > Node.js Fetch() 内存中用于 Nodemailer 附件的 PDF 文件缓冲区

问题描述

我有一个夜间进程,通过调用我创建的端点来发送预定报告,该端点返回 PDF 格式的实时报告。

我遇到的问题是将端点返回的二进制文件放入一个缓冲区,Nodemailer 可以使用它来附加 PDF 报告。

我只是使用从 fetch() 和 arrayBuffer() 返回的 Promise。我没有使用异步等待。

下面的代码片段使用 arrayBuffer() 和 Buffer.from() 工作,但我想知道是否有更有效的方法来处理这个,特别是在处理大型 PDF 文件时。

我更喜欢在内存中工作而不是写入磁盘。我在 Express 中分配了很多内存,但还没有看到任何内存问题。

const sendPDFReport = (reportScheduleId, cb) => {

let scheduleObj;
//*** Get Report Schedule Data from MongoDB and Assign to scheduleObj

let emailAddresses = [];
//*** Push Recipient Emails into Email Array

//*** Do Work Like Build Fetch URL and Connection Properties Object ie., 

let fetchBody={};
//*** Build JSON object of POST Params and assign to fetchBody

let fetchURL = process.env.APPSERVER_URL+some_report_path;

let config = {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(fetchBody),
    responseType: 'blob'
};

let transporter = nodemailer.createTransport({
    host: 'mail.whatever.com',
    service: "Outlook365",
    secure: true,
    port: 465,
    auth: {
        user: process.env.NO_REPLY,
        pass: process.env.NO_REPLY_PW
    },
    tls: {
        ciphers: 'SSLv3',
        rejectUnauthorized: false
    }
});


   fetch(fetchURL, config)
    .then(response => {

        if (response.ok) {

        //*** PDF Binary Response Buffering for Nodemailer
            
        response.arrayBuffer()
           .then(resBufferAr => {
             const pdfBuffer = Buffer.from(resBufferAr);
             
             // **** Build Email mailOptions for Nodemailer Transporter Object ***

             let mailOptions = {
                 from: process.env.NO_REPLY,
                 to: emailAddresses.join([separator = ',']),
                 subject: 'Scheduled Report:  ' + scheduleObj.reportType + ' ' + scheduleObj.selectedReport,
                 html: '<h4> See Attached Report </h4>';
             
              //*** Nodemailer Attachment Section

                 attachments: [{
                     filename: scheduleObj.reportType + '_' + scheduleObj.selectedReport + '_' + now + '.pdf',
                     content: pdfBuffer,
                     encoding: 'base64',
                     contentType: 'application/pdf'
                 }]
              };

             transporter.sendMail(mailOptions, function (err) {
                if (err) {
                   console.log('Transporter Error:  ' + err);
                   return cb(err);
                }
                return cb(null);
             }
   })
   .catch((err) => {
            console.log('Problem Processing Alert Notification:  ' + err);
            return cb(err);
   })

    return cb(null);

}

任何意见或建议都会很棒。

标签: nodemailerarraybuffer

解决方案


是的,有一种更有效的方法:Streams

简短的版本是,流是 Node 中的内置数据结构,它允许您对传入的数据块进行操作,而不是在处理之前等待将全部数据加载到内存中。

node-fetch支持流作为响应负载nodemailer支持流作为附件,因此您可以有效地将获取响应的主体通过管道传输到 nodemailer。从理论上讲,您的机器实际上永远不会在任何时候将整个 PDF 保存在内存中,而是只有 PDF 片段,当它们进入时,它只会从一侧网络传递到另一侧网络(尽管有原因这有时不成立)。

我尚未对此进行全面测试,但这至少应该接近涉及 and 之间的流的工作解决node-fetch方案nodemailer

if (response.ok) {
    let readableStream = response.body;

    let mailOptions = {
        from: process.env.NO_REPLY,
        to: emailAddresses.join(','),
        subject: 'Scheduled Report:  ' + scheduleObj.reportType + ' ' + scheduleObj.selectedReport,
        html: '<h4> See Attached Report </h4>',

        attachments: [{
            filename: scheduleObj.reportType + '_' + scheduleObj.selectedReport + '_' + now + '.pdf',
            content: readableStream,
            encoding: 'base64',
            contentType: 'application/pdf'
        }]
    };

   // ...the rest of your code

更新:虽然我没有使用node-fetch,但我确实在从磁盘读取 3MB 文件并通过电子邮件将其发送给自己时测试了内存使用情况nodemailer。在将整个文件读入内存然后将其传递给 之后nodemailer,驻留集大小 (rss) 约为 48MB,外部 C++ 堆约为 11MB。相反,当给出nodemailer文件的读取流时,rss 约为 37MB,外部堆约为 4MB。所以肯定更有效率。


推荐阅读