如何将从 http 调用收到的 pdf 加载到查看器中?

Posted

技术标签:

【中文标题】如何将从 http 调用收到的 pdf 加载到查看器中?【英文标题】:How to load a pdf received from an http call into the viewer? 【发布时间】:2021-05-12 08:23:12 【问题描述】:

我从以下地址获取 pdf:

return this.http.get(path, observe: 'response', responseType: 'arraybuffer' );

并将其作为 blob 处理:

let blob: Blob = new Blob([response.body], type: 'application/pdf' );

我现在希望将该文件存储在本地并通过本地 url 引用它。我需要使用 url 的原因是 viewer.loadModel() 需要它作为参数。我曾尝试使用let url = window.URL.createObjectURL(this.blobToFile(blob, 'viewer.pdf')); 获取网址。

当我这样做时,我得到:https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a 但是,查看器不知道后面没有“.pdf”的文件类型。如果我将 .pdf 放在该 url 后面:https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a.pdf,它似乎加载了一些数据,但我收到以下错误:

core.js:6241 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'getData' of undefined
TypeError: Cannot read property 'getData' of undefined
    at v (Viewer3D.js:1335)
    at PDFLoader.js:1358
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
    at resolvePromise (zone.js:832)
    at zone.js:739
    at zone.js:755
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)

在下面的最后一行:(Viewer3D.js:1335)

Viewer3D.prototype.loadModel = async function(url, options, onSuccessCallback, onErrorCallback, onWorkerStart) 
        var self = this;

        // Kind of sucks, but I couldn't think of a better way because loaders
        // are so
        var reservation = self.impl._reserveLoadingFile();     // Reserve a slot for a loading file
        options = options || ;

        if (typeof options.skipPropertyDb === "undefined") 
            var skipParam = getParameterByName("skipPropertyDb") || "";
            options.skipPropertyDb = skipParam === "true" ? true : (skipParam === "false" ? false : undefined);
        

        var loaderInstance;
        function onDone( error, model ) 
            self.impl._removeLoadingFile(loaderInstance);
            if (error) 
                self.dispatchEvent( type: et.LOADER_LOAD_ERROR_EVENT, error: error, loader: loaderInstance )
                onError( error.code, error.msg, error.args );
                return;
            

            model.getData().underlayRaster = options.underlayRaster && model.getLeaflet();

有没有一种方法可以在本地存储来自 blob 的 pdf,然后获取一个可以使用它的本地 URL?或者有没有办法可以传递不记名令牌,以便加载程序可以访问存储?我不确定还能做什么,因为 viewer.loadModel() 似乎只有在我使用文件的本地绝对路径时才有效(例如 https://localhost:3000/Content/Documents/qcad1.pdf") . 我还想提一下,我无法通过模型导数 api 处理它,因为如果这样做,它会丢失 pdf 向量。

更多上下文: 这是我正在加载模型的地方...

Autodesk.Viewing.Initializer( env: 'Local' , () => 
            this.viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
            this.viewer.start();
            this.viewer.setTheme('dark-theme');
            this.viewer.loadExtension('Autodesk.Viewing.MarkupsCore');

            this.viewer.loadModel(this.pdfUrl, , this.initializeMeasureSettings.bind(this),
                () => 
                    console.log('File could not be loaded');

                    // On error was hoping to load a sample model but it loses context to the viewer somehow
                    //this.viewer.loadModel(this.samplePDF, , this.initializeMeasureSettings);
                );

            this.viewer.addEventListener('measurement-changed', this.onMeasurementChanged.bind(this));
            this.viewer.addEventListener('measurement-completed', this.onMeasurementCompleted.bind(this));
            this.viewer.addEventListener('finished-calibration', this.onCalibrationFinished.bind(this));
            this.viewer.addEventListener('delete-measurement', this.onObjectDeleted.bind(this));
            this.viewer.addEventListener('extensionLoaded', this.extensionLoaded.bind(this));

        );

【问题讨论】:

只是为了仔细检查,您为什么要先下载 PDF,然后 然后 试图将其转换为客户端上的 URL?是不是因为原始PDF地址被保护了? 是的。如果我尝试通过常规 URL 访问我们的文件,它需要来自客户端的不记名令牌,并且无法将其传递给 viewer.loadModel()。所以我的想法是将实际数据下载到浏览器并如上所述获取对它的引用。但似乎不起作用。 【参考方案1】:

恐怕loadModel 方法不支持指定自定义 HTTP 请求标头的任何方式,因此如果对 PDF 文件的访问受到访问令牌的保护,那将很棘手。

您可以尝试“破解”loadModel 函数内部的逻辑,而不是将 .pdf 直接附加到数据 URI,而是通过在 URL 查询或哈希中伪造扩展名,例如:

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a?name=foo.pdf

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a#foo.pdf

可能足以说服查看者使用 PDF 加载器,同时它不会破坏 URL 的含义。

【讨论】:

我试过了,它通过抓取 PDF 加载器,但仍然失败,出现上面的错误 好的,我刚刚注意到您的代码 sn-p 中有一些内容。您似乎没有在其中的任何地方加载 Autodesk.PDF 扩展名。我不确定加载没有它的 PDF 是否可以正常工作,但为了确定,在加载 PDF 之前激活扩展程序(如 forge.autodesk.com/blog/… 中所述)。 是的,我一直在这样做,但是没有它与本地文件一起工作,所以我想也许我不需要它。 我明白了...我担心 PDF 加载器中有一些额外的检查假定 URL 是正确的服务器端 URL,而不是数据 URI。 想出了一个办法!有兴趣的请看答案【参考方案2】:

所以我最终深入研究了 pdfLoader,并找到了一种向下传递不记名令牌的方法,PDFJS 在从 url 加载文件时使用该令牌!

就在调用viewer.loadModel之前我设置了:

Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = Authorization: `Bearer $this.authenticationService.getToken` ;

这是访问 PDFLoader.js:749 params.httpHeaders = av.endpoint.HTTP_REQUEST_HEADERS;

这个 params 对象最终被传递到 Promise.resolve 中的PDFJS.getDocument(params)(第 1370 行)。

这是 pdf.js 中的标准功能,但也许 Autodesk 可以通过允许在 viewer.loadModel() 中的选项对象中传递它来简化它。

无论如何,这让我可以从后端安全地下载文件并将它们加载到查看器中。

【讨论】:

以上是关于如何将从 http 调用收到的 pdf 加载到查看器中?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 加载PDF问题无法显示电子章问题

如何打开从API调用收到的响应文件

如何解析从文件加载的soap消息?

如何自动化 HTML 到 PDF 的转换?

使用 LeafletLoader 而不是 PDFLoader 将 PDF 加载到伪造查看器中不起作用

如何将从 Ajax 请求收到的响应重定向到另一个 html 页面