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

Express.js中PDFkit缓冲流经pdf2pic生成的PNG图片损坏问题

问题:Express生成的PNG图片损坏无法正常显示

我用Express.js搭建了HTTP服务器,接收包含文本的POST请求:通过PDFkit生成对应内容的PDF,不保存到本地而是用流缓冲数据,之后将缓冲数据传入pdf2pic转换为PNG返回给客户端。但客户端保存后的图片显示损坏(文件体积过小),调试后未找到根源,怀疑是缓冲数据不完整或提前发送。

前端axios请求代码(使用FileSaver的saveAs保存PNG)

const requestData = {
  text: gift.value.text,
};

const config = {
  headers: {
    'Content-Type': 'application/json'
  },
  responseType: 'arraybuffer'
};

axios.post('/png', JSON.stringify(requestData), config)
  .then(response => {
     const blob = new Blob([await res.data], { type: 'image/png' })
     await saveAs(blob, fileName)
  })
  .catch(error => {
    // Handle errors
    console.error(error);
  });

后端Express处理POST请求代码

import type { Request, Response } from 'express'
import express from 'express'
import bodyParser from 'body-parser'
import * as fs from 'fs'
import * as stream from 'stream'
import PDFDocument from 'pdfkit'
import { fromBuffer } from 'pdf2pic'

const router = express.Router()

router.post('/png', bodyParser.json(), async (req: Request, res: Response) => {
  const { text } = req.body

  if (!text) {
    return res.status(400).json({ error: 'Text is required in the request body' })
  }

  // 创建PDF文档
  const pdfDoc = new PDFDocument()

  // 创建用于存储PDF内容的可写流
  const pdfStream = new stream.Writable()
  const pdfBuffer: Buffer[] = []

  pdfStream._write = (chunk, encoding, callback) => {
    pdfBuffer.push(chunk)
    callback()
  }

  pdfStream.on('finish', async () => {
    const pdf2pic = fromBuffer(Buffer.concat(pdfBuffer), {
      density: 100,
      saveFilename: 'output',
      savePath: './',
      format: 'png',
      width: 600,
      height: 800
    })

    const result = await pdf2pic(1) // 转换第一页

    console.log(result)
    // 将PNG图片返回给客户端
    res.contentType('image/png')
    res.send(result)

    // 清理临时文件
    fs.unlinkSync('./')
  })

  pdfDoc.pipe(pdfStream)
  pdfDoc.text(text)
  pdfDoc.end()
})

export { router as services }

损坏的图片

损坏的图片


更新1:测试PDF缓冲流可用性

按照建议直接返回PDF缓冲流,确认可用,测试代码如下:

router.post('/png', bodyParser.json(), async (req: Request, res: Response) => {
  const { text } = req.body

  if (!text) {
    return res.status(400).json({ error: 'Text is required in the request body' })
  }

  // 创建PDF文档
  const pdfDoc = new PDFDocument()

  // 创建用于存储PDF内容的可写流
  const pdfStream = new stream.Writable()
  const pdfBuffer: Buffer[] = []

  pdfStream._write = (chunk, encoding, callback) => {
    pdfBuffer.push(chunk)
    callback()
  }

  pdfStream.on('finish', async () => {
    // 设置内容类型为PDF
    res.contentType('application/pdf')

    // 将PDF缓冲直接发送到响应
    res.send(Buffer.concat(pdfBuffer))
  })

  pdfDoc.pipe(pdfStream)
  pdfDoc.text(text)
  pdfDoc.end()
})

错误分析与修复方案

1. 前端异步逻辑错误

前端then回调未声明为异步函数,且错误使用res.data(应为response.data),导致无法正确获取二进制数据。修改后代码:

axios.post('/png', JSON.stringify(requestData), config)
  .then(async (response) => {
     const blob = new Blob([response.data], { type: 'image/png' })
     await saveAs(blob, fileName)
  })

2. 后端pdf2pic返回值处理错误

pdf2pic(1)返回的是包含图片路径和缓冲区的对象,直接res.send(result)会把对象序列化为JSON发送,而非图片二进制数据,需改为发送result.buffer:

const result = await pdf2pic(1)
res.contentType('image/png')
res.send(result.buffer) // 发送图片二进制缓冲区

3. 无效的文件清理操作

fs.unlinkSync('./')尝试删除目录会报错,因为unlinkSync仅支持删除文件。若不需要保存本地图片,可修改pdf2pic配置禁用本地存储;若需保存,应删除生成的具体文件:

// 禁用本地存储的配置
const pdf2pic = fromBuffer(Buffer.concat(pdfBuffer), {
  density: 100,
  format: 'png',
  width: 600,
  height: 800,
  savePath: false // 不保存到本地
})

// 若需保存后删除
fs.unlinkSync(result.path)

4. 自定义可写流的简化优化

使用stream.PassThrough替代自定义Writable Stream,更简洁可靠:

const pdfBuffer: Buffer[] = []
const passThrough = new stream.PassThrough()
passThrough.on('data', chunk => pdfBuffer.push(chunk))
passThrough.on('end', async () => {
  // 此处执行PDF转PNG及响应逻辑
})
pdfDoc.pipe(passThrough)
pdfDoc.text(text)
pdfDoc.end()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:15:19