为什么我的PDF不间歇性保存在我的节点功能?



首先,让我说,我是一个非常新的后端应用程序和Nodejs。我主要做移动开发,所以我的语言知识有限。

我在Firebase Functions中有一个端点,它可以从Firestore中的数据和存储中的图像中构建和保存PDF。PDF构建工作得很好,我没有得到任何错误。但是,保存PDF的最后一段代码并没有一致地执行。我的日志报表从来没有被解雇,但有时PDF文件被保存。我认为这与我使用异步方法有关,但我不确定。这段代码有什么明显的错误吗?这是我正在使用的全部代码。

const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
const Printer = require('pdfmake');
const fonts = require('pdfmake/build/vfs_fonts.js');
const {Storage} = require('@google-cloud/storage');
const url = require('url');
const https = require('https')
const os = require('os');
const fs = require('fs');
const path = require('path');

const storage = new Storage();
const bucketName = '<BUCKET NAME REMOVED FOR THIS QUESTION>'
admin.initializeApp({
serviceAccountId: 'firebase-adminsdk-ofnne@perimeter1-d551f.iam.gserviceaccount.com',
storageBucket: bucketName
});
const bucket = admin.storage().bucket()
const firestore = admin.firestore()
const fontDescriptors = {
Roboto: {
normal: Buffer.from(fonts.pdfMake.vfs['Roboto-Regular.ttf'], 'base64'),
bold: Buffer.from(fonts.pdfMake.vfs['Roboto-Medium.ttf'], 'base64'),
italics: Buffer.from(fonts.pdfMake.vfs['Roboto-Italic.ttf'], 'base64'),
bolditalics: Buffer.from(fonts.pdfMake.vfs['Roboto-Italic.ttf'], 'base64'),
}
};
function buildLog(data) {
const filePath = data.imageReference;
const fileName = path.basename(filePath);
const tempFilePath = path.join(os.tmpdir(), fileName);
return {
stack: [
{
image: tempFilePath,
fit: [130, 220]
},
{
text: data["logEventType"],
style: 'small'
},
{
text: data["date"],
style: 'small'
}
],
unbreakable: true,
width: 130
}
}
function buildLogsBody(data) {
var body = [];
var row = []
var count = 0
data.forEach(function(logData) {
const log = buildLog(logData)
row.push(log)
count = count + 1
if (count == 4) {
body.push([{columns: row, columnGap: 14}])
body.push([{text: 'n'}])
row = []
count = 0
}
});
body.push([{columns: row, columnGap: 14}])
return body;
}
function title(incidentTitle, pageNumber, logCount, messageCount) {
var pageTitle = "Incident Summary"
const logPageCount = Math.ceil(logCount / 8)
if (messageCount > 0 && pageNumber > logPageCount) {
pageTitle = "Message History"
}
var body = [{
text: incidentTitle + ' | ' + pageTitle,
style: 'header'
}]
return body
}
function messageBody(message) {
var body = {
stack: [
{
columns: [
{width: 'auto', text: message['senderName'], style: 'messageSender'},
{text: message['date'], style: 'messageDate'},
],
columnGap: 8,
lineHeight: 1.5
},
{text: message['content'], style: 'message'},
{text: 'n'}
],
unbreakable: true
}
return body
}
function buildMessageHistory(messages) {
var body = []
if (messages.length > 0) {
body.push({ text: "", pageBreak: 'after' })
}
messages.forEach(function(message) {
body.push(messageBody(message))
body.push('n')
})
return body
}
const linebreak = "n"
async function downloadImages(logs) {
await Promise.all(logs.map(async (log) => {
functions.logger.log('Image download started for ', log);
const filePath = log.imageReference;
const fileName = path.basename(filePath);
const tempFilePath = path.join(os.tmpdir(), fileName);
await bucket.file(filePath).download({destination: tempFilePath});
functions.logger.log('Image downloaded locally to', tempFilePath);
}));
}
//////////// PDF GENERATION /////////////////
exports.generatePdf = functions.https.onCall(async (data, context) => {
console.log("PDF GENERATION STARTED **************************")
// if (request.method !== "GET") {
//   response.send(405, 'HTTP Method ' + request.method + ' not allowed');
//   return null;
// }
const teamId = data.teamId;
const incidentId = data.incidentId;
const incidentRef = firestore.collection('teams/').doc(teamId).collection('/history/').doc(incidentId);
const incidentDoc = await incidentRef.get()
const messages = []
const logs = []
if (!incidentDoc.exists) {
throw new functions.https.HttpsError('not-found', 'Incident history not found.');
}
const incident = incidentDoc.data()
const incidentTitle = incident["name"]
const date = "date" //incident["completedDate"]
const address = incident["address"]

const eventLogRef = incidentRef.collection('eventLog')
const logCollection = await eventLogRef.get()
logCollection.forEach(doc => {
logs.push(doc.data())
})
functions.logger.log("Checking if images need to be downloaded");
if (logs.length > 0) {
functions.logger.log("Image download beginning");
await downloadImages(logs);
}
functions.logger.log("Done with image download");
const messagesRef = incidentRef.collection('messages')
const messageCollection = await messagesRef.get()
messageCollection.forEach(doc => {
messages.push(doc.data())
})
////////////// DOC DEFINITION ///////////////////////
const docDefinition = {
pageSize: { width: 612, height: 792 },
pageOrientation: 'portrait',
pageMargins: [24,60,24,24],
header: function(currentPage, pageCount, pageSize) {
var headerBody = {
columns: [
title(incidentTitle, currentPage, logs.length, messages.length),
{ 
text: 'Page ' + currentPage.toString() + ' of ' + pageCount, 
alignment: 'right',
style: 'header'
}
],
margin: [24, 24, 24, 0]
}
return headerBody
},
content: [
date,
linebreak,
address,
linebreak,
{ text: [
{ text: 'Incident Commander:', style: 'header' },
{ text: ' Daniel', style: 'regular'},
]
},
linebreak,
{ 
text: [
{ text: 'Members involved:', style: 'header' },
{text: ' Shawn, Zack, Gabe', style: 'regular'},
]
},
linebreak,
buildLogsBody(logs),
buildMessageHistory(messages)
],
pageBreakBefore: function(currentNode, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage) {
return currentNode.headlineLevel === 1 && followingNodesOnPage.length === 0;
},
styles: {
header: {
fontSize: 16,
bold: true
},
regular: {
fontSize: 16,
bold: false
},
messageSender: {
fontSize: 14,
bold: true
},
message: {
fontSize: 14
},
messageDate: {
fontSize: 14,
color: 'gray'
}
}
}
const printer = new Printer(fontDescriptors);
const pdfDoc = printer.createPdfKitDocument(docDefinition);
var chunks = []
const pdfName = `${teamId}/${incidentId}/report.pdf`;
pdfDoc.on('data', function (chunk) {
chunks.push(chunk);
});
pdfDoc.on('end', function () {
functions.logger.log("PDF on end started")
const result = Buffer.concat(chunks);
// Upload generated file to the Cloud Storage
const fileRef = bucket.file(
pdfName,
{ 
metadata: { 
contentType: 'application/pdf'
} 
}
);

// bucket.upload("report.pdf", { destination: "${teamId}/${incidentId}/report.pdf", public: true})
fileRef.save(result);
fileRef.makePublic().catch(console.error);
// Sending generated file as a response
// res.send(result);
functions.logger.log("File genderated and saved.")
return { "response": result }
});
pdfDoc.on('error', function (err) {
res.status(501).send(err);
throw new functions.https.HttpsError('internal', err);
});
pdfDoc.end();
})

