Использование PDFKit для хранения PDF в S3 на лету

Я пытаюсь создать pdf-файл с некоторыми изображениями (в формате datauri) на nodejs, и pdf-файл хранится в моем S3. Ожидается, что возврат функции предоставит URL-адрес s3 файла.

Здесь я использую parse-server для сервера и node-canvas для создания холста изображений, а затем PDFKit для создания pdf из элементов холста. (jsPdf не сработал). Теперь я хочу, чтобы этот PDF-файл был отправлен на мой s3 с помощью AWS-SDK и, наконец, вернул URL-адрес файла. Ниже приведен мой код, который работает до генерации холста. Я не знаю, создается ли pdf или нет в первую очередь, даже до отправки на s3. И о! Все это работает на героку.

Parse.Cloud.define('getBulkMeta',async (req)=>{
    const PDFDocument = require('pdfkit'),
        {Canvas,loadImage} = require('canvas');

        try {       
            let baseImg = await loadImage('data:image/png;base64,'+req.params.labels[0]);
            let labels = req.params.labels,
                allCanvas = [],
                rowH = baseImg.naturalHeight,
                rowW = baseImg.naturalWidth,
                perpage = req.params.size[1],
                pages = Math.ceil(labels.length/perpage),
                imgInd = 0,
                g = 10;
                size = req.params.size[0];

            for(var p=0;p<pages;p++){
                let canvas = new Canvas(rowW*((size=='A4')?2:1),rowH*((size=='A4')?2:1)),
                    ctx = canvas.getContext("2d");

                ctx.beginPath();
                ctx.rect(0,0,canvas.width,canvas.height)
                ctx.fillStyle = "#fff";
                ctx.fill();

                if(perpage == 1){
                    let img = await loadImage('data:image/png;base64,'+labels[imgInd++]);
                    ctx.drawImage(img,g,g,rowW-(2*g),rowH-(2*g));
                } else {
                    var thisImgInd = 0;
                    for (var r=0;r<2;r++){
                        for(var c=0;c<2;c++){
                            let img = await loadImage('data:image/png;base64,'+labels[imgInd++]);
                            ctx.drawImage(img,g+(c*(rowW-g/2)),g+(r*(rowH-g/2)),rowW-(1.5*g),rowH-(1.5*g));
                            thisImgInd++
                            if(thisImgInd>=perpage||imgInd>=labels.length){break;}
                        }
                        if(thisImgInd>=perpage||imgInd>=labels.length){break;}
                    }
                }
                allCanvas.push(canvas)
            }
        var thisPDF = new PDFDocument({layout: 'landscape',size:size});
        var bcoded;
        thisPDF.pipe(()=>{bcoded = new Buffer.from(thisPDF).toString('base64')});
        allCanvas.forEach((c,i)=>{
            if(i){thisPDF.addPage();}
            thisPDF.image(c.toDataURL(),0,0,thisPDF.page.width,thisPDF.page.width);
        })
        thisPDF.end();
        const S3_BUCKET = process.env.S3_BUCKET;
        aws.config.region = process.env.AWS_REGION;
        aws.config.signatureVersion  = 'v4';

        let s3 = new aws.S3();
        let fileName = req.params.name;
        let s3Params = {
            Bucket: S3_BUCKET,
            Body: bcoded,
            Key: fileName,
            ContentType : 'application/pdf',
            ACL: 'public-read'
        };
        s3.putObject(s3Params, (err, data) => {
            if(err){
                console.log('\n\n\n\n\n\n\n'+err+'\n\n\n\n\n\n\n');
                throw 'Error: '+ (err);
            }
            let returnData = {
                signedRequest: data,
                url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}`
            };
            return (returnData);
        })
    } catch (e) {throw e;}
})

Обновлять. У меня есть возможность сохранить файл PDF в s3 с помощью следующего кода:

    Parse.Cloud.define('getBulkMeta',async (req)=>{
    const PDFDocument = require('pdfkit'),
        {Canvas,loadImage} = require('canvas');

        try {       
            let baseImg = await loadImage('data:image/png;base64,'+req.params.labels[0]);
            let labels = req.params.labels,
                allCanvas = [],
                rowH = baseImg.naturalHeight,
                rowW = baseImg.naturalWidth,
                perpage = req.params.size[1],
                pages = Math.ceil(labels.length/perpage),
                imgInd = 0,
                g = 10;
                size = req.params.size[0];

            for(var p=0;p<pages;p++){
                let canvas = new Canvas(),
                    ctx = canvas.getContext("2d");

                canvas.height = rowH*((size=='A4')?2:1);
                canvas.width = rowW*((size=='A4')?2:1);

                ctx.beginPath();
                ctx.rect(0,0,canvas.width,canvas.height)
                ctx.fillStyle = "#fff";
                ctx.fill();

                if(perpage == 1){
                    let img = await loadImage('data:image/png;base64,'+labels[imgInd++]);
                    ctx.drawImage(img,g,g,rowW-(2*g),rowH-(2*g));
                } else {
                    var thisImgInd = 0;
                    for (var r=0;r<2;r++){
                        for(var c=0;c<2;c++){
                            let img = await loadImage('data:image/png;base64,'+labels[imgInd++]);
                            ctx.drawImage(img,g+(c*(rowW-g/2)),g+(r*(rowH-g/2)),rowW-(1.5*g),rowH-(1.5*g));
                            thisImgInd++
                            if(thisImgInd>=perpage||imgInd>=labels.length){break;}
                        }
                        if(thisImgInd>=perpage||imgInd>=labels.length){break;}
                    }
                }
                allCanvas.push(canvas)
            }
        var thisPDF = new PDFDocument({layout: 'landscape',size:size});
        let buffers = [],pdfData,returnData='Hi';
        thisPDF.on('data', buffers.push.bind(buffers));
        thisPDF.on('end',() => {
            pdfData = Buffer.concat(buffers);
            const S3_BUCKET = process.env.S3_BUCKET;
            aws.config.region = process.env.AWS_REGION;
            aws.config.signatureVersion  = 'v4';

            let s3 = new aws.S3();
            let fileName = req.params.name;
            let s3Params = {
                Bucket: S3_BUCKET,
                Body: pdfData,
                Key: (+new Date())+'-'+fileName,
                ContentType : 'application/pdf',
                ACL: 'public-read'
            };
            s3.putObject(s3Params,(err, data) => {
                delete pdfData,thisPDF;
                pdfData = null;thisPDF = null;
                if(err){ throw 'Error: '+ (err); }
                returnData = { signedRequest: data, url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}` };
            })
        })
        allCanvas.forEach((c,i)=>{
            if(i){thisPDF.addPage();}
            thisPDF.image(c.toDataURL(),0,0,{fit:[thisPDF.page.width,thisPDF.page.height]});
        })
        thisPDF.end();
        return returnData;
    } catch (e) {throw e;}
})

