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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 09:12:37