【问题标题】:Firebase Cloud Functions - create pdf, store to bucket and send via mailFirebase Cloud Functions - 创建 pdf,存储到存储桶并通过邮件发送
【发布时间】:2018-06-26 21:14:17
【问题描述】:

我正在开发一个 Firebase 函数,该函数在将新订单添加到实时数据库时触发。它所做的第一件事是创建一个 pdf 并将其通过管道传输到谷歌云存储桶。

在存储桶流的 .on("finish") 事件上,下一个函数启动,它应该通过电子邮件将管道 pdf 发送给客户。

一切似乎都奏效了,至少有一点。

首先我遇到了问题,附加的 pdf 总是空的。 (不只是空白。我也在记事本++中打开它,它真的都是空的)。当我检查 bucketFileStream.on("finished") 函数中的 doc 和 bucketFileSream vars 时,它们的长度都为 0。在 doc.end 之后直接检查 doc var 显示的长度大约为 612。

然后我更改了流程,在 sendOrderEmail 函数中,我还从存储桶中新创建的文件中打开了一个新的读取流。

现在我在附件中至少得到了一些 PDF 的内容,但从来没有完整的内容。

当我检查上传到存储桶的 PDF 时,它看起来应该。

我在 Google 上搜索了很多,并找到了一些同样针对这个主题的答案,但正如 cmets 中关于这些问题的答案一样,它们并没有完全有帮助。

我还检查了 nodemailer 文档如何正确传递附件并按照文档实现它。但没有成功。

我认为邮件是在读取流完成之前发送的。

这里是我使用的包版本:

  • “@google-cloud/storage”:“1.5.2”
  • "@types/pdfkit": "^0.7.35",
  • “firebase-admin”:“5.8.0”,
  • “firebase-functions”:“^0.7.3”
  • “nodemailer”:“4.4.1”,

谁能告诉我我做错了什么或提供一个使用当前包版本的工作示例?

这是让我发疯的代码......

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require("nodemailer");
const pdfkit = require("pdfkit");
const storage = require("@google-cloud/storage")({projectId: `${PROJECT_ID}`})

const mailTransport = nodemailer.createTransport({
  host: "smtp.office365.com",
  port: 587,
  secureConnection: false,
  auth: {
    user: "userName",
    pass: "userPassword"
 },
 tls: {
   ciphers: "SSLv3",
 }
});

exports.added = function(event) {
  const order = event.data.val();
  const userId = event.params.userId;

  // Load User Data by userId
  return admin
       .database()
       .ref("/users/" +userId)
       .once("value")
       .then(function (snapshot) {
               return generateOrderPDF(snapshot.val(), userId);
       });
};

function generateOrderPDF(user, userId) {
  const doc = new pdfkit();
  const bucket = storage.bucket(functions.config().bucket);
  const filename =  `/${userId}/test-` + Date.now() + ".pdf";
  const file = bucket.file(filename);
  const bucketFileStream = file.createWriteStream();

  // Pipe its output to the bucket
  doc.pipe(bucketFileStream);

 // Do creation Stuff....

  doc.end();

  bucketFileStream.on("finish", function () {
    return sendOrderEmail(user, filename);
  });

  bucketFileStream.on("error", function(err) {
    console.error(err);
  });
}

function sendOrderEmail(user, filename) {
  const email = user.email;
  const firstname = user.firstName;
  const mailOptions = {
    from: "test@test.test",
    to: email,
    subject: "Order"
  };

  const bucket = storage.bucket(functions.config().bucket);
  const file = bucket.file(filename);

  mailOptions.html = mailTemplate;
  mailOptions.attachments =  [{
    filename: "test.pdf",
    content: file.createReadStream()
  }];

  return mailTransport.sendMail(mailOptions).then(() => {
    console.log("New order email sent to:", email);
  }).catch(error => {
    console.error(error);
  });
}

