Node.js Nodemailer 附件的内存中的 Fetch() PDF 文件缓冲区

Node.js Fetch() PDF File Buffer In Memory for Nodemailer Attachment

我有一个夜间进程,它通过调用我创建的端点发送预定报告,该端点 returns 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);

}

任何意见或建议都将是很好的。

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

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

node-fetch 支持 streams as response payloads and nodemailer supports streams as attachments,因此您可以有效地将获取响应的主体通过管道传输到 nodemailer。从理论上讲,您的机器实际上从来没有在任何时候将整个 PDF 存储在内存中,而是只有 PDF 的一部分,当它们进入时,它只会从一侧网络传递到另一侧网络(尽管有一些原因这有时站不住脚)。

我还没有完全测试过这个,但这至少应该接近于涉及 node-fetchnodemailer 之间的流的工作解决方案:

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。所以它肯定更有效率。