JavaScript中下载PDF blob的问题

22

我创建了一个函数,它接受blobfileName,用于下载该blob,实现如下:

TBD

const blobToBase64 = (blob, callback) => {
  const reader = new FileReader();
  reader.onloadend = () => {
    const base64 = reader.result;
    console.log({ base64 });
    callback(base64);
  };
  reader.readAsDataURL(blob);
};

const downloadFile = (blob, fileName) => () => {
  const link = document.createElement('a');
  blobToBase64(blob, (base64) => {
    link.href = base64;
    link.download = fileName;
    link.click();
  });
};


downloadFile(myBlob, myFileName);
为了尝试调试这个问题,我使用console.log记录了由reader.result创建的base64值。
那个base64值是data:application/octet-stream;base64,Mzc4MDY4...
我的PDF文件已被下载但已损坏。在我的文件下载实现中,我做错了什么?
如果有任何其他细节能帮助解决这个问题,请告诉我。我确信blob本身不是一个损坏的文件。

您的文件类型应为 application/pdf,而不是 application/octet-stream - Zenoo
2
将“application/octet-stream”替换为“application/pdf”并不能解决问题。 - Barry Michael Doyle
那么你的 blob 可能只是无效的。你确定它不是无效的吗? - Zenoo
我非常确定它不是无效的,一直在研究:/ - Barry Michael Doyle
@BarryMichaelDoyle,你解决问题了吗? - pathe.kiran
是的,被接受的答案解决了问题。 - Barry Michael Doyle
2个回答

50

我不能确定为什么你的代码不起作用,但我肯定可以说,你正在做的事情在最好的情况下是无用的。

不要将 Blob 转换为 dataURI,99%* 的情况下,你想要使用此 dataURI 完成的操作可以直接使用原始 Blob 和 blobURI 来实现。

*剩余的 1% 是当你需要创建包含二进制数据的独立文档时发生的,它确实发生过,但并不常见。

在这里,你想要做的事情(将 anchor 设置为指向 Blob 的数据)可以直接使用 Blob 完成:只需调用 URL.createObjectURL(blob) 创建一个 blobURI(它只是指向内存中的数据的指针)。

const downloadFile = (blob, fileName) => {
  const link = document.createElement('a');
  // create a blobURI pointing to our Blob
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  // some browser needs the anchor to be in the doc
  document.body.append(link);
  link.click();
  link.remove();
  // in case the Blob uses a lot of memory
  setTimeout(() => URL.revokeObjectURL(link.href), 7000);
};


downloadFile(new Blob(['random data']), "myfile.txt");


由于某种原因,iOS 对这个实现非常棘手。有人说这与合成触摸事件有关,因此我在页面上创建了一个 href 为对象 URL 的 <a>。这在 iOS 上的 Safari + Firefox 上可以工作,但是 Chrome 不行。有人遇到过这个问题吗? - 3stacks
@3stacks,可能是因为在focus中调用了revokeObjectURL,我已经编辑了一个更好的解决方案,你能确认它是否适用于你(我手头没有iOS设备)。 - Kaiido
@Kaiido感谢你的快速回复。我添加了revokeObjectURL并得到了一些不同的行为。它在iOS Safari上实际上是有效的,但是iOS Firefox只是在同一个窗口中打开blob,而iOS Chrome什么也没有做。我尝试了几种不同的方法,比如在安装时生成对象URL,然后将其添加为DOM的href,这对Safari和Firefox有效,但Chrome有问题。编辑:我认为这可能只是iOS过于糟糕了。桌面macOS和Android浏览器都很正常工作。 - 3stacks
很不幸,正如我所说,我没有iOS设备来测试这个... 标准上应该是可以工作的。Safari for iOS在v13中添加了支持,我找不到任何与FF或Chrome for iOS相关的内容。很抱歉,但我们可能需要第三个人来帮忙。附:刚刚看了Eligrey的FileSaver,他们使用了一个丑陋的isChromeIOS UA嗅探来切换到href=dataURI... - Kaiido

15

我尝试使用Fetch API从服务器下载PDF文件,但服务器响应的内容类型是octet-stream。因此,如果您检查响应,您会得到像%PDF-1.4这样的字符。

以下是解决方案:

function download(pdfUrl) {
        fetch(pdfUrl).then(resp => resp.arrayBuffer()).then(resp => {

            // set the blog type to final pdf
            const file = new Blob([resp], {type: 'application/pdf'});

            // process to auto download it
            const fileURL = URL.createObjectURL(file);
            const link = document.createElement('a');
            link.href = fileURL;
            link.download = "FileName" + new Date() + ".pdf";
            link.click();
        });
    }

您可以使用相同的方法,在创建blob之前解码八位字节流的内容。


对我来说,你的代码没问题,但我没有使用两次createObjectURL(),只用了一次(请参见const fileURL = ...link.href = ...行)。我没有尝试上面的双重createObjectURL版本,所以我不能说它是否有效。 - Daniele Cruciani
@DanieleCruciani 恐怕你是对的。在使代码更易读的同时,我做了两次。更新了代码。谢谢。 - Akansh
1
由于它尝试从服务器下载,您很可能会遇到CORS错误。 - mending3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接