Node.js中从流响应获取的PDF始终损坏或受密码保护求助
PDF下载后损坏/提示加密的问题解决思路
我完全懂这种卡壳的感觉!耗了一整天还没搞定确实让人头大。先帮你捋捋问题出在哪里,再给你一步步的修复方案:
核心问题分析
从你的代码和提供的PDF片段来看,最大的问题是二进制流在传输过程中被错误解析成了JSON格式,导致原始的PDF数据被破坏。另外还有几个小细节没处理到位,一起导致了文件损坏或加密提示。
先拆解流程里的坑:
- 服务助手的
sendRequest用axios请求远程API时,默认把响应解析成JSON,但远程返回的是二进制PDF流,这一步直接把二进制数据转成了错误的字符串/JSON,数据彻底乱了。 - axios请求里的
accept头设置成了application/json,远程API可能会因为这个头返回错误格式,或者即使返回二进制,axios还是会硬转成JSON。 - 后端返回数据时,没有确保传输的是原始二进制,而且CORS头的写法也不够规范。
分步修复方案
1. 修复服务助手的二进制请求处理
首先要让axios正确接收二进制流,而不是解析成JSON。修改你的sendRequest和getFileFromCard函数:
修改getFileFromCard:
const getFileFromCard = async (card_id, fileName) => { // 新增responseType配置,告诉axios接收arraybuffer return await sendRequest(`card/${card_id}/file`, 'get', { fileName }, { responseType: 'arraybuffer', headers: { accept: 'application/pdf' } }) .then(res => { return res.data; }); };
修改sendRequest:
让它支持传入额外的axios配置,并且去掉不必要的Content-Type头(GET请求不需要这个):
const sendRequest = async (api_method, http_method, params = {}, axiosOptionsOverride = {}) => { const url = `${API_URL}/${api_method}`; return new Promise(async (resolve, reject) => { try { // 先获取token const authResponse = await axios.post(`${API_URL}/auth`, { login: API_LOGIN, password: API_PASS }); const { token } = authResponse.data; const baseOptions = { method: http_method, url, headers: { Authorization: `Bearer ${token}`, 'accept-language': 'en_US', // 合并传入的自定义header ...(axiosOptionsOverride.headers || {}) }, // 合并传入的配置(比如responseType) ...axiosOptionsOverride }; if (http_method === 'get') { baseOptions.params = params; } else if (['post', 'put'].includes(http_method.toLowerCase())) { baseOptions.data = params; // POST/PUT请求才需要Content-Type baseOptions.headers['Content-Type'] = 'application/json'; } // 发送请求并resolve const res = await axios(baseOptions); resolve(res); } catch (err) { console.error('Request failed:', err); reject(err); // 一定要reject,不然错误会被吞掉 } }); };
2. 修复后端控制器的响应处理
确保后端正确返回二进制数据,并且规范CORS配置(建议用全局中间件,不要在单个路由里加):
router.post('/get-file-from-card', checkPermissions(), async (req, res) => { const { card_id, filename } = req.body; try { const rawFileData = await getFileFromCard(card_id, filename); // 全局配置CORS,这里可以删掉这两行 // res.header('Access-Control-Allow-Origin', '*'); // res.header('Access-Control-Allow-Headers', 'X-Requested-With'); // 设置正确的响应头 res.setHeader('Content-Type', 'application/pdf'); // 可选:设置下载文件名,前端可以直接用这个,不用硬编码file.pdf res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); // 发送二进制数据 res.send(rawFileData); } catch (err) { res.status(500).json({ error: 1, message: err.toString() }); } });
3. 前端Action的小优化
增加错误检查,确保请求成功再处理:
export const getCardFile = (card_id, filename) => dispatch => { fetch(`${config.protocol}://${config.server}:${config.port}/api/v1/get-file-from-card`, { method: 'post', headers: { Authorization: localStorage.getItem('token'), 'Content-type': 'application/json; charset=UTF-8', }, body: JSON.stringify({ card_id, filename }), }) .then(response => { if (!response.ok) { throw new Error(`请求失败,状态码:${response.status}`); } return response.blob(); }) .then(blob => { // 用后端返回的文件名,或者保持原来的 FileSaver.saveAs(blob, filename || 'file.pdf'); }) .catch(error => console.error('文件下载失败:', error)); };
额外说明
你提供的PDF片段里有/Encrypt 48 0 R,说明这个PDF本身可能是加密的。如果修复后还是提示密码保护,那大概率是远程API返回的就是加密PDF,需要确认是否有解密的方式或者密码。但先解决数据损坏的问题,再排查加密的情况。
内容的提问来源于stack exchange,提问作者Evgeny Malkov
相关产品推荐
相关产品推荐

