You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js/Loopback生成PDF保存至路径后,如何实现自动下载?

Hey Ahmed, let's sort out how to trigger an automatic download right after your PDF finishes generating in LoopBack! Here's a step-by-step fix with modified code:

Step 1: Update the PDF Generator to Return a Promise

First, we need to make your pdfGenerator function asynchronous so we can wait for the PDF to fully write to disk before triggering the download. Wrap it in a Promise:

// generatorPdf.js
module.exports = {
  pdfGenerator: function(data, pathfile) {
    return new Promise((resolve, reject) => {
      var fonts = {
        Roboto: {
          normal: 'server/pdfgenerator/fonts/Roboto-Regular.ttf',
          bold: 'server/pdfgenerator/fonts/Roboto-Medium.ttf',
          italics: 'server/pdfgenerator/fonts/Roboto-Italic.ttf',
          bolditalics: 'server/pdfgenerator/fonts/Roboto-MediumItalic.ttf'
        }
      };
      var datePaiements = '';
      var dateFinPaiements = '';
      
      if (data.abonnement[0].datePaiement != null)
        datePaiements = new Date(data.abonnement[0].datePaiement.toString());
      
      if (datePaiements !== '') {
        dateFinPaiements = ('0' + datePaiements.getDate()).slice(-2) + '/' + ('0' + (datePaiements.getMonth() + 1)).slice(-2) + '/' + (datePaiements.getFullYear() + 1);
        datePaiements = ('0' + datePaiements.getDate()).slice(-2) + '/' + ('0' + (datePaiements.getMonth() + 1)).slice(-2) + '/' + datePaiements.getFullYear();
      }
      
      var dateFacture = new Date(data.abonnement[0].timestampCreation.toString());
      dateFacture = ('0' + dateFacture.getDate()).slice(-2) + '/' + ('0' + (dateFacture.getMonth() + 1)).slice(-2) + '/' + dateFacture.getFullYear();
      
      var PdfPrinter = require('pdfmake/src/printer');
      var printer = new PdfPrinter(fonts);
      var fs = require('fs');
      
      var dd = {
        content: [/* Your existing content here */],
        footer: {/* Your existing footer here */}
      };
      
      try {
        var pdfDoc = printer.createPdfKitDocument(dd);
        
        // Handle existing file deletion before writing
        const writePdfStream = () => {
          pdfDoc.pipe(fs.createWriteStream(pathfile))
            .on('finish', () => resolve(pathfile)) // Resolve when PDF is ready
            .on('error', (err) => reject(err));
          pdfDoc.end(); // Don't forget to end the PDF stream!
        };
        
        if (fs.existsSync(pathfile)) {
          fs.unlink(pathfile, (err) => {
            if (err) reject(err);
            else writePdfStream();
          });
        } else {
          writePdfStream();
        }
      } catch (e) {
        console.error(e);
        reject(e);
      }
    });
  }
};

Step 2: Modify the LoopBack Remote Method to Trigger Download

Now, update your remote method to wait for the PDF to generate, then use Express's res.download() to prompt the browser to download the file:

// Your LoopBack remote method
cm_abonnements.getAbonnementById = async (options, req, res) => {
  const token = options && options.accessToken;
  const userId = token && token.userId;
  
  try {
    if (!userId) {
      return res.status(401).send('Unauthorized access');
    }
    
    let dataComedien = await app.models.cm_comediens.getComedienByUser(userId);
    let argAbn = {};
    
    // Parse form data with promise wrapper
    await new Promise((resolve, reject) => {
      const form = new formidable.IncomingForm();
      form.parse(req, (err, fields) => {
        if (err) reject(err);
        argAbn.idAbonnement = fields.key;
        resolve();
      });
    });
    
    let dataFac = await cm_abonnements.find({
      where: {
        and: [
          { idAbonnement: argAbn.idAbonnement },
          { idComedien: dataComedien.idComedien }
        ]
      }
    });
    
    if (!dataFac.length) {
      return res.status(404).send('Invoice not found');
    }
    
    // Prepare PDF file details
    var data = { abonnement: dataFac };
    var str_date = new Date(dataFac[0].timestampCreation.toString());
    var nameFile = `Fac_${dataFac[0].idFacture}_${str_date.getFullYear()}-${('0' + (str_date.getMonth() + 1)).slice(-2)}-${('0' + str_date.getDate()).slice(-2)}.pdf`;
    var path = `public/upload/Comediens/${dataComedien.idComedien}/factures/${nameFile}`;
    
    // Wait for PDF to finish generating
    await createPdf.pdfGenerator(data, path);
    
    // Trigger browser download
    res.download(path, nameFile, (err) => {
      if (err) {
        console.error(err);
        res.status(500).send('Failed to initiate PDF download');
      }
      // Optional: Delete the file after download if you don't need to keep it
      // fs.unlink(path, (unlinkErr) => { if (unlinkErr) console.error(unlinkErr); });
    });
  } catch (e) {
    console.error(e);
    res.status(500).send('Error generating invoice PDF');
  }
};

cm_abonnements.remoteMethod(
  'getAbonnementById', {
    http: { verb: 'POST' },
    description: 'Retrieve invoice details and trigger PDF download',
    accepts: [
      { arg: "options", type: "object", http: "optionsFromRequest" },
      { arg: 'req', type: 'object', http: { source: 'req' }},
      { arg: 'res', type: 'object', http: { source: 'res' }}
    ]
    // Remove the `returns` config since we're sending a file, not JSON
  }
);

Key Changes Explained:

  • Promise Wrapper for PDF Generation: Ensures we wait until the PDF is fully written to disk before trying to download it.
  • res.download(): Sets the correct HTTP headers (Content-Disposition) to tell the browser to download the file instead of displaying it.
  • Error Handling: Added proper error responses for unauthorized access, missing invoices, and generation/download failures.
  • Stream Cleanup: Made sure to call pdfDoc.end() to finalize the PDF stream (this was missing in your original code!).

内容的提问来源于stack exchange,提问作者Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:21:12