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
相关产品推荐
相关产品推荐

