如何使用 jsreport 和 jsreport Chrome Pdf 渲染 1k pdf 文件?

How to render 1k pdf files using jsreport and jsreport Chrome Pdf?

我正在尝试使用 Express.js 和端点生成 1k 报告,我在 JSON 中传递一个数组,API 遍历它并在forEach 循环,然后使用每个对象 抓取 一个门户,获取响应,并创建一个 PDF 文件...

这种方法伪工作,但我很确定存在一些并发问题...因为,如果我在 JSON 数组中传递 2 个项目,API 可以创建 2 PDF 文件没有问题,但如果我传递 300,API 会随机创建 50...或 60 或 120。

这是我的 jsreport 配置

const jsReportConfig = {
  extensions: {
    "chrome-pdf": {
      launchOptions: {
        timeout: 10000,
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
      },
    },
  },
  tempDirectory: path.resolve(__dirname, './../../temporal/pdfs'),
  templatingEngines: {
    numberOfWorkers: 4,
    timeout: 180000,
    strategy: 'http-server',
  },
};

我这样设置 jsreport 实例

jsreport.use(jsReportChrome());
jsreport.use(jsReportHandlebars());
jsreport.init()

而且,这就是我呈现报告的方式,checkInvoiceStatus 函数用作 HTTP 调用,returns 一个 HTML 响应被注入 Handlebars 模板。

const renderReports = (reporter, invoices) => new Promise(async (resolve, reject) => {
    try {
      const templateContent = await readFile(
        path.resolve(__dirname, './../templates/hello-world.hbs'),
        'utf-8',
      );
      invoices.forEach(async (invoice) => {
        try {
          const response = await checkInvoiceStatus(invoice.re, invoice.rr, invoice.id)
          const $ = await cheerio.load(response);
          const reporterResponse = await reporter.render({
            template: {
              content: templateContent,
              engine: 'handlebars',
              recipe: 'chrome-pdf',
              name: 'PDF Validation',
              chrome: {
                displayHeaderFooter: true,
                footerTemplate: '<table width=\'100%\' style="font-size: 12px;"><tr><td width=\'33.33%\'>{#pageNum} de {#numPages}</td><td width=\'33.33%\' align=\'center\'></td><td width=\'33.33%\' align=\'right\'></td></tr></table>',
              },
            },
            data: {
              taxpayerId: 'CAC070508MY2',
              captcha: $('#ctl00_MainContent_ImgCaptcha').attr('src'),
              bodyContent: $('#ctl00_MainContent_PnlResultados').html(),
            },
          });
          reporterResponse.result.pipe(fs.createWriteStream(`./temporal/validatedPdfs/${invoice.id}.pdf`));
        } catch (err) {
          console.error(err);
          reject(new Error(JSON.stringify({
            code: 'PORTAL-PDFx001',
            message: 'The server could not retrieve the PDF from the portal',
          })));
        }
      });
      resolve();
    } catch (err) {
      console.error(err);
      reject(new Error(JSON.stringify({
        code: 'PORTAL-PDFx001',
        message: 'The server could not retrieve the PDF from the portal',
      })));
    }
  });

不知道为什么,这个函数在500ms后终止,但是文件是在1分钟后创建的...

app.post('/pdf-report', async (req, res, next) => {
  const { invoices } = req.body;
  repository.renderReports(reporter, invoices)
    .then(() => res.status(200).send('Ok'))
    .catch((err) => {
      res.status(500).send(err);
    });
});

更新

除了@hurricane 提供的代码外,我还必须将 jsReport 配置更改为此

const jsReportConfig = {
  chrome: {
    timeout: 180000,
    strategy: 'chrome-pool',
    numberOfWorkers: 4
  },
  extensions: {
    'chrome-pdf': {
      launchOptions: {
        timeout: 180000,
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
        ignoreDefaultArgs: ['--disable-extensions'],
      },
    },
  },
  tempDirectory: path.resolve(__dirname, './../../temporal/pdfs'),
  templatingEngines: {
    numberOfWorkers: 4,
    timeout: 180000,
    strategy: 'http-server',
  },
};

我认为在您的结构中,使用 writeFileSync 函数而不是使用 writeStream 可以轻松解决您的问题。但这并不意味着它是最好的方法。因为您必须等待每个渲染和每个写入文件进程启动另一个。所以我建议使用 Promise.all,这样您就可以同时 运行 您的长流程。这意味着您将只等待最长的过程。

快速取胜-缓慢过程

reporterResponse.result.pipe 

将此行更改为

   fs.createFileSync(`./temporal/validatedPdfs/${invoice.id}.pdf`);

更好的方法 - 快速过程

const renderReports = (reporter, invoices) => new Promise(async (resolve, reject) => {
  try {
    const templateContent = await readFile(
      path.resolve(__dirname, './../templates/hello-world.hbs'),
      'utf-8',
    );
    const renderPromises = invoices.map((invoice) => {
      const response = await checkInvoiceStatus(invoice.re, invoice.rr, invoice.id)
      const $ = await cheerio.load(response);
      return await reporter.render({
        template: {
          content: templateContent,
          engine: 'handlebars',
          recipe: 'chrome-pdf',
          name: 'PDF Validation',
          chrome: {
            displayHeaderFooter: true,
            footerTemplate: '<table width=\'100%\' style="font-size: 12px;"><tr><td width=\'33.33%\'>{#pageNum} de {#numPages}</td><td width=\'33.33%\' align=\'center\'></td><td width=\'33.33%\' align=\'right\'></td></tr></table>',
          },
        },
        data: {
          taxpayerId: 'CAC070508MY2',
          captcha: $('#ctl00_MainContent_ImgCaptcha').attr('src'),
          bodyContent: $('#ctl00_MainContent_PnlResultados').html(),
        },
      });
    });
    const renderResults = await Promise.all(renderPromises);
    const filewritePromises = results.map(renderedResult => await fs.writeFile(`./temporal/validatedPdfs/${invoice.id}.pdf`, renderedResult.content));
    const writeResults = await Promise.all(filewritePromises);
    resolve(writeResults);
  } catch (err) {
    console.error(err);
    reject(new Error(JSON.stringify({
      code: 'PORTAL-PDFx001',
      message: 'The server could not retrieve the PDF from the portal',
    })));
  }
});