Javascript ExpressJS和PDFKit-在内存中生成PDF并发送到客户端下载

Javascript ExpressJS和PDFKit-在内存中生成PDF并发送到客户端下载,javascript,node.js,express,node-pdfkit,Javascript,Node.js,Express,Node Pdfkit,在我的api路由器中,有一个名为generatePDF的函数,其目的是使用PDFKit模块在内存中生成PDF文件并发送到客户端下载,而不是仅显示 在api.js中: var express = require('express'); var router = express.Router(); const PDFDocument = require('pdfkit'); router.get('/generatePDF', async function(req, res, next) {

在我的
api
路由器中,有一个名为
generatePDF
的函数,其目的是使用PDFKit模块在内存中生成PDF文件并发送到客户端下载,而不是仅显示

api.js
中:

var express = require('express');
var router = express.Router();

const PDFDocument = require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
    var myDoc = new PDFDocument({bufferPages: true});
    myDoc.pipe(res);
    myDoc.font('Times-Roman')
         .fontSize(12)
         .text(`this is a test text`);
    myDoc.end();
    res.writeHead(200, {
        'Content-Type': 'application/pdf',
        'Content-disposition': 'attachment;filename=test.pdf',
        'Content-Length': 1111
    });
    res.send( myDoc.toString('base64'));
});

module.exports = router;
这是行不通的。错误消息为
(节点:11444)未经处理的PromisejectionWarning:错误[ERR\u HTTP\u HEADERS\u SENT]:发送到客户端后无法设置头

我如何着手解决问题并使其正常工作

另外,一个相关的问题是,我如何将PDF生成的业务逻辑与路由器分离,并将它们链接起来?

完整的解决方案

var express = require('express');
var router = express.Router();

const PDFDocument =  require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
var myDoc = new PDFDocument({bufferPages: true});

let buffers = [];
myDoc.on('data', buffers.push.bind(buffers));
myDoc.on('end', () => {

    let pdfData = Buffer.concat(buffers);
    res.writeHead(200, {
    'Content-Length': Buffer.byteLength(pdfData),
    'Content-Type': 'application/pdf',
    'Content-disposition': 'attachment;filename=test.pdf',})
    .end(pdfData);

});

myDoc.font('Times-Roman')
     .fontSize(12)
     .text(`this is a test text`);
myDoc.end();
});

module.exports = router;

您可以像这样使用blob流

参考:

将所有pdf数据传输到blob,然后将其写入文件或url。 或者你可以将pdf直接存储到firebase存储等云存储中,并将下载链接发送到客户端

如果您想动态生成pdf,那么您还可以在节点中试用html pdf库,它允许您从html模板创建pdf并在其中添加动态数据。而且它比pdfkit更可靠 也请参考此链接
首先,我建议为PDF工具包创建一个服务。然后一个控制器到达你想要的路线

我使用了
get stream
来简化这个过程

它还回答了您的问题,并给出了公认的答案:

如何将PDF生成的业务逻辑与 把他们连起来

这是我的专业解决方案:

从“pdfkit”导入PDFDocument;
从“get stream”导入getStream;
从“fs”导入fs;
导出默认类PdfKitService{
/**
*生成信件的PDF
*
*@返回{Buffer}
*/
异步generatePdf(){
试一试{
const doc=新的PDFDocument();
doc.fontSize(25).text('一些嵌入字体的文本!',100100);
if(process.env.NODE_env===‘development’){
doc.pipe(fs.createWriteStream(`${uu dirname}/./file.pdf`);
}
doc.end();
const pdfStream=等待getStream.buffer(doc);
返回pdfStream;
}捕获(错误){
返回null;
}
}
}
然后是控制器的方法:

(…)
异步显示(req、res){
const pdfKitService=新的pdfKitService();
const pdfStream=await pdfKitService.generatePdf();
物件
.文书主任(200{
“内容长度”:缓冲区。字节长度(pdfStream),
“内容类型”:“应用程序/pdf”,
“内容处置”:“附件;文件名=test.pdf”,
})
.完(pdfStream);
}
最后是路线:

routes.get('/pdf', FileController.show);

对于那些不想将RAM浪费在缓存PDF上并立即向客户端发送块的用户:

const filename=`Receipt_${invoice.number}.pdf`;
const doc=新的PDFDocument({bufferPages:true});
const stream=res.writeHead(200{
“内容类型”:“应用程序/pdf”,
“内容处置”:“附件;文件名=${filename}.pdf`,
});
doc.on('data',(chunk)=>stream.write(chunk));
doc.on('end',()=>stream.end());
doc.font('Times-Roman')
.fontSize(12)
.text(`这是测试文本');
doc.end();

谢谢。主体引用的是什么?它是
mydoc
?mydoc看起来像一个对象,因此请了解如何从中获取实际数据。添加了完整的解决方案感谢您的回答。这管用!唯一的问题是我仍然对将
end
事件绑定到myDoc感到困惑。PDFKit文档中没有提到这一点,文档中确实提到使用doc.pipe(res)将pdf文档写入http响应。但是你的回答中没有用到。另一件事是在我原来问题的末尾<代码>如何将PDF生成的业务逻辑与路由器分离并将它们链接起来?
我想从req.query中获取一个参数,并将其传递给PDF生成逻辑。值得单独问一个问题吗?谢谢!最初的问题是针对节点服务器端,而不是浏览器端。我将看一看html pdf库。是的,解决方案是浏览器端,但一旦创建blob,您就可以在文件系统中创建写流,直接写入文件并存储在本地存储中,或者直接将流写入云存储。
routes.get('/pdf', FileController.show);