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

MERN项目中Axios下载Google API导出的PDF文件始终损坏如何解决?

问题核心原因

  • 异步时序错误:后端Google Drive文件下载到本地的逻辑未完全执行完成,就提前向客户端返回了未写完的文件,是PDF偶尔可用、大部分时候损坏的核心诱因
  • 路径配置错误:本地存储路径仅指定了文件夹目录,未指定具体文件名,PDF未正确写入到预期的LOR.pdf路径
  • 并发冲突:所有用户的下载请求都写入同一个LOR.pdf文件,多请求并发时会出现文件内容互相覆盖的问题
  • 前端语法错误:Blob构造函数的参数括号闭合错误,导致PDF的MIME类型未正确配置

修复方案

1. 前端代码修正

仅需要修正Blob构造的语法错误即可:

export const downloadDocument = (googleDocId) => (dispatch) => {
  dispatch({
    type: DOCUMENT_DOWNLOADING
  })
  axios.get('/api/documents/download?googleDocId=' + googleDocId, {
      responseType: 'arraybuffer'
    })
    .then(res => {
      dispatch({
        type: DOCUMENT_DOWNLOADED,
        payload: res.data
      })
      // 修正括号位置,把type配置传入Blob构造函数
      const url = window.URL.createObjectURL(new Blob([res.data], {
        type: 'application/pdf'
      }))
      const link = document.createElement('a')
      link.href = url;
      link.setAttribute('download', 'LOR.pdf')
      document.body.appendChild(link)
      link.click()
      // 用完及时释放ObjectURL避免内存泄漏
      window.URL.revokeObjectURL(url)
      document.body.removeChild(link)
    })
    .catch(err => {
      dispatch(returnErrors(err.response.data, err.response.status))
      dispatch({
        type: DOCUMENT_DOWNLOAD_FAIL
      })
    })
}

2. 后端代码修正

推荐优先使用直接导流不存本地的方案,既避免了本地文件读写的时序、并发问题,也减少了磁盘IO开销:

erouter.get('/download', async (req, res) => {
  const googleDocId = req.query.googleDocId
  if (!googleDocId) {
    return res.status(400).json({
      msg: "googleDocId not specified"
    })
  }

  const { google } = require("googleapis")
  const path = require('path')

  const auth = new google.auth.GoogleAuth({
    keyFile: "credentials.json",
    scopes: ["https://www.googleapis.com/auth/drive"]
  })

  const client = await auth.getClient()
  const gDrive = google.drive({
    version: "v3",
    auth: client
  })

  try {
    const response = await gDrive.files.export({
      fileId: googleDocId,
      mimeType: 'application/pdf'
    }, {
      responseType: 'stream'
    })
    // 直接设置响应头,把Google返回的流导入express响应
    res.setHeader('Content-Type', 'application/pdf')
    res.setHeader('Content-Disposition', 'attachment; filename=LOR.pdf')
    response.data.pipe(res)
  } catch (err) {
    console.log(err)
    return res.status(500).json({
      msg: "PDF导出失败"
    })
  }
})

如果必须保留本地存储的逻辑,修正后的代码如下:

erouter.get('/download', async (req, res) => {
  const googleDocId = req.query.googleDocId
  if (!googleDocId) {
    return res.status(400).json({
      msg: "googleDocId not specified"
    })
  }

  const { google } = require("googleapis")
  const fs = require('fs')
  const path = require('path')

  const auth = new google.auth.GoogleAuth({
    keyFile: "credentials.json",
    scopes: ["https://www.googleapis.com/auth/drive"]
  })

  const client = await auth.getClient()
  const gDrive = google.drive({
    version: "v3",
    auth: client
  })

  try {
    const response = await gDrive.files.export({
      fileId: googleDocId,
      mimeType: 'application/pdf'
    }, {
      responseType: 'stream'
    })

    // 修正路径为完整的文件路径,可按googleDocId命名避免并发冲突
    const fileName = `LOR_${googleDocId}.pdf`
    const filePath = path.join(__dirname, "../../tmp", fileName)
    await new Promise((resolve, reject) => {
      const dest = fs.createWriteStream(filePath);
      let progress = 0

      response.data
        .on('end', () => resolve(filePath))
        .on('error', err => reject(err))
        .pipe(dest)
    })

    const options = {
      root: path.join(__dirname, "../../tmp"),
      headers: {
        'Content-Type' : 'application/pdf'
      }
    }
    res.sendFile(fileName, options, (err) => {
      if (err) throw err
      // 返回完成后删除本地临时文件,避免磁盘占用
      fs.unlinkSync(filePath)
    })
  } catch (err) {
    console.log(err)
    return res.status(500).json({
      msg: "PDF下载失败"
    })
  }
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 17:15:07