首先让我们修复 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);
确保return 或await 事件处理程序中的任何承诺也非常重要,否则您的代码可能随时终止,从而导致意外错误。
由于您只发送 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.
因此,对于我们的附件,我们希望提供 filename、content、contentDisposition 和 contentType。因为我们发送的 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 的用户名/密码组合等内容,而不是将它们写入您的代码中。