【问题标题】:Displaying pdf files and dowloading onclick from an arraybuffer blob显示 pdf 文件并从 arraybuffer blob 下载 onclick
【发布时间】:2021-03-02 17:04:08
【问题描述】:

我在 nodejs 后端创建 pdf 并将它们作为 blob 存储在我的数据库中。

在我的前端(React)中,我正在调用 api 并接收整个 blob 数据列表并将其转换为如下文件。然后从它们创建 URL 并将 URL 存储为数组和状态。

componentDidMount() {
    const fileURLs = [];
    fetch(`/api/invoices`)
      .then((res) => res.json())
      .then((invoices) => {
        this.setState({ invoices });
        invoices.map((inv) => {
          const file = new Blob([inv.file_content], {
            type: "application/pdf",
          });
          const fileURL = URL.createObjectURL(file);
          fileURLs.push(fileURL);
        });
        this.setState({ fileURLs });
      })
  }

我收到的 blob 数据也如下所示: blob data

在我的 render() 中,我将 url 链接到锚标记以在新选项卡中打开它。网址看起来像这样:blob:http://localhost:3000/1b5c4fe3-bb7c-4850-8804-d850f824b07f

this.state.invoices.map((p, i) => (
...
  <a
    href={this.state.fileURLs[i]}
    target="_blank"
    rel="noopener noreferrer"
  >
    {p.file_name}
  </a>
...
)

如果我访问该网址,它只会显示 无法加载 PDF 文档。

在我的后端,我将 response.arrayBuffer() 存储为我的 blob 数据。 我还将 pdf 保存在本地文件系统中只是为了测试,它可以毫无问题地创建 pdf 文件。

编辑:我也附上了我的一些后端代码。

.then(async data => {
  const response = await fetch(api, {
      method: "GET",
      headers: {
          "Accept": "application/octet-stream",
          "Authorization": "Bearer " + token
      },
      encoding: null
 });
const filename = response.headers.get('Content-Disposition')
    .split(';')
    .find(n => n.includes('filename='))
    .replace('filename=', '')
    .trim();
const invoice = await response.arrayBuffer();
return [filename, invoice];
})
.then(data => {
    const created = moment().format('YYYY-MM-DD HH:mm:ss');
    //save pdf file in local directory
    var base64Str = Buffer.from(data[1]).toString('base64');
    base64.base64Decode(base64Str, './frontend/src/invoices/' + data[0]);
    
// save pdf in database table
    const query = `call create_invoice_procedure('${data[0]}','${data[1]}','${created}')`;
    connection.query(query, (err, rows) => {
        if (err) {
            throw err;
        }
     });
  })

获取所有发票的发票路径:

router.get('/', (req, res) => {
    var query = `SELECT * from table`;
    connection.query(query, (error, results) => {
        if (error) {
            return error;
        }
        else {
            res.json(results);
        }
    })
});

所以我的问题是我无法在前端查看或下载 pdf。

【问题讨论】:

  • 将整个 PDF 存储在应用程序状态中是否有特定原因?为什么不让节点提供一个端点来提供 PDF 并在您的前端链接到该端点? (另外,控制台输出显示包含 17 个字节的缓冲区,这听起来像是您不小心发送了文件名,而不是二进制数据?)
  • 1.所以你是说我应该在后端创建blob url并将其保存在数据库中的一个字段中并在前端进行相应的访问?我也会试试这个。 2. 在后端,我发送字节长度约为 44000 的数组缓冲区,但在前端它肯定很小,即使它是相同的字段。
  • 不,忘记创建 blob url。我是说“编写节点代码,以便转到localhost:3000/api/invoices/:filename 使节点为文件服务”。此外,如果您的前端收到 17 个字节,那么您的后端可能正在提供 17 个字节,抱歉。最后,您的后端数据库不应该存储二进制 PDF 的 Blob,它应该存储每张发票的数据,以便动态构建 PDF。如果您需要某种数字存档,您可能应该改用服务器的文件系统。
  • 您存储在 db 中的数据实际上是数组缓冲区。 nodejs 本身不支持 Blob。我在我的应用程序中为使其工作所做的工作完全相同,获取数组缓冲区(通过从 FS 动态读取 pdf)-> 在前端将其转换为 blob -> 从 blob 创建 objectUrl 并使用 window.open带有一些附加参数。这就是我怀疑您可能因为锚标签而遇到此问题的原因。另外,您使用的数据库是什么?它可能有更好的方法来存储 pdf 文件,例如 mongodb 中的 gridFs
  • @ChrisG 你的建议很有希望。我将数据存储为 json 并在需要的地方使用数据构建 pdf。谢谢,我会告诉你的。

标签: javascript node.js reactjs pdf blob


【解决方案1】:

如果您确定您的 blob 是正确的,请尝试在任何元素上添加点击事件以打开 url,而不是在锚标记上使用 href。 这对我有用:

const blob = new Blob([fileData], {
        type: 'application/pdf'
      });
      const fileURL = URL.createObjectURL(blob);
      window.open(fileURL, '_blank', 'location=yes,height=650,width=1000,scrollbars=yes,status=yes');

【讨论】:

  • 再说一次,既然可以直接提供文件,为什么还要在前端构建一个 blob?
  • @ChrisG 我同意,这是最简单的解决方案。但就我而言,我不想在新窗口中打开,而是想把它放在我的 Angular 应用程序中并使用对象或 iframe 显示。而且由于问题已经设置了大部分块,我建议在不改变整个设计的情况下可以工作。
猜你喜欢
  • 2017-06-25
  • 2022-11-02
  • 1970-01-01
  • 2018-12-07
  • 1970-01-01
  • 1970-01-01
  • 2015-10-05
  • 2021-11-19
  • 2015-07-15
相关资源
最近更新 更多