作为快速参考,主要端点方法是exports.generatePdf,最后的pdfDoc.on是应该处理保存的代码,但是代码似乎永远不会触发,因为其中的日志从未被记录,并且文档并不总是被保存。

这是一个函数生命周期问题,你的函数在完成任务之前被杀死,因为你做的最后一个操作是处理事件处理程序,而不是返回一个承诺。有时候成功的原因只是因为你很幸运。一旦一个函数完成,它应该已经完成了它需要做的一切。

所以你需要做的是正确地管道数据从pdfDoc流通过云存储,所有包裹在承诺,云功能可以用来监控进度,而不是杀死你的函数完成之前。

最简单的形式是这样的:

const stream = /* ... */;
const storageStream = bucket
.file(/* path */)
.createWriteStream(/* options */);
return new Promise((resolve, reject) => {
storageStream.once("finish", resolve); // resolve when written
storageStream.once("error", reject);   // reject when either stream errors
stream.once("error", reject);
stream.pipe(storageStream);            // pipe the data
});

注意:谷歌云存储节点SDK与Firebase客户端的云存储SDK不一样!

return new Promise((resolve, reject) => {
const pdfDoc = printer.createPdfKitDocument(docDefinition);
const pdfName = `${teamId}/${incidentId}/report.pdf`;

// Reference to Cloud Storage upload location
const fileRef = bucket.file(pdfName);

const pdfReadStream = pdfDoc;
const storageWriteStream = fileRef.createWriteStream({ 
predefinedAcl: 'publicRead', // saves calling makePublic()
contentType: 'application/pdf'
});

// connect errors from the PDF
pdfReadStream.on('error', (err) => {
console.error("PDF stream error: ", err);
reject(new functions.https.HttpsError('internal', err));
});
// connect errors from Cloud Storage
storageWriteStream.on('error', (err) => {
console.error("Storage stream error: ", err);
reject(new functions.https.HttpsError('internal', err));
});
// connect upload is complete event.
storageWriteStream.on('finish', () => {
functions.logger.log("File generated and saved to Cloud Storage.");
resolve({ "uploaded": true });
});
// pipe data through to Cloud Storage
pdfReadStream.pipe(storageWriteStream);

// finish the document
pdfDoc.end();
});

最新更新