Однако returnData всегда выдает «Привет» в качестве вывода, и также кажется, что функция не закрывается - Heroku каждый раз выдает ошибку превышения памяти.


person HymnZzy    schedule 22.08.2019    source источник
comment
Кроме того, может ли кто-нибудь помочь мне с ошибкой превышения памяти Heroku, пожалуйста? Что я здесь делаю неправильно?   -  person HymnZzy    schedule 22.08.2019


Ответы (1)


Поскольку вы используете асинхронные функции для создания PDF-файла и отправки его в S3, ваша облачная функция возвращается до того, как эти операции будут фактически завершены. Вот почему у вас всегда есть Hi в вашей переменной returnData. Вам нужно создать обещание и дождаться завершения этого обещания в конце этих двух операций. Это должно быть что-то вроде этого:

await (new Promise((resolve, reject) => {
  var thisPDF = new PDFDocument({layout: 'landscape',size:size});
  let buffers = [];
  thisPDF.on('data', buffers.push.bind(buffers));
  thisPDF.on('end',() => {
    pdfData = Buffer.concat(buffers);
    const S3_BUCKET = process.env.S3_BUCKET;
    aws.config.region = process.env.AWS_REGION;
    aws.config.signatureVersion  = 'v4';

    let s3 = new aws.S3();
    let fileName = req.params.name;
    let s3Params = {
      Bucket: S3_BUCKET,
      Body: pdfData,
      Key: (+new Date())+'-'+fileName,
      ContentType : 'application/pdf',
      ACL: 'public-read'
    };
    s3.putObject(s3Params,(err, data) => {
      delete pdfData,thisPDF;
      pdfData = null;thisPDF = null;
      if(err){ reject(err); }
      returnData = { signedRequest: data, url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}` };
      resolve();
    })
  })
});

Кстати, вместо использования AWS SDK вы можете использовать адаптер Parse S3 и сохранить PDF как обычный файл Parse.

person Davi Macêdo    schedule 22.08.2019
comment
Время истекло с этим кодом. Добавляя thisPDF.on('end',() внутрь функции await, не исключаем ли мы это из нормального хода вещей? - person HymnZzy; 22.08.2019
comment
Вы также можете добавить другие строки внутри обратного вызова, но я не думаю, что это проблема (только что отредактировал ответ). Попробуйте добавить несколько вызовов console.log, чтобы узнать, вызывалась ли функция resolve в какой-то момент времени. - person Davi Macêdo; 22.08.2019
comment
Я поместил всю функцию в обещание, и она работает. Проблема была с ненужными петлями здесь. Кроме того, я не смог заставить S3 работать с адаптером синтаксического анализа. Есть ли место, где это лучше задокументировано в документах синтаксического анализа? - person HymnZzy; 24.08.2019