【问题讨论】:

  • PDF 看起来应该在存储中吗?
  • 是的。存储桶中的 PDF 看起来不错。我已编辑问题以添加此附加信息。
  • 到现在为止我投入了很多时间。我发现,使用上面的代码,函数完成得太早了。如果我将 bucketFileStream 事件包装成一个承诺,执行顺序是正确的,但附加的 pdf 仍然不完整。我还测试了不通过管道输出,并将 pdf 从存储下载到 tmpDir 然后附加它。它总是附有相同的不完整的pdf。即使在发送前 10 秒超时,它仍然会失败。

标签: node.js firebase google-cloud-functions


【解决方案1】:

我的方法中的问题是在 pdfkit 库中,而不是在 nodemailer 或 firebase 中。下面的行似乎触发了结束事件。所以在这些行之后发送了pdf。在对他们发表评论后,一切都按原样进行。并不是像 Hari 所说的那样没有达到终点。

/*  doc.lineCap("underline")
    .moveTo(72, 321)
    .lineTo(570, 321)
    .stroke();*/

完成 MVP 后,我将进行根本原因分析,并将最终答案作为评论发布在此答案下方。

这是此用例的源代码的工作示例。它还确保在所有工作完成之前,firebase 功能不会完成。这是通过将事件驱动的 doc.on() 函数包装到一个 Promise 中来处理的,当 doc.on("end") 被调用时会解决这个问题。

exports.added = function(event) {
  const order = event.data.val();
  const userId = event.params.userId;

  // Load User Data by userId
  return admin.database().ref("/users/" + userId).once("value").then(function (snapshot) {
    return generatePDF(snapshot.val(), userId);
  });
};

function generatePDF(user, userId) {
  const doc = new pdfkit();
  const bucket = admin.storage().bucket(functions.config().moost.orderbucket);
  const filename =  "/${userId}/attachement.pdf";
  const file = bucket.file(filename);
  const bucketFileStream = file.createWriteStream();
  var buffers = [];
  let p = new Promise((resolve, reject) => {
    doc.on("end", function() {
      resolve(buffers);
    });
    doc.on("error", function () {
      reject();
    });
  });

  doc.pipe(bucketFileStream);
  doc.on('data', buffers.push.bind(buffers));

  //Add Document Text and stuff

  doc.end();

  return p.then(function(buffers) {
     return sendMail(buffers);
  });
}

function sendMail(buffers) {
  const pdfData = Buffer.concat(buffers);
  const mailOptions = {
    from: "FromName <from@example.com>",
    to: "to@example.com",
    subject: "Subject",
    html: mailTemplate,
    attachments: [{
      filename: 'attachment.pdf',
      content: pdfData
    }]
  };

  return mailTransport.sendMail(mailOptions).then(() => {
    console.log("New email sent to:", "to@example.com");
  }).catch(error => {
    console.error(error);
  });
}

【讨论】:

  • 嗨。有没有办法使用 html 代码来创建 pdf?我有一个模板,但是用这段代码我做不到。
  • 在此示例中,我使用 pdfkit 创建 pdf。您需要查看相应的文档:pdfkit.org/docs/getting_started.html,因为这对于该线程来说有点离题。如果您需要帮助,请创建一个新问题并提供有关您迄今为止尝试过的内容的信息。
【解决方案2】:

您的代码中的主要问题是 stream.on('finish') 永远不会完成。我也遇到了同样的问题。

不是流式传输,而是将 pdf 转换为缓冲区并作为附件发送。

以下对我来说很好,

 const doc = new pdfkit()
 const filename =  '/${userId}/test-' + Date.now() + ".pdf"
 const file = bucket.file(filename);
 const bucketFileStream = file.createWriteStream();
 doc.pipe(bucketFileStream);
 doc.end();
 var buffers = []
 doc.on('data', buffers.push.bind(buffers));
 doc.on('end',function(){
     let pdfData = Buffer.concat(buffers);
     '<<nodemailer stuffs goes here>
     'attach the doc as content
 });

【讨论】:

  • 感谢您的方法。但正如我在回答中提到的,我在 pdfkit 中发现了另一个错误,这是问题的根源。由于答案没有错,我留下了赞成票。它可以工作,正如我所问的那样是一个工作样本。
猜你喜欢
  • 1970-01-01
  • 2021-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-04
  • 1970-01-01
  • 2020-04-25
相关资源
最近更新 更多