使用Puppeteer和adm-zip批量生成PDF时出现Node加密哈希错误
问题分析
你遇到的node:internal/crypto/hash:113错误,本质是adm-zip的addFile方法接收到了无效的内容数据(比如undefined),触发了底层哈希计算的异常。结合你的场景:仅特定视图批量生成时出错、单个生成正常,问题出在循环中某个特定ID对应的PDF生成失败,但代码仍继续执行,将未定义的pdf传入了zip.addFile。
大概率是以下某类原因:
- 特定视图的页面加载未完成(比如
networkidle0等待条件未满足,页面资源加载超时) - 特定视图中不存在
gerar-pdf按钮,导致page.evaluate抛出异常,后续page.pdf未执行 - 特定页面的内容渲染异常,导致
page.pdf生成失败
解决方案
1. 严格校验PDF数据,避免无效内容传入压缩包
在调用zip.addFile前,先检查pdf是否有效,无效则跳过该文件:
const pdf = await page.pdf({ format: 'A4', printBackground: true, pageRanges: '1' }); // 校验pdf是否为有效Buffer if (pdf && Buffer.isBuffer(pdf)) { zip.addFile(`${id}.pdf`, pdf, `PDF para o ID ${id}`); } else { console.warn(`跳过无效PDF: ID ${id}`); }
2. 优化页面加载与错误处理
- 给页面加载添加超时时间,避免无限等待:
await page.goto(url, {waitUntil: 'networkidle0', timeout: 30000}); // 30秒超时
- 处理
gerar-pdf按钮不存在的情况,避免page.evaluate抛出异常:
await page.evaluate(() => { const button = document.getElementById('gerar-pdf'); if (button) button.remove(); // 先判断按钮存在再移除 });
3. 隔离页面上下文,避免跨页面污染
批量生成时复用单个页面可能导致上下文残留,尝试为每个ID创建新页面:
// 移除全局的page变量,在循环内创建新页面 for (let i = 0; i < idsArray.length; i++) { const id = idsArray[i]; const url = `http://${IP}:${port}/${database}/${tipo}/${id}`; console.log(url); try { const page = await browser.newPage(); // 每个ID创建新页面 await page.goto(url, {waitUntil: 'networkidle0', timeout: 30000}); await page.evaluate(() => { const button = document.getElementById('gerar-pdf'); if (button) button.remove(); }); const pdf = await page.pdf({ format: 'A4', printBackground: true, pageRanges: '1' }); if (pdf && Buffer.isBuffer(pdf)) { zip.addFile(`${id}.pdf`, pdf, `PDF para o ID ${id}`); } await page.close(); // 及时关闭页面释放资源 } catch (err) { console.error(`处理ID ${id}时出错:`, err.stack); } }
4. 排查特定视图的异常
针对触发错误的特定ID,单独访问对应的URL,检查:
- 页面是否能正常加载,无控制台报错
gerar-pdf按钮是否存在- 页面渲染是否完整(比如是否有动态加载的内容未完成)
修改后的完整代码示例
router.get('/:database/:tipo/:ids/:limite?.zip', async (req, res) => { const { database, tipo, ids, limite } = req.params; let idsArray; if (ids === "ids") { const pool = new Pool({ user: process.env.POSTGRES_USER, host: process.env.POSTGRES_HOST, database: database, password: process.env.POSTGRES_PASSWORD, port: process.env.POSTGRES_PORT, }); let query = `SELECT id FROM controle_interno.${tipo} ORDER BY id ASC`; if (limite) { query += ` LIMIT ${limite}`; } const result = await pool.query(query); idsArray = result.rows.map(row => row.id); } else { idsArray = ids.split(","); } const zip = new AdmZip(); const browser = await puppeteer.launch({ args: ['--disable-gpu', '--disable-software-rasterizer', '--disable-dev-shm-usage', '--disable-setuid-sandbox', '--no-sandbox'] }); for (let i = 0; i < idsArray.length; i++) { const id = idsArray[i]; const url = `http://${IP}:${port}/${database}/${tipo}/${id}`; console.log(url); try { const page = await browser.newPage(); await page.goto(url, {waitUntil: 'networkidle0', timeout: 30000}); await page.evaluate(() => { const button = document.getElementById('gerar-pdf'); if (button) button.remove(); }); const pdf = await page.pdf({ format: 'A4', printBackground: true, pageRanges: '1' }); if (pdf && Buffer.isBuffer(pdf)) { zip.addFile(`${id}.pdf`, pdf, `PDF para o ID ${id}`); } else { console.warn(`ID ${id}未生成有效PDF`); } await page.close(); } catch (err) { console.error(`处理ID ${id}出错:`, err.stack); } } await browser.close(); const zipBuffer = zip.toBuffer(); res.set({ 'Content-Type': 'application/zip', 'Content-Disposition': `attachment; filename=${tipo}.zip`, 'Content-Length': zipBuffer.length }); res.send(zipBuffer); }); module.exports = router;
内容的提问来源于stack exchange,提问作者lucasmsqt
相关产品推荐
相关产品推荐

