利用 Puppeteer 生成 PDF
需求
项目中有个需求是能够把在线的电子教材导出成 pdf 下载到本地。这里记录下实现方案。
整体思路
前端提供给后端一个能够访问预览 pdf 的 url 地址 ,后端通过 Puppeteer 访问前端提供的 url 地址,得到 pdf Buffer 后,再转为文件对象返回给前端。
具体实现
后端使用 express 搭建。
下载相关包
shell
npm install puppeteer@22.15.0 puppeteer-core@22.15.0 @sparticuz/chromium@123.0.1
编写 Controller
ts
import chromium from "@sparticuz/chromium";
import puppeteerCore from "puppeteer-core";
import puppeteer from "puppeteer";
export default {
async downloadCoco(req, res) {
const params = validate(res, Object.assign({}, req.query, req.params, req.body), schema);
const { bookId, bookName, baseUrl, bookPageConfig, mode = 'edit' } = params; // bookId: 教材 id, baseUrl: baseUrl地址, bookPageConfig: 教材页面配置, mode: 模式(edit: 支持选中文字 |detail: 不支持选中文字)
const { size } = bookPageConfig;
let p = null;
let options = {};
if (isDev) {
p = puppeteer;
options = {
headless: true
}
} else {
p = puppeteerCore;
options = {
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(), // 生产环境使用我们自定义的chromium路径
headless: chromium.headless,
}
}
const browser = await p.launch(options);
const page = await browser.newPage();
// 添加 token
await page.setExtraHTTPHeaders({
'Authorization': req.headers.authorization
});
await page.goto('前端提供的 url 地址', {waitUntil: 'networkidle0'});
const scale = 1;
const pdf = await page.pdf({
printBackground: true,
width: size.width * scale + 'px',
height: size.height * scale + 'px',
scale: scale,
});
await browser.close();
if (pdf) {
// 将 PDF Buffer 转换为文件对象
const pdfFile = Buffer.from(pdf);
const [err, data] = await uploadFileToCos(pdfFile, {
fileName: bookName,
ContentType: 'application/pdf',
'Content-Disposition': `attachment; filename=${encodeURIComponent(bookName)}.pdf`
}, config.cos.SOURCE_PDF_TEMP_PATH);
if (err) {
return sendResponse(res, {
success: false,
error_msg: '上传 PDF 文件到 COS 失败',
err,
});
} else {
return sendResponse(res, {
data: {
url: data
},
error_msg: '下载成功'
});
}
} else {
return sendResponse(res, {
success: false,
error_msg: '生成 PDF 文件失败'
});
}
},
}