【问题标题】:How to send file by email from Firebase Storage ObjectMetadata如何通过电子邮件从 Firebase 存储 ObjectMetadata 发送文件
【发布时间】:2021-05-30 07:42:57
【问题描述】:

我正在开发一个触发器函数,用于侦听指定存储桶中的新对象。我想要的是使用 nodemailer 发送通过电子邮件返回的对象。

const transport = nodemailer.createTransport({
  host: "smtp.gmail.com",
  port: 465,
  secure: true,
  auth: {
    user: "xxxxxxxxxx@gmail.com",
    pass: "xxxxxxxxxxxxxxx"
  }
});

exports.sendConfirmationEmail = functions.storage.bucket('bucket-name').object().onFinalize(async (object) => {
  const orderID = object.name.slice(0, -4);
  admin.database().ref('/pedidos/' + orderID).once('value', (snapshot) => {
    return sendEmail(snapshot.val().customer, snapshot.val().email, snapshot.val().number, /*FILE*/);
  });
});

function sendEmail(user, email, order, file){
  console.log("Sending Email...");
  return transport.sendMail({
      from: "XXXXXX <xxxxxxxxxx@gmail.com>",
      to: email,
      subject: "Confirmación pedido " + order,
      html: `
            <h1> Estiamdo ${user}, </h1>
            <p> Hemos recibido su pedido correctamente. Le mantendremos infromado de su estado. </p>
            <p> Gracias por confiar en nosotros </p>
          `,
      attachment: file
    })
    .then(r => r)
    .catch(e => {
      console.log("An error has ocurred " + e);
    });
  }
}

有人可以帮忙吗?

【问题讨论】:

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


    【解决方案1】:

    首先让我们修复 Cloud Function 的主要部分。在为您的函数编写代码时,通常不要使用 Admin SDK 的回调 API。

    admin.database().ref('/pedidos/' + orderID).once('value', (snapshot) => { /* ... */ });
    

    应该是

    admin.database().ref('/pedidos/' + orderID).once('value')
      .then((snapshot) => { /* ... */ });
    

    const snapshot = await admin.database().ref('/pedidos/' + orderID);
    

    确保returnawait 事件处理程序中的任何承诺也非常重要,否则您的代码可能随时终止,从而导致意外错误。

    由于您只发送 PDF 文档,我们将忽略所有非 PDF 文件。

    exports.sendConfirmationEmail = functions.storage.bucket('bucket-name').object().onFinalize(async (object) => {
      if (object.contentType !== "application/pdf")
        return; // ignore non-pdfs
      
      const orderID = object.name.slice(0, -4);
    
      // ↓↓ this return is needed
      return admin.database().ref('/pedidos/' + orderID).once('value')
        .then((snapshot) => {
          return sendEmail(snapshot.val().customer, snapshot.val().email, snapshot.val().number, /*FILE*/);
        });
    });
    

    接下来,我们继续您的 sendEmail 函数。在您当前的sendEmail 函数中,您错误地使用了attachment 而不是attachments。您还可以删除这些只会引入问题的行:

    .then(r => r) // doesn't do anything
    .catch(e => { // logs an error, but incorrectly prevents it being handled by .catch() elsewhere
      console.log("An error has ocurred " + e);
    });
    

    这使我们可以将sendEmail 重新定义为:

    function sendEmail (user, email, order, attachments = undefined) {
      return transport.sendMail({
        from: "XXXXXX <xxxxxxxxxx@gmail.com>",
        to: email,
        subject: "Confirmación pedido " + order,
        html: `
              <h1> Estiamdo ${user}, </h1>
              <p> Hemos recibido su pedido correctamente. Le mantendremos infromado de su estado. </p>
              <p> Gracias por confiar en nosotros </p>
              `,
        attachments
      });
    }
    

    接下来,让我们回顾一下attachments property 的文档:

    attachments 包含附件对象数组的消息对象中的选项。

    附件对象由以下属性组成:

    • filename - 文件名报告为附加文件的名称。允许使用 unicode。
    • content - 附件的字符串、缓冲区或流内容
    • path - 如果您想流式传输文件而不是包含它,则指向文件的路径(更适合较大的附件)
    • href – 文件的 URL(也允许数据 uri)
    • httpHeaders - 与 href 请求一起传递的可选 HTTP 标头,例如。 {authorization: "bearer ..."}
    • contentType - 附件的可选内容类型,如果未设置,则从文件名属性派生
    • contentDisposition - 附件的可选内容处置类型,默认为“附件”
    • cid - 在 HTML 消息源中使用内嵌图像的可选内容 ID
    • encoding - 如果设置且内容是字符串,则使用指定的编码将内容编码到缓冲区。示例值:“base64”、“hex”、“binary”等。如果您想在 JSON 格式的电子邮件对象中使用二进制附件,这很有用。
    • headers - 附件节点的自定义标头。与消息头的用法相同
    • raw - 是一个可选的特殊值,它覆盖当前 mime 节点的全部内容,包括 mime 标头。如果您想自己准备节点内容,这很有用

    现在我们知道每个附件对象可以使用什么,我们需要将该列表与我们可以从传递给Storage Event Cloud Function 事件处理程序的object 参数中提取的内容进行比较。主要属性包括:

    const fileBucket = object.bucket; // The Storage bucket that contains the file.
    const filePath = object.name; // File path in the bucket.
    const contentType = object.contentType; // File content type.
    const metageneration = object.metageneration; // Number of times metadata has been generated. New objects have a value of 1.
    

    因此,对于我们的附件,我们希望提供 filenamecontentcontentDispositioncontentType。因为我们发送的 Cloud Storage 对象不在磁盘或内存中,所以我们将通过将 Stream 作为附件对象的 content 属性将其从 Cloud Storage 流式传输到 nodemailer。这导致:

    const bucket = admin.storage().bucket(object.bucket);
    const remoteFile = bucket.file(object.name);
    
    const attachment = {
      filename: `order-${orderID}.pdf`,       // the attachment will be called `order-<ID>.pdf`
      content: remoteFile.createReadStream(), // stream data from Cloud Storage
      contentType: object.contentType,        // use appropriate content type
      contentDisposition: "attachment",       // this file is a downloadable attachment
    };
    

    我们现在可以将它们一起滚动并使用async/await 语法清理它:

    const transport = nodemailer.createTransport({ /* ... */ });
    
    exports.sendConfirmationEmail = functions.storage.bucket('bucket-name').object().onFinalize(async (object) => {
      if (object.contentType !== "application/pdf") {
        console.log("Content-Type was not application/pdf. Ignoring.");
        return; // ignore non-pdfs
      }
    
      // if (object.metageneration > 1) {
      //   console.log("Metageneration was greater than 1. Ignoring.");
      //   return; // ignore rewritten files
      // }
      
      try {
        const orderID = object.name.slice(0, -4);
    
        const orderSnapshot = await admin.database()
          .ref(`/pedidos/${orderID}`)
          .once('value');
    
        if (!orderSnapshot.exists) {
          console.error(`Order #${orderID} document not found`);
          return;
        }
    
        const { customer, email, number } = orderSnapshot.val();
    
        // prepare attachment
        const bucket = admin.storage().bucket(object.bucket);
        const remoteFile = bucket.file(object.name);
    
        const attachment = {
          filename: `order-${orderID}.pdf`,       // override name of the PDF
          content: remoteFile.createReadStream(), // stream data from Cloud Storage
          contentType: object.contentType,        // use appropriate content type
          contentDisposition: "attachment",       // this file is a downloadable attachment
        };
    
        console.log("Sending confirmation email...");
        await sendEmail(customer, email, number, [ attachment ]);
    
        console.log(`Email confirmation was sent successfully for Order #${orderID}`);
      } catch (error) {
        console.error("Unexpected error: ", error);
      }
    });
    
    function sendEmail (user, email, order, attachments = undefined) {
      return transport.sendMail({
        from: "XXXXXX <xxxxxxxxxx@gmail.com>",
        to: email,
        subject: "Confirmación pedido " + order,
        html: `
              <h1> Estiamdo ${user}, </h1>
              <p> Hemos recibido su pedido correctamente. Le mantendremos infromado de su estado. </p>
              <p> Gracias por confiar en nosotros </p>
              `,
        attachments
      });
    }
    

    注意:metageneration大于1时,你应该决定是否发送邮件。

    附录:我强烈建议使用functions.config() 来存储nodemailer 的用户名/密码组合等内容,而不是将它们写入您的代码中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-01
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 2014-09-04
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多